포인터를 정수로 변환


88

기존 코드를 64 비트 시스템에 적용하려고합니다. 주요 문제는 한 함수에서 이전 코더가 함수 자체에서 적합한 유형으로 변환되는 void * 인수를 사용한다는 것입니다. 간단한 예 :

void function(MESSAGE_ID id, void* param)
{
    if(id == FOO) {
        int real_param = (int)param;
        // ...
    }
}

물론 64 비트 컴퓨터에서는 오류가 발생합니다.

error: cast from 'void*' to 'int' loses precision

32 비트 컴퓨터에서 가능한 한 깔끔하게 작동하도록이 문제를 수정하고 싶습니다. 어떤 생각?


5
나는 이것이 오래된 게시물을 파고 있다는 것을 알고 있지만 수락 된 답변이 정확하지 않은 것 같습니다. size_t작동하지 않는 구체적인 예 는 i386 세그먼트 메모리입니다. 32 비트 컴퓨터했지만, sizeof반환 2을 위해 size_t. 아래 Alex 대답 이 올바른 것 같습니다 . Alex의 대답과 uintptr_t작업은 거의 모든 곳에서 현재 표준입니다. C ++ 11 처리를 제공하고 C ++ 03 헤더 가드도 제공합니다.
jww

답변:


70

사용 intptr_t하고 uintptr_t.

이식 가능한 방식으로 정의되도록하려면 다음과 같은 코드를 사용할 수 있습니다.

#if defined(__BORLANDC__)
    typedef unsigned char uint8_t;
    typedef __int64 int64_t;
    typedef unsigned long uintptr_t;
#elif defined(_MSC_VER)
    typedef unsigned char uint8_t;
    typedef __int64 int64_t;
#else
    #include <stdint.h>
#endif

.h 파일에 넣고 필요한 곳에 포함하면됩니다.

또는 여기stdint.h 에서 Microsoft 버전의 파일을 다운로드 하거나 여기 에서 휴대용 파일을 사용할 수 있습니다 .


MSVC (및 아마도 Borland)에서 작동하는 stdint.h를 얻는 방법에 대한 정보는 stackoverflow.com/questions/126279/… 를 참조하십시오 .
Michael Burr

2
두 링크가 끊어졌습니다!
Antonio

1
이 대답은 C와 관련이 있지만 언어는 C ++ 태그가 지정 되어 있으므로 내가 찾던 대답이 아닙니다.
HaseeB Mir

@HaSeeBMiR 적절한 수정로 전환하는 것입니다 <cstdint>, 또는 적합한를 다운로드 cstdint당신이를 다운로드하는 경우 stdint.h.
Justin Time-Monica 복원

1
@HaSeeBMiR 대답이 C ++ 대신 C와 관련된 유일한 이유는 동등한 C ++ 헤더 대신 C 헤더를 사용하기 때문입니다. C 전처리 기는 C ++ cstdint의 일부이며 여기에 정의 된 모든 유형 이름과 마찬가지로 C ++ 표준의 일부입니다. 실제로 지정된 태그에 적합합니다. ... 나는 유형을 수동으로 정의하는 데 동의하지 않지만 그렇게하지 않는 컴파일러로 작업 할 때 필요할 수 있습니다.
Justin Time-Monica 복원

94

이것이 현대적인 C ++ 방식이라고 말하고 싶습니다.

#include <cstdint>
void *p;
auto i = reinterpret_cast<std::uintptr_t>(p);

수정 :

정수에 대한 올바른 유형

따라서 포인터를 정수로 저장하는 올바른 방법은 uintptr_t또는 intptr_t유형 을 사용하는 것 입니다. ( C99에 대한 cppreference 정수 유형 참조 ).

이러한 유형은 <stdint.h>C99 및 stdC ++ 11 의 네임 스페이스 에 정의되어 있습니다 <cstdint>( C ++의 정수 유형 참조 ).

C ++ 11 (이상) 버전

#include <cstdint>
std::uintptr_t i;

C ++ 03 버전

extern "C" {
#include <stdint.h>
}

uintptr_t i;

C99 버전

#include <stdint.h>
uintptr_t i;

올바른 캐스팅 연산자

C에서는 캐스트가 하나 뿐이며 C ++에서 C 캐스트를 사용하는 것은 눈살을 찌푸립니다 (따라서 C ++에서는 사용하지 마십시오). C ++에는 다른 캐스트가 있습니다. reinterpret_cast이 변환에 대한 올바른 캐스트입니다 ( 여기 참조 ).

C ++ 11 버전

auto i = reinterpret_cast<std::uintptr_t>(p);

C ++ 03 버전

uintptr_t i = reinterpret_cast<uintptr_t>(p);

C 버전

uintptr_t i = (uintptr_t)p; // C Version

관련 질문


6
제대로 reinterpret_cast 언급 유일한 해답
plasmacel

