Chuyển đổi một con trỏ thành một số nguyên


85

Tôi đang cố gắng điều chỉnh mã hiện có thành máy 64 bit. Vấn đề chính là trong một hàm, người viết mã trước đó sử dụng đối số void * được chuyển đổi thành kiểu phù hợp trong chính hàm. Một ví dụ ngắn gọn:

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

Tất nhiên, trên máy 64 bit, tôi gặp lỗi:

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

Tôi muốn sửa lỗi này để nó vẫn hoạt động trên máy 32 bit và sạch nhất có thể. Bất kỳ ý tưởng ?


5
Tôi biết điều này đang đào lên một bài viết cũ, nhưng có vẻ như câu trả lời được chấp nhận không hoàn toàn chính xác. Một ví dụ cụ thể của size_tviệc không hoạt động là bộ nhớ phân đoạn i386. Mặc dù là máy 32 bit, sizeoftrả về 2cho size_t. Alex câu trả lời dưới đây có vẻ đúng. Câu trả lời của Alex và uintptr_thoạt động ở mọi nơi và tiêu chuẩn bây giờ của nó. Nó cung cấp một cách xử lý C ++ 11 và thậm chí nó còn cung cấp cho các bộ bảo vệ tiêu đề C ++ 03.
jww 27/09/16

Câu trả lời:


68

Sử dụng intptr_tuintptr_t.

Để đảm bảo nó được xác định theo cách di động, bạn có thể sử dụng mã như sau:

#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

Chỉ cần đặt nó vào một số tệp .h và đưa vào bất cứ nơi nào bạn cần.

Ngoài ra, bạn có thể tải xuống phiên bản stdint.htệp của Microsoft từ đây hoặc sử dụng phiên bản di động từ đây .


Xem stackoverflow.com/questions/126279/… để biết thông tin về cách tải stdint.h hoạt động với MSVC (và có thể cả Borland).
Michael Burr

2
Cả hai liên kết bị hỏng!
Antonio

1
Câu trả lời này liên quan đến C nhưng ngôn ngữ được gắn thẻ C ++ nên nó không phải là câu trả lời mà tôi đang tìm kiếm.
HaseeB Mir,

@HaSeeBMiR Một cách khắc phục thích hợp là chuyển sang <cstdint>hoặc tải xuống thích hợp cstdintnếu bạn tải xuống a stdint.h.
Justin Time - Phục hồi Monica

1
@HaSeeBMiR Lý do duy nhất câu trả lời liên quan đến C thay vì C ++ là nó sử dụng tiêu đề C thay vì tiêu đề C ++ tương đương. Bộ tiền xử lý C là một phần của C ++ và cstdintlà một phần của tiêu chuẩn C ++, cũng như tất cả các tên kiểu được định nghĩa ở đó. Nó thực sự phù hợp với các thẻ được chỉ định. ... Tôi không đồng ý với việc xác định các kiểu theo cách thủ công, nhưng có thể cần thiết khi làm việc với các trình biên dịch không làm như vậy.
Giờ Justin - Phục hồi Monica

91

Tôi muốn nói đây là cách C ++ hiện đại.

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

CHỈNH SỬA :

Loại chính xác cho Số nguyên

vì vậy cách thích hợp để lưu trữ một con trỏ dưới dạng số nguyên là sử dụng các kiểu uintptr_thoặc intptr_t. (Xem thêm trong các kiểu số nguyên cppreference cho C99 ).

các kiểu này được định nghĩa trong <stdint.h>C99 và trong không gian tên stdcho C ++ 11 in <cstdint>(xem kiểu số nguyên cho C ++ ).

Phiên bản C ++ 11 (trở đi)

#include <cstdint>
std::uintptr_t i;

Phiên bản C ++ 03

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

uintptr_t i;

Phiên bản C99

#include <stdint.h>
uintptr_t i;

Toán tử truyền chính xác

Trong C chỉ có một kiểu ép kiểu và việc sử dụng kiểu ép C trong C ++ là điều không tốt (vì vậy đừng sử dụng nó trong C ++). Trong C ++ có các phôi khác nhau. reinterpret_castlà diễn viên chính xác cho chuyển đổi này (Xem thêm tại đây ).

Phiên bản C ++ 11

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

Phiên bản C ++ 03

