การแปลงตัวชี้เป็นจำนวนเต็ม


88

ฉันกำลังพยายามปรับรหัสที่มีอยู่ให้เป็นเครื่อง 64 บิต ปัญหาหลักคือในฟังก์ชันหนึ่ง coder ก่อนหน้านี้ใช้อาร์กิวเมนต์ 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ไฟล์จากที่นี่หรือใช้แบบพกพาจากที่นี่


ดูstackoverflow.com/questions/126279/…สำหรับข้อมูลเกี่ยวกับวิธีรับ stdint.h ที่ทำงานร่วมกับ MSVC (และอาจเป็น Borland)
Michael Burr

2
ลิงค์เสียทั้งคู่!
อันโตนิโอ

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ประเภท (ดูเพิ่มเติมในประเภทจำนวนเต็ม cppreference สำหรับ C99 )

ประเภทเหล่านี้กำหนดไว้<stdint.h>สำหรับ C99 และในเนมสเปซstdสำหรับ C ++ 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 cast ใน 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

ยอดเยี่ยม ... นักแสดงคือสิ่งที่ฉันกำลังมองหา ถ้าเราบอกให้ใช้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' คุณควรจะใช้ 'size_t' ได้ซึ่งในระบบ 64 บิตควรเป็นประเภท 64 บิต

การสนทนาที่ไม่ได้ลงนาม int vs size_t นี้จะกล่าวถึงรายละเอียดเพิ่มเติมอีกเล็กน้อย


34
แม้ว่า size_t มักจะใหญ่พอที่จะถือตัวชี้ได้ แต่ก็ไม่จำเป็นต้องเป็นเช่นนั้น มันจะดีกว่าถ้าค้นหาส่วนหัว stdint.h (ถ้าคอมไพเลอร์ของคุณยังไม่มี) และใช้ uintptr_t
Michael Burr

3
แต่น่าเสียดายที่ข้อ จำกัด เฉพาะในก็คือว่ามันจะต้องมีผลมาจากการใด ๆsize_t sizeof()สิ่งนี้ไม่จำเป็นต้องทำให้เป็น 64 บิตบน x64 ดูเพิ่มเติม
Antoine

3
size_t สามารถจัดเก็บค่าของตัวชี้ที่ไม่ใช่สมาชิกได้อย่างปลอดภัย ดูen.cppreference.com/w/cpp/types/size_t
AndyJost

2
@AndyJost ไม่มันไม่ได้ แม้แต่ลิงค์ของคุณเองก็ยืนยันเช่นนั้น

1
@YoYoYonnY: "ในหลาย ๆ แพลตฟอร์ม (ข้อยกเว้นคือระบบที่แบ่งแอดเดรสแบบแบ่งส่วน) std :: size_t สามารถจัดเก็บค่าของตัวชี้ที่ไม่ใช่สมาชิกได้อย่างปลอดภัยซึ่งในกรณีนี้จะมีความหมายเหมือนกันกับ std :: uintptr_t" - คุณกำลังพูดถึงอะไร?
slashmais


8

คำตอบหลายคำชี้ให้เห็นuintptr_tและ#include <stdint.h>เป็น 'ทางออก' นั่นคือฉันขอแนะนำเป็นส่วนหนึ่งของคำตอบ แต่ไม่ใช่คำตอบทั้งหมด คุณต้องดูด้วยว่าฟังก์ชันถูกเรียกใช้ด้วยรหัสข้อความของ FOO ที่ใด

พิจารณารหัสนี้และการรวบรวม:

$ 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
$

คุณจะสังเกตเห็นว่ามีปัญหาในตำแหน่งที่เรียก (ใน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

@PierreBdR อย่างไรก็ตามเขาทำให้ถูกต้องมาก การดูโค้ดนั้นไม่ง่ายเสมอไป (รวมถึงเมื่อคอมไพเลอร์เตือนเกี่ยวกับเรื่องนี้) ที่ใช้ int ที่มีการเซ็นชื่อ แต่ใช้สำหรับขนาดและคิดว่าสามารถเปลี่ยนเป็นไม่ได้ลงนามได้ น่าเสียดายที่มันไม่ง่ายเสมอไป คุณต้องดูแต่ละกรณีอย่างชัดเจนเว้นแต่คุณต้องการทำให้เกิดข้อบกพร่องที่อาจเกิดขึ้น - และจุดบกพร่องที่ละเอียดอ่อนนั้น
Pryftan


4

ฉันเจอคำถามนี้ขณะศึกษาซอร์สโค้ดของSQLiteข้อมูล 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 * ในกรณีนี้เป็นแฮนเดิลทั่วไป ไม่ใช่ตัวชี้ไปที่ค่า แต่เป็นค่านั้นเอง (นี่เป็นเพียงการใช้ void * โดยโปรแกรมเมอร์ C และ C ++)

ถ้ามันถือค่าจำนวนเต็มก็ควรอยู่ในช่วงจำนวนเต็ม!

นี่คือการแสดงผลเป็นจำนวนเต็มอย่างง่ายดาย:

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

ควรให้คำเตือนเท่านั้น


0

เนื่องจากuintptr_tจะไม่ได้รับประกันว่าจะมีใน C ++ / C ++ 11ถ้าเป็นแปลงวิธีหนึ่งที่คุณสามารถพิจารณากำหนดเสมอใน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.