<cstdint>를 포함하려는 경우 std :: uintptr_t를 대신 사용할 수도 있습니다.
linleno 2015

대단해 ... 캐스트는 내가 찾고 있던 것입니다. uintptr_t대신 사용하라는 메시지가 표시되면 size_t왜 필요 reinterpret_cast합니까? static_cast표준이 특별히 호환 가능한 데이터 유형을 제공하기 때문에 간단 해야하는 것 같습니다 .
jww

1
@jww 읽기 : en.cppreference.com/w/cpp/language/static_cast 여기서 내 이해 static_cast는 유형을 변환하거나 포인터 인 경우 유형이 필요하면 포인터 조정을 수행 할 수 있다는 것입니다. reinterpret_cast실제로 기본 메모리 패턴의 유형을 변경하는 것입니다 (변이 없음). 명확히하기 위해 : static_cast여기서 동일하게 작동합니다.
Alexander Oh

2
C 및 C ++ 에서 캐스팅하는 방법에 대한 모든 세부 정보를 제공하므로 대신 선택된 답변으로 표시되어야합니다 .
HaseeB Mir

43

아키텍처와 일치하려면 'size_t'및 'ptrdiff_t'가 필요합니다. 따라서 'int'를 사용하는 것보다 64 비트 시스템에서 64 비트 유형이어야하는 'size_t'를 사용할 수 있어야한다고 생각합니다.

이 토론 unsigned int 대 size_t 는 좀 더 자세히 설명합니다.


34
size_t는 일반적으로 포인터를 보유하기에 충분히 크지 만 반드시 그런 것은 아닙니다. stdint.h 헤더를 찾고 (컴파일러에 아직없는 경우) uintptr_t를 사용하는 것이 좋습니다.
Michael Burr

3
불행히도 유일한 제약 size_t은 모든 sizeof(). x64에서 반드시 64 비트가되는 것은 아닙니다. 참조
Antoine

3
size_t 비 멤버 포인터의 값을 안전하게 저장할 수 있습니다 . en.cppreference.com/w/cpp/types/size_t를 참조하십시오 .
AndyJost 2016

2
@AndyJost 아니요. 자신의 링크조차도 그것을 확인합니다.
yyny

1
@YoYoYonnY : "많은 플랫폼에서 (예외는 세그먼트 주소 지정 시스템) std :: size_t는 멤버가 아닌 포인터의 값을 안전하게 저장할 수 있습니다.이 경우 std :: uintptr_t와 동의어입니다." -무슨 소리 야?
slashmais


8

여러 답변에서 지적 uintptr_t하고#include <stdint.h> '는'솔루션으로. 즉, 답변의 일부이지만 전체 답변은 아닙니다. 또한 FOO의 메시지 ID로 함수가 호출되는 위치를 확인해야합니다.

이 코드와 컴파일을 고려하십시오.

$ cat kk.c
#include <stdio.h>
static void function(int n, void *p)
{
    unsigned long z = *(unsigned long *)p;
    printf("%d - %lu\n", n, z);
}

int main(void)
{
    function(1, 2);
    return(0);
}
$ rmk kk
        gcc -m64 -g -O -std=c99 -pedantic -Wall -Wshadow -Wpointer-arith \
            -Wcast-qual -Wstrict-prototypes -Wmissing-prototypes \
            -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE kk.c -o kk 
kk.c: In function 'main':
kk.c:10: warning: passing argument 2 of 'func' makes pointer from integer without a cast
$

호출 위치 (in main())에 문제가 있음을 알 수 있습니다. 캐스트없이 정수를 포인터로 변환합니다. function()값이 전달되는 방식을 확인하려면 모든 용도를 분석해야 합니다. function()호출이 작성되면 내 코드 가 작동합니다.

unsigned long i = 0x2341;
function(1, &i);

당신의 것들은 아마도 다르게 쓰여질 것이므로, 표시된대로 값을 사용하는 것이 합리적인지 확인하기 위해 함수가 호출되는 지점을 검토해야합니다. 잊지 마세요. 잠재적 인 버그를 발견 할 수 있습니다.