uintptr_t i = reinterpret_cast<uintptr_t>(p);

Phiên bản C

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

Câu hỏi liên quan


6
câu trả lời duy nhất đúng đề cập reinterpret_cast
plasmacel

Nếu bạn muốn bao gồm <cstdint>, bạn có thể cũng muốn sử dụng std :: uintptr_t để thay thế.
linleno

Tuyệt vời ... Dàn diễn viên chính là thứ tôi đang tìm kiếm. Nếu chúng ta được yêu cầu sử dụng uintptr_tthay vì size_t, thì tại sao nó lại yêu cầu reinterpret_cast? Nó có vẻ như là một việc đơn giản static_castnên làm vì tiêu chuẩn đặc biệt cung cấp các loại dữ liệu tương thích ...
jww 27/09/2016

1
@jww read: en.cppreference.com/w/cpp/language/static_cast hiểu của tôi ở đây là nó static_castcó thể chuyển đổi kiểu hoặc nếu đó là một con trỏ có thể thực hiện điều chỉnh con trỏ nếu kiểu cần nó. reinterpret_castthực sự chỉ là thay đổi kiểu của mẫu bộ nhớ cơ bản (không có đột biến). để làm rõ: static_castkhông cư xử giống hệt nhau ở đây.
Alexander Oh

2
điều này sẽ được đánh dấu là câu trả lời đã chọn thay vì nó cung cấp tất cả các chi tiết về cách truyền trong C và C ++ .
HaseeB Mir

42

'size_t' và 'ptrdiff_t' được yêu cầu để phù hợp với kiến ​​trúc của bạn (bất kể nó là gì). Do đó, tôi nghĩ thay vì sử dụng 'int', bạn có thể sử dụng 'size_t', trên hệ thống 64 bit nên là loại 64 bit.

Cuộc thảo luận unsigned int vs size_t này đi vào chi tiết hơn một chút.


34
Mặc dù size_t thường đủ lớn để chứa một con trỏ nhưng không nhất thiết phải như vậy. Sẽ tốt hơn nếu bạn định vị tiêu đề stdint.h (nếu trình biên dịch của bạn chưa có) và sử dụng uintptr_t.
Michael Burr

3
Thật không may, hạn chế duy nhất size_tlà nó phải giữ kết quả của bất kỳ sizeof(). Điều này không nhất thiết phải làm cho nó 64 bit trên x64. xem thêm
Antoine

3
size_t có thể lưu trữ an toàn giá trị của một con trỏ không phải thành viên. Xem en.cppreference.com/w/cpp/types/size_t .
AndyJost

2
@AndyJost Không, không thể. Ngay cả liên kết của riêng bạn cũng xác nhận điều đó.
yyny

1
@YoYoYonnY: "Trên nhiều nền tảng (ngoại lệ là các hệ thống có địa chỉ phân đoạn) std :: size_t có thể lưu trữ an toàn giá trị của bất kỳ con trỏ không phải thành viên nào, trong trường hợp đó nó đồng nghĩa với std :: uintptr_t." - bất cứ điều gì bạn đang nói về?
slashmais


8

Một số câu trả lời đã chỉ ra uintptr_t#include <stdint.h>là 'giải pháp'. Đó là, tôi đề nghị, một phần của câu trả lời, nhưng không phải là toàn bộ câu trả lời. Bạn cũng cần phải xem nơi hàm được gọi với ID thông báo của FOO.

Hãy xem xét mã này và biên dịch:

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

Bạn sẽ quan sát thấy rằng có một vấn đề tại vị trí đang gọi (in main()) - chuyển đổi một số nguyên thành một con trỏ mà không cần ép kiểu. Bạn sẽ cần phải phân tích function()tất cả các cách sử dụng của mình để xem cách các giá trị được chuyển cho nó. Mã bên trong của tôi function()sẽ hoạt động nếu các cuộc gọi được viết:

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

Vì của bạn có thể được viết khác, bạn cần xem lại các điểm mà hàm được gọi để đảm bảo rằng việc sử dụng giá trị như được hiển thị là hợp lý. Đừng quên, bạn có thể đang tìm thấy một lỗi tiềm ẩn.

