C 포인터 질문좀욧
마땅히 물어볼데가 여기밖에 없네요;;;;;^^
혼자 연구하는 C/C++ 공부하면서 안풀리는게 생겼는데...
x86 CPU 환경에서
int main()
{
int ar[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
printf("%d\n", sizeof(ar));
printf("%d\n", sizeof(ar+0));
printf("%d\n", sizeof(ar+2));
// 위 세 문장의 차이가 무엇인지 궁금합니다.
//나름대로 생각해서 적어봤는데 틀린 부분이 무엇인지 여쭤봅니다.~
//배열이 sizeof연산자의 피연산자일때 배열 그 자체로 평가된다고 책에 적혀있길래
//첫번째 문장: ar은 배열 그 자체의 크기니까 모든 요소들의 크기 48이 나와야겠구나~!
//두번째 문장: 수학적으로 ar+0 == ar 이지 않나? 그럼 똑같잖아? 그럼 48 나와야지?
//세번째 문장: ar+2는 2번째 요소 즉 세번째 배열명을 가리키므로 세번째 배열의 크기 16이 나와야겠지?
//
printf("%d\n", sizeof(*(ar+2)));
//네번째 문장: 이것도 ar[2]랑 같은 의미니까 세번째 배열명(==이 배열의 시작 번지)을 가리키고 배열명 == 포인터 상수 이지.
// sizeof연산자의 피연산자이니까 배열 그 자체의 크기 16이 나오지.
return 0;
}
이렇게 판단했는데
몇개는 맞는데 몇개는 틀리네요...
누가 이 돌머리 이해시켜주실 분 없나요?~
자, 답글을 달기에 앞서, 한 가지 좀 되묻도록
자, 답글을 달기에 앞서, 한 가지 좀 되묻도록 합시다.
대체 어떻게, 무슨 수로 말이죠,
표준에 의거 비직관적인 동작을 할 여지가 조금이라도 있으면, 꼭 누군가가 그 코드를 정말로 작성하는 거죠?
sizeof(ar+0)
라니, 상상도 못 했습니다.표준에 의거 설명을 드릴 수는 있습니다만, 이렇게 교묘하게 표준 해킹을 하는 코드라니요.
혹시 알고 그러신 건가요? 그게 아니면 ioccc 같은 대회에서 요구하는 종류의 능력을 타고나신 걸지도...
흠, 우선, 이 답변글에서 C언어 표준 설명은 C99 표준을 기반으로 하고 있다는 것을 밝혀 둡니다. 물론 다른 버전의 표준이라고 해도 큰 틀은 달라지지 않습니다.
이 모든 일의 단초는, C언어 표현식(expression)의 문법이 애초에 배열을 직접적으로 다루는 경우가 극히 드물다는 겁니다.
아무래도 표준을 제정한 사람들이, 그렇게 함으로써 표준을 좀 더 단순하게 만들 수 있을 거라고 생각한 것 같아요.
아무튼, 덕분에 C언어에서 초보 프로그래머들이 배열을 사용하고 있다고 믿고 있을 때, 사실은 그렇지 않은 경우가 종종 있어요.
심지어 가장 "배열"스러운 연산자인 배열 첨자 연산자(Array subscripting operator, [])를 사용할 때조차도 그렇습니다.
"타입 T에 대한 배열"은, 다음과 같은 예외 상황을 제외하고, 그 배열의 첫 원소를 가리키는 rvalue "타입 T에 대한 포인터"로 암시적 변환됩니다.
쉽게 말해서, 배열
arr
가 마치&arr[0]
과 같이 쓰인 것처럼 변환된다는 거죠.예외는 C99 기준으로 딱 셋입니다.
1)
sizeof
연산자의 operand로 사용될 때2) unary & 연산자의 operand로 사용될 때
3) 문자열 상수(string literal)로 배열을 초기화할 때
더 쉽게, 예를 들어서 설명해 드리죠.
자, 이제 질문자님의 코드를 풀어 봅시다.
sizeof(ar)
: 예외 1에 의해서ar
은 타입int[3][4]
를 고스란히 유지하고, 따라서int[3][4]
가 되어 답은12 * sizeof(int)
입니다.sizeof(int)
는 x86에서 보통 4니까 아마 48이 나오겠죠.sizeof(ar+0)
:+0
은 아무 효과 없는 것처럼 보이지만, 어쨌건 있기는 있는 거고,ar
가sizeof
의 operand가 아니게 만드는 결과를 초래합니다! 결국 아무 예외 조항에도 걸리지 않으니ar
는 속절없이int (*)[4]
라는 포인터 타입으로 변하고 맙니다. 그러고 나서+0
이 적용되는거고, 이제 와서 아무 효과 없어봤자 변환된 타입이 원래대로 돌아가지는 않지요. 결국 답은sizeof(int (*)[4])
이고, 뭐 포인터 하나 크기가 반환되겠죠. x86이면 4, x86_64면 8...sizeof(ar+2)
: 위와 같습니다. 위는{1, 2, 3, 4}
를 가리키는 포인터이고, 이건{9, 10, 11, 12}
를 가리키는 포인터겠죠. 어쨌든 타입은int (*)[4]
으로 같으니까요. 그러니 역시 답은 x86이면 4, x86_64면 8...sizeof(*(ar+2))
: 뭐 별 거 있습니까.ar+2
가{9, 10, 11, 12}
를 가리키는int (*)[4]
타입 포인터였으니,*(ar+2)
는{9, 10, 11, 12}
인int [4]
타입 배열인 거죠. 배열이sizeof
연산자의 operand가 되고 있으므로 예외 1에 걸리고, 배열 타입을 고스란히 유지하여sizeof(int [4])
가 됩니다. 그러므로 답은4 * sizeof(int)
.sizeof(int)
는 x86에서 보통 4니까 아마 16이 나오겠죠.어때요, 맞았나요? 가서 한 번 확인해봅시다.
실행 결과 : http://ideone.com/VimNlY
예상하셨던 것과 달리 비수학적(!)이게도
ar
이ar+0
과 다르게 동작해서 많이 당황하셨나요?어쩔 수 없습니다. 이건 대수학이 아니거든요. C언어의 semantic이라는 거죠.
그리고, 아무리 그렇다고 해도 ioccc 출품작 말고는
sizeof(ar+0)
같은 코드 쓰지 마세요.댓글 달기