int와 void* 사이에 타입 캐스팅시 데이터 무결성

Yi Soo An@Google의 이미지

static void *
threadFunc(void *arg)
{
    int s = (int) arg;
 
    return (void *) s;
}

1) 위 코드가 플랫폼/아키텍처에 상관없이 항상 그 데이터에 오류가 없음을 보장하는지 궁금합니다.
2) GLib 기반의 프로그램이나 TLPI 책에서 많이 사용되는데 저런 코드를 작성하면 어떤 사이드 이펙트를 고려해서 사용해야 할까요?

1)은 ILP32, LP64, LLP64 같은 데이터 모델을 고려해야 할 것 같은데 LP64 모델에서는 위 코드의 데이터가 깨질거 같아서요. 그 외에 또 있는지 궁금합니다.

감사합니다.

익명 사용자의 이미지

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보다는 확실히 나을 테니까요.

Yi Soo An@Google의 이미지

자세한 설명 감사드립니다. 특히 3번 내용이 도움 많이 되었습니다!

---------------
Happy Hacking!

Yi Soo An@Google의 이미지

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!

Hodong Kim@Google의 이미지

제 지식이 부족하여 위의 코드가 정상 작동하는지는 잘 모르겠습니다.

Glib 에 타입 변환에 관한 매크로가 있습니다. 참고하시기 바랍니다.
https://developer.gnome.org/glib/stable/glib-Type-Conversion-Macros.html

Description
 
Many times GLib, GTK+, and other libraries allow you to pass "user data" to a callback, in the form of a void pointer. From time to time you want to pass an integer instead of a pointer. You could allocate an integer, with something like:
 
int *ip = g_new (int, 1);
*ip = 42;
 
But this is inconvenient, and it's annoying to have to free the memory at some later time.
 
Pointers are always at least 32 bits in size (on all platforms GLib intends to support). Thus you can store at least 32-bit integer values in a pointer value. Naively, you might try this, but it's incorrect:
 
gpointer p;
int i;
p = (void*) 42;
i = (int) p;
 
Again, that example was not correct, don't copy it. The problem is that on some systems you need to do this:
 
gpointer p;
int i;
p = (void*) (long) 42;
i = (int) (long) p;
 
The GLib macros GPOINTER_TO_INT(), GINT_TO_POINTER(), etc. take care to do the right thing on every platform.
 
Warning: You may not store pointers in integers. This is not portable in any way, shape or form. These macros only allow storing integers in pointers, and only preserve 32 bits of the integer; values outside the range of a 32-bit integer will be mangled.
Yi Soo An@Google의 이미지

링크 감사합니다. 위 매크로는 Nautilus 분석할 때 봤던 코드인데 거기선 그냥 정수 0과 1만 사용하여 북마크 LOAD, SAVE를 위한 job queue(gqueue이용)를 구현한 코드였습니다. 데이터 손실 외에도 어떤걸 고려해야 되고 기타 사이드 이펙트가 있는지 궁금했었습니다.

---------------
Happy Hacking!

jick의 이미지

이런 때를 대비해서 요즘 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"이라고 되어 있군요... -_-;

Yi Soo An@Google의 이미지

intptr_t 괜찮은것 같네요. 워드 사이즈에 따라 long int나 int로 typedef 되네요. 다만 포인터에 정수값을 넣고 거기서 가져온 값에 arithmetic 연산을 해야겠다면 이 타입을 계속쓰면서 주석을 달아놔야 헷갈리지 않을거 같네요. 처음에 타입명이 intptr이길래 그냥 int* 아닌가 싶었습니다..ㅎ

---------------
Happy Hacking!

댓글 달기

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
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.