Перетворення покажчика на ціле число


85

Я намагаюся адаптувати існуючий код до 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. Відповідь Алекса нижче виглядає правильно. Відповідь Алекса uintptr_tпрацює майже скрізь, і це зараз є стандартним. Він забезпечує обробку C ++ 11 і навіть захищає заголовки C ++ 03.
jww

Відповіді:


68

Використовуйте 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 Мір

@HaSeeBMiR Відповідним виправленням є перехід на <cstdint>або завантаження відповідного, cstdintякщо ви завантажуєте stdint.h.
Джастін Тайм - відновити Моніку

1
@HaSeeBMiR Єдина причина, що відповідь пов'язана з C замість C ++, полягає в тому, що він використовує заголовок C замість еквівалентного заголовка C ++. Препроцесор С є частиною С ++ і cstdintє частиною стандарту С ++, як і всі імена типів, визначені там. Це дійсно підходить для зазначених тегів. ... Я не згоден із визначенням типів вручну, але це може знадобитися при роботі з компіляторами, які цього не роблять.
Час Джастіна - відновити Моніку

91

Я б сказав, що це сучасний спосіб 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-акторського перекладу в 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

Якщо ви мали намір включити <cstdint>, ви, ймовірно, також хочете використовувати std :: uintptr_t.
linleno

Чудово ... Акторський склад - це те, що я шукав. Якщо нам кажуть використовувати uintptr_tзамість size_t, то чому це вимагає reinterpret_cast? Здається, це static_castслід зробити просто , оскільки стандарт спеціально передбачає сумісні типи даних ...
jww

1
@jww read: en.cppreference.com/w/cpp/language/static_cast Я розумію тут, що це static_castможе перетворити тип, або якщо це вказівник, може зробити налаштування покажчика, якщо типу це потрібно. reinterpret_castнасправді просто змінює тип основного шаблону пам'яті (без мутацій). уточнити: static_castтут поводиться ідентично.
Олександр О

2
натомість це повинно бути позначено як вибрана відповідь, оскільки воно надає всі подробиці, як передати дані на C та C ++ .
HaseeB Mir,

42

'size_t' та 'ptrdiff_t' потрібні, щоб відповідати вашій архітектурі (якою б вона не була). Тому, я думаю, замість того, щоб використовувати 'int', ви повинні мати можливість використовувати 'size_t', який у 64-бітовій системі повинен мати 64-бітний тип.

Це обговорення без підпису int vs size_t йде трохи детальніше.


34
Хоча size_t зазвичай достатньо великий, щоб містити покажчик, це не обов'язково так. Краще було б знайти заголовок stdint.h (якщо ваш компілятор його ще не має) і використовувати uintptr_t.
Майкл Берр,

3
На жаль, єдиним обмеженням size_tє те, що воно повинно містити результат будь-якого sizeof(). Це не обов'язково становить 64 біти на x64. див. також
Антуан

3
size_t може безпечно зберігати значення покажчика, що не є членом. Див. En.cppreference.com/w/cpp/types/size_t .
AndyJost

2
@AndyJost Ні, не може. Навіть ваше власне посилання це підтверджує.
yyny

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
$

Ви помітите, що існує проблема у місці виклику (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

@PierreBdR Тим не менше, він робить дуже вагому думку. Це не завжди так просто, як подивитися на код (у тому числі, коли компілятори попереджають про це), який використовує підписаний int, але використовується для розміру, і вважає, що добре змінити його на unsigned. На жаль, це не завжди так просто. Ви повинні чітко розглядати кожен випадок, якщо не хочете викликати потенційні помилки - і тонкі помилки.
Прифтан,

4
  1. #include <stdint.h>
  2. Використовуйте uintptr_tстандартний тип, визначений у стандартному файлі заголовка.

3

Я зіткнувся з цим питанням під час вивчення вихідного коду 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.