또한이 값의 형식을하려는 경우, void *매개 변수 (변환 등)에주의 깊게 살펴 <inttypes.h>헤더 (대신 stdint.h- inttypes.h의 서비스를 제공하는 stdint.h특별한이다,하지만 C99 표준은 말한다 그는 헤더는 [t] <inttypes.h>헤더를 포함 <stdint.h>하고 호스팅 된 구현에서 제공하는 추가 기능으로 확장하고 형식 문자열에 PRIxxx 매크로를 사용합니다.

또한 내 의견은 C ++가 아닌 C에 엄격하게 적용되지만 코드는 C와 C ++간에 이식 가능한 C ++의 하위 집합에 있습니다. 내 의견이 적용될 가능성은 공평합니다.


3
내 질문의 요점을 놓친 것 같습니다. 코드는 포인터에 정수 값을 저장합니다. 상기 코드의 일부 (기록 된 정수 값을 추출하는 예 대향하고있는 바와 같이 포인터).
PierreBdR 2014 년

@PierreBdR 그럼에도 불구하고 그는 매우 유효한 지적을합니다. 부호있는 int를 사용하지만 크기에 사용되는 코드 (컴파일러가 경고 할 때 포함)를 보는 것이 항상 그렇게 간단하지는 않으며 unsigned로 변경해도 괜찮다고 생각합니다. 불행히도 항상 그렇게 간단하지는 않습니다. 잠재적 인 버그와 미묘한 버그를 일으키고 싶지 않다면 각 사례를 명시 적으로 살펴 봐야합니다.
Pryftan

4
  1. #include <stdint.h>
  2. uintptr_t포함 된 표준 헤더 파일에 정의 된 표준 유형을 사용하십시오 .

4

SQLite 의 소스 코드를 연구하는 동안이 질문을 보았습니다 .

에서 sqliteInt.h , 정수와 포인터 사이 매크로 변환을 정의 된 코드의 단락이있다. 저자는 먼저 컴파일러에 의존적 인 문제가되어야한다고 지적한 다음 대부분의 인기있는 컴파일러를 설명하는 솔루션을 구현했습니다.

#if defined(__PTRDIFF_TYPE__)  /* This case should work for GCC */
# define SQLITE_INT_TO_PTR(X)  ((void*)(__PTRDIFF_TYPE__)(X))
# define SQLITE_PTR_TO_INT(X)  ((int)(__PTRDIFF_TYPE__)(X))
#elif !defined(__GNUC__)       /* Works for compilers other than LLVM */
# define SQLITE_INT_TO_PTR(X)  ((void*)&((char*)0)[X])
# define SQLITE_PTR_TO_INT(X)  ((int)(((char*)X)-(char*)0))
#elif defined(HAVE_STDINT_H)   /* Use this case if we have ANSI headers */
# define SQLITE_INT_TO_PTR(X)  ((void*)(intptr_t)(X))
# define SQLITE_PTR_TO_INT(X)  ((int)(intptr_t)(X))
#else                          /* Generates a warning - but it always works     */
# define SQLITE_INT_TO_PTR(X)  ((void*)(X))
# define SQLITE_PTR_TO_INT(X)  ((int)(X))
#endif

자세한 내용은 다음과 같은 의견을 인용합니다.

/*
** The following macros are used to cast pointers to integers and
** integers to pointers.  The way you do this varies from one compiler
** to the next, so we have developed the following set of #if statements
** to generate appropriate macros for a wide range of compilers.
**
** The correct "ANSI" way to do this is to use the intptr_t type.
** Unfortunately, that typedef is not available on all compilers, or
** if it is available, it requires an #include of specific headers
** that vary from one machine to the next.
**
** Ticket #3860:  The llvm-gcc-4.2 compiler from Apple chokes on
** the ((void*)&((char*)0)[X]) construct.  But MSVC chokes on ((void*)(X)).
** So we have to define the macros in different ways depending on the
** compiler.
*/

크레딧은 커미터에게갑니다.


2

가장 좋은 방법은 포인터 유형에서 비 포인터 유형으로 변환하지 않는 것입니다. 그러나 이것은 귀하의 경우에는 분명히 불가능합니다.

모두가 말했듯이 uintptr_t는 사용해야합니다.

링크 에는 64 비트 코드로의 변환에 대한 좋은 정보가 있습니다.

comp.std.c 에서도 이에 대한 좋은 토론이 있습니다.


2

이 경우 void *의 "의미"는 일반적인 핸들이라고 생각합니다. 값에 대한 포인터가 아니라 값 자체입니다. (이것은 C 및 C ++ 프로그래머가 void *를 사용하는 방식입니다.)

정수 값을 보유하고 있다면 정수 범위 내에있는 것이 좋습니다!

다음은 정수로의 쉬운 렌더링입니다.

int x = (char*)p - (char*)0;

경고 만 제공해야합니다.


0

이후이 uintptr_t됩니다 ++ / C ++ (11) C에이 보장되지 이것이 당신이 고려할 수있는 하나 개의 방법 변환의 경우, uintmax_t항상 정의가 <cstdint>.

auto real_param = reinterpret_cast<uintmax_t>(param);

안전하게 플레이하려면 코드의 아무 곳에 나 어설 션을 추가 할 수 있습니다.

static_assert(sizeof (uintmax_t) >= sizeof (void *) ,
              "No suitable integer type for conversion from pointer type");

uintptr_t가 없으면 uintmax_t도 답이 아닙니다. 포인터 값을 저장할 수 있다는 보장은 없습니다! 이를 수행하는 정수 유형이 없을 수도 있습니다.
PierreBdR
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.