C 표준을 준수하는 정수-포인터 변환에 대해

체스맨의 이미지

기술적인 문제와는 별개로, 표준을 해석하는 것 역시 참 까다로운 일 같습니다.

C99 에 따르면,

An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation.
 
Any pointer type may be converted to an integer type.  Except as previously specified, the result is implementation-defined.  If the result cannot be represented in the integer type, the behavior is undefined.  The result need not be in the range of values of any integer type.

결국, 다음과 같은 코드는 상황에 따라 결과를 보장할 수 없다는 얘기일 것이구요.

int i = 10;
void* p = (void*)i;
assert( i==(int)p );

그렇다면, 다음은 어떨까요. 다음과 같이 변환을 구현해서 쓰고 있는데, 표준 위배에 해당할 부분이 없다고 생각은 되는데 확인을 받고 싶습니다.

int i = 10;
void* p = (char*)0 + i;
assert( i== (int)( (char*)p - (char*)0 ) );

kkb110의 이미지

무슨책이였더라... 이런 말이 생각나네요 .정의되지 않음은 쓰이지 않음을 의미한다.

천재정치 (e-book) - Claude Vorilhon
참고자료 : 미국인들 IQ 낮을수록 부시 찍었다

체스맨의 이미지

그 의미를 그렇게 받아들이는 것도 크게 무리는 아니라고 생각합니다만, 제가 올린 글의 핵심은 다음 코드가 표준에 위배됨이 없는가에 관한 것입니다.

int i = 10;
void* p = (char*)0 + i;
assert( i== (int)( (char*)p - (char*)0 ) );

이 코드 안에는 정수-포인터 캐스팅은 전혀 없고, 단지 포인터 연산만 있을 뿐입니다.

Orion Project : http://orionids.org

doldori의 이미지

두번째 코드도 해결책은 되지 못할 듯 합니다.
이 코드의 문제점은 널 포인터의 주소는 0번지라고 가정하는 것입니다.
그러나 포인터의 값이 0이라는 것은 아무것도 가리키고 있지 않음을 나타낼 뿐
0번지를 뜻하는 것은 아니라고 알고 있습니다. 즉 (char*)0 + 10이
반드시 주소 10을 가리킨다는 보장이 없는 것이죠.

그러한 문제 이외에도 (char*)p - (char*)0 와 같은 수식의 결과도 정의되지 않습니다.
포인터의 - 연산은 두 피연산자가 같은 배열의 원소 또는 마지막 원소 바로 다음을
가리킬 때만 유효합니다. 포인터와 정수의 + 연산 역시 마찬가지고요.

구현체에 의해 보장받는 정도로 보자면 차라리 처음 코드가 낫다고 봅니다.
첫번째 코드는 implementation-defined라도 되지만
두번째는 그나마도 없기 때문입니다.

체스맨의 이미지

첫번째 말씀하신 것과 같은 경우는 실제로 대개의 offsetof 매크로가 이용하고 있는 방법이기도 합니다만, 물론 이게 호환성을 항상 보장할 수 있는 방법은 아닌 걸로 알고 있습니다. 아무튼 결과값이 반드시 10번지가 돼야할 필요는 없고, 다시 ptr->int 할 때 10으로만 되돌아와주면 되는 일이긴 하지요...

두번째 말씀하신 것도 일리 있는 얘기 같습니다. 캐스팅이 더 낫다고 해서, implementation-defined 를 덥썩 쓰기도 그렇구요. 표준 준수를 고민하는 의미가 사라지잖아요...

여러 플렛폼에 대해 호환성을 고려해서 인터페이스 프로토타입을 결정하는데, 리턴 값을 void* 로 잡아놓고, 특정 플렛폼에서는 정수 핸들을 리턴해야 할 일이 생기는 경우가 있습니다.

그렇다고 sizeof(int) 할당해서 정수 저장하고 리턴하는 건 별로 마음에 들지가 않구요...

표준을 준수할 생각이라면 할당 말고는 방법이 없을까요? 이 부분에 대한 고민의 종지부를 찍고 싶군요.

Orion Project : http://orionids.org

라키시스의 이미지

한가지만,.......

C99 draft 발췌입니다

Quote:
An integer constant expression with the value 0, or such an expression cast to type
void *, is called a null pointer constant.55) If a null pointer constant is converted to a
pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal
to a pointer to any object or function