Ngoài ra, nếu bạn định định dạng giá trị của void *tham số (như đã được chuyển đổi), hãy xem xét kỹ <inttypes.h>tiêu đề (thay vì stdint.h- inttypes.hcung cấp các dịch vụ của stdint.h, điều này không bình thường, nhưng tiêu chuẩn C99 nói rằng [t] he header <inttypes.h>bao gồm tiêu đề <stdint.h>và mở rộng nó với các tiện ích bổ sung được cung cấp bởi các triển khai được lưu trữ ) và sử dụng macro PRIxxx trong chuỗi định dạng của bạn.

Ngoài ra, nhận xét của tôi hoàn toàn có thể áp dụng cho C chứ không phải C ++, nhưng mã của bạn nằm trong tập hợp con của C ++ có thể di chuyển giữa C và C ++. Cơ hội từ công bằng đến tốt mà nhận xét của tôi áp dụng.


3
Tôi nghĩ rằng bạn đã bỏ lỡ điểm trong câu hỏi của tôi. Mã đang lưu trữ giá trị của một số nguyên trong một con trỏ. Và phần mã đó đang làm ngược lại (ví dụ: trích xuất giá trị của số nguyên được viết dưới dạng con trỏ).
PierreBdR

@PierreBdR Tuy nhiên, anh ấy đưa ra một quan điểm rất xác đáng. Không phải lúc nào cũng đơn giản như việc xem mã (kể cả khi trình biên dịch cảnh báo về nó) sử dụng int có dấu nhưng được sử dụng cho một kích thước và nghĩ rằng có thể thay đổi nó thành không dấu. Thật không may, nó không phải lúc nào cũng đơn giản như vậy. Bạn phải xem xét từng trường hợp một cách rõ ràng trừ khi bạn muốn gây ra các lỗi tiềm ẩn - và các lỗi nhỏ ở đó.
Pryftan

4
  1. #include <stdint.h>
  2. Sử dụng uintptr_tloại tiêu chuẩn được xác định trong tệp tiêu đề tiêu chuẩn được bao gồm.

3

Tôi đã gặp câu hỏi này khi đang nghiên cứu mã nguồn của SQLite .

Trong sqliteInt.h , có một đoạn mã được xác định là một macro chuyển đổi giữa số nguyên và con trỏ. Tác giả đã đưa ra một tuyên bố rất hay khi chỉ ra rằng nó phải là một vấn đề phụ thuộc vào trình biên dịch và sau đó thực hiện giải pháp để tính đến hầu hết các trình biên dịch phổ biến hiện có.

#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

Và đây là trích dẫn của bình luận để biết thêm chi tiết:

/*
** 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.
*/

Tín dụng sẽ được chuyển cho người cam kết.


2

Điều tốt nhất cần làm là tránh chuyển đổi từ loại con trỏ sang các loại không phải con trỏ. Tuy nhiên, điều này rõ ràng là không thể trong trường hợp của bạn.

Như mọi người đã nói, uintptr_t là thứ bạn nên sử dụng.

Đây link có thông tin tốt về chuyển đổi sang mã 64-bit.

Cũng có một cuộc thảo luận tốt về điều này trên comp.std.c


2

Tôi nghĩ rằng "ý nghĩa" của void * trong trường hợp này là một xử lý chung. Nó không phải là một con trỏ đến một giá trị, nó là chính giá trị đó. (Đây chỉ là cách void * được các lập trình viên C và C ++ sử dụng.)

Nếu nó đang giữ một giá trị số nguyên, thì nó tốt hơn nên nằm trong phạm vi số nguyên!

Đây là cách dễ dàng kết xuất thành số nguyên:

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

Nó chỉ nên đưa ra một cảnh báo.


0

Kể từ khi uintptr_tđược không được bảo đảm để có mặt ở đó trong C ++ / C ++ 11 , nếu điều này là một sự chuyển đổi một cách để bạn có thể xem xét uintmax_t, luôn luôn quy định tại <cstdint>.

auto real_param = reinterpret_cast<uintmax_t>(param);

Để chơi an toàn, người ta có thể thêm vào bất cứ đâu trong mã một khẳng định:

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

Nếu bạn không có uintptr_t, thì uintmax_t cũng không phải là câu trả lời: không có garanty bạn có thể lưu trữ giá trị của một con trỏ trong đó! Có thể không có kiểu số nguyên nào làm được điều đó.
PierreBdR
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.