구조체 포인터의 casting
책 잘 읽고 있습니다. 빨리는 읽지 못하네요. 한번에 이해할 만한 C 입문서가 아니기 때문에 내용 하나하나 되새겨 읽고 있습니다. 홈페이지 오픈이 늦어지네요... 빨리 여셨으면 좋겠는데 :cry:
지난번에 pointer의 casting에 관련된 질문을 드렸었습니다.
일반적으로 생각하는 것과는 달리 pointer의 casting 굉장히 복잡하다는 것을 깨달았습니다.
문득 그동안 갖고 있었던 질문이 하나 생각났습니다.
리눅스에서의 소켓 프로그래밍에서 소켓 주소를 담는 구조체 sockaddr를 그대로 사용하지 않고 sockaddr_in으로 casting해서 사용하는 것이 일반적입니다. 그 이유는 소켓 구조체 sockaddr이 IP 주소, 포트 번호 등을 직접 읽거나 쓰기가 불편하기 때문입니다.
그래서 bind()나 connect()는 다음과 같이 사용하는 것이 일반적입니다.
main() { struct hostent *myhost; struct sockaddr_in me; int s_waiting, s; ...snip... s_waiting = socket(AF_INET, SOCK_STREAM, 0); bind(s_waiting, (struct sockaddr *) &me, sizeof(me)); ...snip... }
라이브러리의 소켓 구조체 sockaddr과 sockaddr_in는 다음과 같습니다.
struct sockaddr { u_short sa_family; /* address family */ char sa_data[14]; /* 주소 */ }; struct sockaddr_in { short sin_family; /* 주소체계 */ u_short sin_port; /* 16bit 포트 번호 */ struct in_addr sin_addr; /* 32bit IP 주소 */ char sin_zero[8]; /* dummy */ };
리눅스 소켓 프로그래밍을 다룬 각종 서적과 문서에는 sockaddr_in의 마지막 sin_zero 멤버가 sockaddr과 sockaddr_in의 호환성을 위해 비워두는 더미 멤버라고 소개되어 있습니다. 16byte를 맞추기 위해 존재하는 8byte라는 소리죠.
사실 지난번의 예에서처럼 char type을 int type으로 casting 하는 것조차 정렬제한을 위반할 가능성이 있는데요. 단순히 두 구조체의 크기가 같다고 해서 같은 내부 표현과 정렬제한을 갖는 것은 아니지 않습니까?
라이브러리에 관련된 문제이기 때문에 임플리멘테이션(GCC)이 명시적으로 두 구조체에 대해서만 casting을 허가하는 것인가요?
구조체를 가리키는 pointer간의 casting에 대해서 표준에서 언급하고 있는 내용은 없나요? (...없겠죠?)
책에서도 포인터 및 포인터 casting 과 구조체 부분을 읽어봤습니다만 구조체 포인터의 casting에 대해서는 나와있지 않는 듯 했습니다. 물론 다 읽어보지 못했기 때문에 그런 것이라면 어디를 봐서 공부하면 좋을지 지적해 주시기 바랍니다.
[quote]사실 지난번의 예에서처럼 char type을 int ty
두 구조체 sockaddr과 sockaddr_in은 다른 크기를 가질 수도 있습니다. 무엇보다 short형의 크기 자체가 뚜렷하게 정의되어 있는 것이 아닙니다. 또한 구조체 안의 padding bit도 구현체 마음대로 줄 수 있는 것이고... 결국 아무것도 보장된 바 없습니다. 만약 다양한 특성을 가진 여러 시스템에서 위의 코드를 돌아가게 하려고 한다면 많은 트릭과 #ifdef ... #endif 구문이 필요하겠죠.
제가 알기로는 많은 컴파일러에서 정렬 제한을 무시한다던가 맴버 변수들의 재배치를 금지한다던가 padding bit를 금지한다던가 하는 옵션이나 #pragma 지시자를 제공하는 걸로 알고 있습니다.
변환된 주소값이 정렬제한만 어기지 않는다면 모든 포인터형은 다른 포인터형으로 변환 가능합니다. 물론 그렇다고 해서 참조(읽기, 쓰기)했을때 문제가 생기지 않는다는 것은 아니며, 그럴 경우는 대부분 정의되지 않은 동작을 갖습니다(무조건 문제가 생긴다는 뜻이 아닙니다). 위의 상황에서는 두 구조체의 내부 표현 방법에 따라 그 결과가 결정되겠죠.
그냥 포인터 끼리의 캐스팅이 궁금한 것이라면 표준문서 6.3 Conversions의 포인터 관련 부분을 보시면 됩니다. 표준문서를 구하기 어렵다면 구글에서 n869 c language pdf 치시면 C99의 초안문서인 n869를 구할 수 있으니 아쉬운 대로 그거라도 참고하시면 되겠습니다.
Re: 구조체 포인터의 casting
우선 short(2 byte), struct in_addr(4 byte)를 가정하고 위 자료구조를 살펴보면, 2/2/4/8 순서로 놓여 있습니다. 전체 struct의 시작 주소가 4의 배수 주소에 놓인다면 sin_family와 sin_port는 2의 배수 주소를 갖고, sin_addr는 4의 배수 주소를 갖게 됨을 확인할 수 있습니다. 정렬에 신경을 써서 만든 구조죠.
만약 short가 2byte가 아닌 구현이 있다면 위에서 short 자리에는 int16_t 쯤이 대신 들어가도록 만들었겠죠. 어쨌든 IP 헤더에 정렬 문제가 발생하면 C 컴파일러나 OS 개발자가 알아서 고치겠지만, 각자가 만든 구조체의 정렬/엔디안 문제가 발생하면 각자 알아서 :evil: 해결해야 합니다. 피하던지, 부딛혀서 버그를 만들어내던지 :wink:
Re: 구조체 포인터의 casting
여름 학기가 정기 학기이고 이번주에 시험이 있어 홈페이지를 손보지
못하고 있습니다. 종종 들려 혼자 놀던 홈페이지가 없으니 저도
답답하군요.
우선, C89의 해석을 거침 없이 적용하면...
알고 계신대로 서로 다른 두 구조체형의 크기가 동일하다고 해서 두 구조체
포인터 사이의 변환이 정렬 제한 문제 없이 허락되는 것은 아닙니다.
따라서, 위 프로그램에서 (struct sockaddr *) &me 부분은 정렬 제한과
관련된 문제를 일으킬 수 있는 부분입니다.
하지만, opaque type 지원을 위해서 C89에서도 사실상 모든 구조체형의
포인터는 "동일한 정렬 제한", 동일한 표현을 가져야 합니다. 이러한
요구는 C89 당시에는 인식을 못하다가 C99에는 공식적으로 반영되었습니다.
따라서, 우선 위 프로그램에서 포인터 변환과 관련된 문제는 없다고 생각
하시면 됩니다.
하지만, 이미 다른 분들이 지적해 주셨듯이 두 구조체형이 호환되는
type이 아니기에 두 구조체의 layout 은 프로그래머가 의도한대로 되지
않을 수도 있습니다. 그리고 이미 추측하고 계신 것처럼 특정
implementation 이 지극히 일반적인 방법으로 구조체 멤버를 정렬해 준다는
가정을 하면서 작성된 구조체입니다 - 이 부분에 대한 설명은 이미 위에서
다른 분들이 잘 해주셨습니다.
이 경우에는 두 구조체가 common initial member 을 공유하고 있는 것도
아니기에 두 구조체를 공용체로 묶어도 layout 에 대한 보장을 받을 수는
없습니다. 일반적으로 opaque type 구현에서는 common initial member 를
공유하는 구조체들을 공용체로 묶어 해당 부분에 대해서 동일한 layout 을
보장받을 수 있습니다.
618쪽에서 언급했듯이 구조체 포인터 사이의 변환은 포인터에 대한 지극히
일반적인 내용이 적용되기에 특별히 따로 다룰 내용이 없습니다. 위에서
추측하신대로 다른 곳에 적용되는 포인터와 관련된 내용을 그대로
적용하시면 됩니다. 단, 모든 구조체형 포인터가 동일한 정렬 제한을
갖는다는 사실은 분명 구조체 포인터와 관련된 특수한 조건이기에
어딘가에서 ;-) 소개했다고 믿고 있습니다만, 엄밀하게는 C99와 관련된
내용이고 호환되지 않는 두 구조체 포인터 사이의 변환이 이식성을
위해서는 결코 바람직하지 않기에 해당 내용을 생략했는지는 확실치
않습니다 - 책 쓴지 오래 되니 이젠 무슨 내용을 썼는지도 가물가물합니다.
그럼...
--
Jun, Woong (woong at gmail.com)
http://www.woong.org
댓글 달기