제가 이해하기로는 (char *)0 은, 위 인용의 내용에 따라, 0 번지를 가리키는 char * 형 포인터가 아니라
char * 형 NULL 포인터로 해석해야 하는 것 같습니다만...;;

대부분의 플랫폼에서 offsetof 가

#define offsetof(type, field) ((size_t)(&((type *)0)->field))

와 같이 정의되어 있습니다만,... 아래 글에서도 언급하셨군요 ^^;

그러나, 포인터 끼리의 마이너스/플러스 연산에 관해서는 제가 잘 몰라서요......;; _(__)_

짧은 제 생각에는, generic pointer와 정수형 사이의 변환은 두 타입의 크기가 차이가 없는 한
특별한 플랫폼이 아닌 이상(예를 들어 포인터의 타입에 따라서 크기가 다르다든지 : sizeof(char *) != sizeof(int *) 같은 경우) 일반적으로 통용되는 표현이며, 따라서, 실제 practice에서는 작성하는 프로그램이 타겟으로 삼는 환경이 특수하지 않다면, 이렇게까지 고민을 하지 않아도 될 것 같습니다.

체스맨의 이미지

지금 문제에서는 포인터 연산이 중요한 건 아닙니다. 표준에 근거해서 다음 코드에서 p 와 i 값이 변함 없다면, 이 문제에 대한 고민은 더 이상 없습니다.

p = (void*)(intptr_t)p;
i = (intptr_t)(void*)i;

포인터 연산을 생각해본 건, 발제 글에서 인용한 표준 문서 부분이 포인터-정수 변환을 implementation defined 로 규정했기 때문이었습니다. 아무튼 저기 아래 다시 언급한 표준 문서의 intptr_t 를 규정하는 부분에 의하면 위 코드 같은 경우는 표준에서 보장되는 게 아닌가 판단을 하고 있습니다...

아무튼 이 가려운 부분을 단정적으로 확 긁어주실 분이 있으면 좋겠습니다.

'실제 구현에서 대부분 문제를 일으키지 않는다'로 만족할 거였으면 이런 글을 올리지도 않았을 겁니다. 구현하다보면 이런 캐스팅이 필요해지는 경우들이 있는데( 이 스레드에서도 간단히 언급했습니다만...) 그 때마다 꺼려지는 생각이 자꾸 들어서 확실히 결론짓고 싶은 생각이 들었기 때문입니다.

Orion Project : http://orionids.org

체스맨의 이미지

제가 제시한 코드가 표준 준수에 문제가 있다면, 표준을 준수하면서, 정수를 포인터 변수에 담아뒀다가, 다시 해당 정수를 얻을 수 있는 방법은 없는 것인지 알고 싶습니다.

Orion Project : http://orionids.org

krisna의 이미지

이런 건 어떤가요?

int i = 10;
void* p = NULL;
int n;
 
memcpy(&p, &i, sizeof(i));
memcpy(&n, &p, sizeof(n));
체스맨의 이미지

그 방법도 생각을 안해본 건 아닙니다...
아마 쓰기가 꺼려지는 이유도 알고 계실 거라 생각됩니다. ^^

Orion Project : http://orionids.org

krisna의 이미지

포인터 값을 인티저에 저장하는 것이 아닌, 인티저를 포인터에 저장하는 경우라서 큰 무리가 없지 않을까 생각합니다.

어떤 이유에서 쓰기가 꺼려지시는 지 설명을 좀 부탁드립니다.

제 경우는 일종의 Generic Value 타입같은 것을 아래와 같이 구현해서 사용하는 부분도 있습니다.

char* data;
size_t datalen;
void* p;
 
if (datalen > sizeof(p)) {
	p = malloc(datalen);
	memcpy(p, data, datalen);
} else {
	memcpy(&p, data, datalen);
}

위 코드에서는 어떤 문제가 있을까요?

체스맨의 이미지

물론 지금 제시하신 코드처럼 raw 데이터에 대해 그렇게 하는 것은 크게 무리가 없다고 생각하구요.

