int와 void* 사이에 타입 캐스팅시 데이터 무결성
글쓴이: Yi Soo An@Google / 작성시간: 목, 2019/06/06 - 11:18오후
static void * threadFunc(void *arg) { int s = (int) arg; return (void *) s; }
1) 위 코드가 플랫폼/아키텍처에 상관없이 항상 그 데이터에 오류가 없음을 보장하는지 궁금합니다.
2) GLib 기반의 프로그램이나 TLPI 책에서 많이 사용되는데 저런 코드를 작성하면 어떤 사이드 이펙트를 고려해서 사용해야 할까요?
1)은 ILP32, LP64, LLP64 같은 데이터 모델을 고려해야 할 것 같은데 LP64 모델에서는 위 코드의 데이터가 깨질거 같아서요. 그 외에 또 있는지 궁금합니다.
감사합니다.
Forums:
가뭄에 단비같은 재미있는 질문
1. 0의 값을 갖는 정수 상수 표현식은 null pointer constant이며, void *를 포함한 어떤 포인터 타입으로든 변환이 가능합니다. 물론 변환 결과는 언제나 null pointer이죠.
2. 위에 명시한 경우를 제외하고도 정수는 어떤 포인터 타입으로든 변환이 가능합니다만, 그 경우 결과는 구현에 따라 달라집니다(implementation-defined). 정렬(align) 조건을 만족하지 않을 수도 있고, referenced type의 entity를 가리키지 않을 수도 있으며, trap representation일 수도 있습니다.
3. 반대로, 어떤 포인터든 정수 타입으로 변환될 수 있습니다. 그 결과도 implementation-defined이며, 특히 결과가 변환 대상 정수 타입의 표현 범위 밖일 경우에는 동작이 정의되지 않습니다(undefined behavior). 참고로 포인터를 정수로 변환한 결과가 어떤 정수형으로도 표현 불가능할 수도 있습니다.
4. 위 항목에 대한 표준 서술의 주석에는 pointer-to-integer 및 integer-to-pointer 대응 함수는 해당 실행 환경의 주소 구조(addressing structure)와 일관성이 있어야 한다고 하고 있습니다.
표준상으로는 implementation-defined이라고 되어 있긴 하지만 대부분의 구현체들이 아마 void *를 정수형으로 변환했다가 다시 void * 형으로 변환하는 정도는 적절히 지원해 줄 거라고 생각은 됩니다.
다만 int의 표현 범위가 void *를 항상 나타낼 수 없다는 보장은 없는데, 이건 문제가 될 수 있지요.
꼭 포인터를 정수형으로 나타내야 한다면, 저라면 차라리 size_t를 쓸 겁니다. 이거라고 문제가 없을 거라는 보장은 여전히 없지만 int보다는 확실히 나을 테니까요.
자세한 설명 감사드립니다. 특히 3번 내용이 도움
자세한 설명 감사드립니다. 특히 3번 내용이 도움 많이 되었습니다!
---------------
Happy Hacking!
return (void *) strlen(str);
size_t로 구현된 것도 봤습니다만 size_t의 경우 결과가 implementation-defined인데 이 말은 LP64 모델에서도 unsigned int(32 bits)의 크기를 가질 가능성이 있다는 말로 보면 될까요? 아니면 size_t는 항상 데이터 모델에 따른 unsigned long int크기를 보장하는건가요?
속 편하게 uintptr_t, intptr_t를 쓰는것이 답 같기는 하지만 표준에서 size_t에 대해 서술한게 좀 헷갈리네요..
---------------
Happy Hacking!
제 지식이 부족하여 위의 코드가 정상 작동하는지는 잘
제 지식이 부족하여 위의 코드가 정상 작동하는지는 잘 모르겠습니다.
Glib 에 타입 변환에 관한 매크로가 있습니다. 참고하시기 바랍니다.
https://developer.gnome.org/glib/stable/glib-Type-Conversion-Macros.html
링크 감사합니다. 위 매크로는 Nautilus 분석할
링크 감사합니다. 위 매크로는 Nautilus 분석할 때 봤던 코드인데 거기선 그냥 정수 0과 1만 사용하여 북마크 LOAD, SAVE를 위한 job queue(gqueue이용)를 구현한 코드였습니다. 데이터 손실 외에도 어떤걸 고려해야 되고 기타 사이드 이펙트가 있는지 궁금했었습니다.
---------------
Happy Hacking!
...
이런 때를 대비해서 요즘 C(++)에서는 "포인터를 담을 수 있는 정수형"을 무려 세 가지(!!)나 만들어 놨습니다.
https://en.cppreference.com/w/c/types/integer
https://en.cppreference.com/w/c/types/ptrdiff_t
포인터 값을 signed integer에 넣으려면: intptr_t
포인터 값을 unsigned integer에 넣으려면: uintptr_t
포인터와 포인터의 차이를 정수에 넣으려면: ptrdiff_t
...를 쓰면 된다고는 하는데, 사실 저도 C 전문가가 아니라서 왜 이짓을 하려고 서로 다른 타입을 세 개나 만든 것인지는 잘 모르겠습니다.
그냥 셋 중에 하나 맘에 드는 걸 골라서 일관적으로 쓰시는 걸 추천합니다.
오 그렇네요. 배워갑니다.
유효한 void * 포인터를 intptr_t 및 uintptr_t로 변환했다가 다시 void *로 변환했을 때 원래 포인터와 동일하다는 것이 보장되는군요.
근데 그 밑에 "These types are optional"이라고 되어 있군요... -_-;
intptr_t 괜찮은것 같네요. 워드 사이즈에 따라
intptr_t 괜찮은것 같네요. 워드 사이즈에 따라 long int나 int로 typedef 되네요. 다만 포인터에 정수값을 넣고 거기서 가져온 값에 arithmetic 연산을 해야겠다면 이 타입을 계속쓰면서 주석을 달아놔야 헷갈리지 않을거 같네요. 처음에 타입명이 intptr이길래 그냥 int* 아닌가 싶었습니다..ㅎ
---------------
Happy Hacking!
댓글 달기