#include <Turboc.h>
void main(void)
{
int ar[5]={1,2,3,4,5};
printf("ar[2]=%d\n",ar[2]);
printf("ar[2]=%d\n",*(ar+2));
printf("ar[2]=%d\n",2[ar]);
}
크기 5의 정수형 배열 ar을 선언 및 초기화하고 이 배열의 2번째 요소를 다양한 방법으로 읽어 보았다. 실행 결과 "ar[2]=3"이라는 똑같은 문자열이 세 번 출력되는데 세 방법이 모두 같은 배열 요소를 읽어낸다. ar[2] 연산식은 ar 배열의 2번째 요소를 의미하므로 당연히 3이 읽혀진다. 더 이상 이유를 설명할 필요가 없을 정도로 당연한 결과이다.
*(ar+2)도 마찬가지로 ar 배열의 2번째 요소를 읽는다. 앞 장에서 실습했다시피 배열명 그자체는 배열의 시작 번지를 가리키는 포인터 상수이므로 ar배열명은 ar 배열의 시작 번지를 가리킬 것이다. 이 번지에서 +2하여 두 칸 다음으로 이동하면 8바이트 뒤쪽 번지가 된다. ar이 정수형 배열(따라서 정수형 포인터 상수)이므로 +2는 +2*sizeof(int) 바이트 더 뒤쪽이 되며 포인터와 정수를 더했으므로 결과는 역시 포인터이다. 마지막으로 *연산자는 이 번지 이후 4바이트의 정수를 읽으므로 결과는 3이 된다
*(ar+2)가 어째서 ar[2]와 같은지 이유를 따질 필요가 없다. 이것은 일종의 약속이며 [ ] 연산자는 지정한 첨자의 배열 요소를 읽는 포인터 연산을 하도록 정의되어 있는 것이다. 포인터가 가리키는 배열의 n번째 요소를 읽으려면 배열의 시작 번지에서 n만큼 더한 후 *연산자로 그 번지의 값을 읽으면 된다. 이 연산식이 바로 *(ptr+n)이며 이 식은 포인터 연산 규칙과 *연산자에 의해 배열 요소를 아주 잘 읽어낸다.
그래서 *(ptr+n)식만 이해할 수 있으면 원하는 배열 요소를 자유롭게 액세스할 수 있다. 그런데 이 식은 잘 동작하기는 하지만 직관적이지 못하고 왠지 복잡해 보인다. 게다가 배열 요소를 읽어야 할 경우는 아주 흔하기 때문에 좀 더 읽기 쉬운 형태의 [ ] 연산자를 정의해 놓은 것이다. 아무래도 *(ptr+n)보다는 ptr[n]이 더 읽기 쉽고 보기에도 좋다. 배열 요소를 읽을 때 사용하는 [ ] 연산자는 내부적으로 포인터 연산을 수행하는 포인터 연산자로 해석되며 *(ptr+n)과 완전하게 동일한 식이다.
[ ] 연산자가 배열 요소 참조를 위해 특별히 따로 정의되어 있는 것이 아니라 알고 보면 포인터 연산의 다른 표기법일 뿐이다. 위 예제의 세 번째 출력문은 이를 분명히 확인시켜 준다. 2[ar]이라는 표현식이 어색해 보이고 틀린 것 같지만 에러도 발생하지 않고 실행도 정상적이다. 왜 그런지 보자. 정의에 의해 2[ar]은 *(2+ar)이 되고 덧셈은 교환법칙이 성립하므로 이 식은 *(ar+2)와 같다. 결국 2[ar]=ar[2]와 완전히 같은 식이므로 컴파일러가 이 식을 해석하는데 아무 문제가 없는 것이다.
사실 ptr[n] 표현식은 컴파일러에 의해 *(ptr+n)으로 바뀐 후 컴파일되며 생성되는 기계어 코드도 완전히 동일하다. 연산 순위표를 보면 [ ] 연산자가 *보다 우선 순위가 높은 것으로 되어 있는데 이는 두 연산자가 달라서 그런 것이 아니라 [ ] 연산자의 정의에 괄호가 포함되어 있기 때문이다. 괄호는 어떠한 연산자보다도 우선 순위가 높으며 따라서 [ ] 연산자가 *보다 더 높은 우선 순위를 가지는 것은 지극히 자연스럽다.
[ ] 연산자가 1차 배열에 대해 동작하는 것은 일종의 약속이므로 외우기만 하면 된다. 그렇다면 2차 배열에서 [ ] 연산자가 어떻게 동작하는지 연구해 보자. 이 과정을 보면 확실히 C의 다차원 배열은 배열의 배열이라는 것을 확인할 수 있다. int ar[3][4] 배열에서 ar[2][1] 연산식을 분석해 보자. ar[2][1]은 정의에 의해 *(*(ar+2)+1)이 되며 컴파일러는 이 포인터 연산식을 실행할 것이다. 연산 순위에 따라 괄호의 제일 안쪽부터 연산된다.
#include <Turboc.h>
void main(void)
{
int ar[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
printf("ar[2][1]=%d\n",ar[2][1]);
printf("ar[2][1]=%d\n",*(*(ar+2)+1));
printf("sizeof(ar+2)=%d\n",sizeof(ar+2));
printf("sizeof(*(ar+2))=%d\n",sizeof(*(ar+2)));
printf("sizeof(*(ar+2)+1)=%d\n",sizeof(*(ar+2)+1));
}
void main(void)
{
int ar[5]={1,2,3,4,5};
printf("ar[2]=%d\n",ar[2]);
printf("ar[2]=%d\n",*(ar+2));
printf("ar[2]=%d\n",2[ar]);
}
크기 5의 정수형 배열 ar을 선언 및 초기화하고 이 배열의 2번째 요소를 다양한 방법으로 읽어 보았다. 실행 결과 "ar[2]=3"이라는 똑같은 문자열이 세 번 출력되는데 세 방법이 모두 같은 배열 요소를 읽어낸다. ar[2] 연산식은 ar 배열의 2번째 요소를 의미하므로 당연히 3이 읽혀진다. 더 이상 이유를 설명할 필요가 없을 정도로 당연한 결과이다.
*(ar+2)도 마찬가지로 ar 배열의 2번째 요소를 읽는다. 앞 장에서 실습했다시피 배열명 그자체는 배열의 시작 번지를 가리키는 포인터 상수이므로 ar배열명은 ar 배열의 시작 번지를 가리킬 것이다. 이 번지에서 +2하여 두 칸 다음으로 이동하면 8바이트 뒤쪽 번지가 된다. ar이 정수형 배열(따라서 정수형 포인터 상수)이므로 +2는 +2*sizeof(int) 바이트 더 뒤쪽이 되며 포인터와 정수를 더했으므로 결과는 역시 포인터이다. 마지막으로 *연산자는 이 번지 이후 4바이트의 정수를 읽으므로 결과는 3이 된다
*(ar+2)가 어째서 ar[2]와 같은지 이유를 따질 필요가 없다. 이것은 일종의 약속이며 [ ] 연산자는 지정한 첨자의 배열 요소를 읽는 포인터 연산을 하도록 정의되어 있는 것이다. 포인터가 가리키는 배열의 n번째 요소를 읽으려면 배열의 시작 번지에서 n만큼 더한 후 *연산자로 그 번지의 값을 읽으면 된다. 이 연산식이 바로 *(ptr+n)이며 이 식은 포인터 연산 규칙과 *연산자에 의해 배열 요소를 아주 잘 읽어낸다.
그래서 *(ptr+n)식만 이해할 수 있으면 원하는 배열 요소를 자유롭게 액세스할 수 있다. 그런데 이 식은 잘 동작하기는 하지만 직관적이지 못하고 왠지 복잡해 보인다. 게다가 배열 요소를 읽어야 할 경우는 아주 흔하기 때문에 좀 더 읽기 쉬운 형태의 [ ] 연산자를 정의해 놓은 것이다. 아무래도 *(ptr+n)보다는 ptr[n]이 더 읽기 쉽고 보기에도 좋다. 배열 요소를 읽을 때 사용하는 [ ] 연산자는 내부적으로 포인터 연산을 수행하는 포인터 연산자로 해석되며 *(ptr+n)과 완전하게 동일한 식이다.
[ ] 연산자가 배열 요소 참조를 위해 특별히 따로 정의되어 있는 것이 아니라 알고 보면 포인터 연산의 다른 표기법일 뿐이다. 위 예제의 세 번째 출력문은 이를 분명히 확인시켜 준다. 2[ar]이라는 표현식이 어색해 보이고 틀린 것 같지만 에러도 발생하지 않고 실행도 정상적이다. 왜 그런지 보자. 정의에 의해 2[ar]은 *(2+ar)이 되고 덧셈은 교환법칙이 성립하므로 이 식은 *(ar+2)와 같다. 결국 2[ar]=ar[2]와 완전히 같은 식이므로 컴파일러가 이 식을 해석하는데 아무 문제가 없는 것이다.
사실 ptr[n] 표현식은 컴파일러에 의해 *(ptr+n)으로 바뀐 후 컴파일되며 생성되는 기계어 코드도 완전히 동일하다. 연산 순위표를 보면 [ ] 연산자가 *보다 우선 순위가 높은 것으로 되어 있는데 이는 두 연산자가 달라서 그런 것이 아니라 [ ] 연산자의 정의에 괄호가 포함되어 있기 때문이다. 괄호는 어떠한 연산자보다도 우선 순위가 높으며 따라서 [ ] 연산자가 *보다 더 높은 우선 순위를 가지는 것은 지극히 자연스럽다.
[ ] 연산자가 1차 배열에 대해 동작하는 것은 일종의 약속이므로 외우기만 하면 된다. 그렇다면 2차 배열에서 [ ] 연산자가 어떻게 동작하는지 연구해 보자. 이 과정을 보면 확실히 C의 다차원 배열은 배열의 배열이라는 것을 확인할 수 있다. int ar[3][4] 배열에서 ar[2][1] 연산식을 분석해 보자. ar[2][1]은 정의에 의해 *(*(ar+2)+1)이 되며 컴파일러는 이 포인터 연산식을 실행할 것이다. 연산 순위에 따라 괄호의 제일 안쪽부터 연산된다.
#include <Turboc.h>
void main(void)
{
int ar[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
printf("ar[2][1]=%d\n",ar[2][1]);
printf("ar[2][1]=%d\n",*(*(ar+2)+1));
printf("sizeof(ar+2)=%d\n",sizeof(ar+2));
printf("sizeof(*(ar+2))=%d\n",sizeof(*(ar+2)));
printf("sizeof(*(ar+2)+1)=%d\n",sizeof(*(ar+2)+1));
}
'Native > C' 카테고리의 다른 글
배열 출력 (0) | 2013.10.02 |
---|---|
인수 작성법 (pointer, array) (0) | 2013.10.02 |
main 함수의 인수 (0) | 2013.10.02 |
calloc 재할당 (0) | 2013.10.02 |
malloc 할당 및 해제 (0) | 2013.10.02 |