표준을 준수하진 못하더라도, 직접 캐스팅하거나, 위에 제가 제시한, 포인터 옵셋을 이용하는 방법 ( 대개는 컴파일러가 최적화해서, 덧뺄셈 연산은 사라질 겁니다. )을 쓰면 대부분의 경우는 호환성이 유지된다고 했을 때, memcpy 는 아무래도 성능상 좀 떨어진다고 봐야돼서 그렇습니다. ( 차이가 크고 적고를 떠나서요... )

예를 들어, 파일 핸들을 포인터로 변경하여 리턴한 경우라면, 파일로부터 읽을 때마다

int fd;
memcpy( fd, &p, sizeof(int) );
read( fd, buf, n );

이런 식이 될텐데, 썩 마음에 들지가 않습니다.

Orion Project : http://orionids.org

체스맨의 이미지

C99 표준의 다음 부분에 대해서는 어떨까요? Draft 문서에서 발췌했지만, 표준 문서에도 비슷한 내용이 있는 걸로 압니다.

7.18.1.4 Integer types capable of holding object pointers
 
The following type designates a signed integer type with the property that <span>any valid pointer to void can be converted to this type, then converted back to pointer to void, and the result will compare equal to the original pointer</span>:
 
intptr_t
 
The following type designates an unsigned integer type with the property that <span>any valid pointer to void can be converted to this type, then converted back to pointer to void, and the result will compare equal to the original pointer</span>:
 
uintptr_t

여기서 말하는 "converted to", "converted back to" 라는 건 casting 에 의해 변환되는 것을 말하는 걸까요? 그렇다면, 제가 맨 처음 글에 올린 다음 부분과 위배되는 것은 아닐까요?

Any pointer type may be converted to an integer type.  Except as previously specified, <span>the result is implementation-defined.</span>

아니면, converted to 에 대해서는 implementation-defined 이지만, converted back to, 즉 원래대로 변환되어 되돌아오는 경우는 문제가 없는 것일까요?

Orion Project : http://orionids.org

sjpark의 이미지

저는 왜 이문제가 고민의 대상이 되는지 이해가 되질 않아요 (공력부족..)

32 비트 머신에서 32 비트 컴파일러로 주소가 32 비트로 표현되고 int 도 32 비트 값을 가지고 있는 경우라면,
제시한 모든 경우에 문제 없이 동작하는 것이 아닌가 싶습니다.

반례로

int 는 16 비트크기이고 주소는 32 비트로 표현되는 상황이라면, 문제가 될 수 있을 것입니다.
16 비트의 int 를 32 bit 크기로 확장해야 하니까요,.
제가 잘 모르는 것이 int 의 크기가 32 bit 머신에서 32 bit 로 "정의 되는가" 인데, 어디까지나 제가 제시한 반례는 "정의 되지 않는다" 를 가정하였습니다.

표준 문서에서 설명하는 것이 이러한 상황에서 차이를(컴파일러의 구현 및 머신? 에 의존) intptr_t 와 uintptr_t 로 해결할 수 있도록 정의하는 것이 아닌가 싶습니다.

http://nicesj.com

체스맨의 이미지

표준 문서에서 설명하는 것이 이러한 상황에서 차이를(컴파일러의 구현 및 머신? 에 의존) intptr_t 와 uintptr_t 로 해결할 수 있도록 정의하는 것이 아닌가 싶습니다.

예 저도 이렇게 말씀하신 것처럼 명쾌하게 끝나면 되는 문제면 좋겠습니다.

어떤 경우 표준 문서를 보고 그런가보다 생각해서 구현해놓으면, 결과적으론 제가 잘 못 해석한 것이었지만 어쨌든간에 아주 피곤하게 됐던 일 (전부 수정 -_-; ) 있어서 조심스러운 감이 있습니다.

위에서 언급한 다른 표준 문서 부분을 보시면 표준 문서 안에도 해석이 모호하게 되는 경우들이 있어서, 이런 부분을 같이 보고 표준 문서의 내용들을 어떻게 받아들이면 되는지 생각해보고 싶었습니다.

Orion Project : http://orionids.org

appler의 이미지

----------------------------
궁금증으로 가득찬 20후반 -_-;;
탄생은 죽음의 시작에 불과하다.

블로그
http://azdream.egloos.com
http://koreaappler.blogspot.com

검색엔진
http://applersearchengine.topicle.com/


laziness, impatience, hubris

不恥下問 - 진정으로 대화를 원하면 겸손하게 모르는 것은 모른다고 말하는 용기가 필요하다.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.