sizeof (int) == sizeof (void*)?

Существует ли целочисленный тип того же размера, что и указатель? Гарантировано на всех микроархитектурах?


person Community    schedule 02.02.2009    source источник
comment
Почему вы хотите хранить указатель в целом числе? Мне не ясно, что это хорошая идея, даже если она гарантированно сработает (а это не так).   -  person paxdiablo    schedule 02.02.2009
comment
Оптимизация. Единый доступ к памяти/кэшу вместо двух отдельных доступов.   -  person    schedule 02.02.2009
comment
Одной из веских причин для этого является побитовая манипуляция. Побитовые операторы, похоже, не работают с указателями. Например, если вы хотите реализовать связанный список XOR или хотите замаскировать некоторые биты (возможно, чтобы получить адрес страницы), вам нужно сначала преобразовать в целое число.   -  person Jay Conrod    schedule 05.05.2009


Ответы (10)


Согласно этой странице Википедии, в C99 ваш заголовок stdint.h может объявить intptr_t и uintptr_t, но тогда это, конечно, требует

  • C99
  • Разработчик компилятора, решивший реализовать эту необязательную часть стандарта.

Так что в целом я думаю, что это сложно.

person unwind    schedule 02.02.2009
comment
GCC реализует это; для Visual Studio попробуйте azillionmonkeys.com/qed/pstdint.h или code.google.com/p/msinttypes - person Christoph; 02.02.2009
comment
Также есть версия MinGW: cygwin.com/cgi-bin/cvsweb.cgi/src/winsup/mingw/include/ - person Christoph; 02.02.2009
comment
стандарт НЕ< /b> гарантировать, что они одного размера; только то, что вы можете привести к (u)intptr_t и обратно (при условии, что память, на которую он ссылается, все еще действительна), и что нулевой указатель преобразуется в 0. В разумных архитектурах это будет просто адрес памяти, но это ни в коем случае означает гарантированный (например, приведение может случайным образом переставлять биты, нет гарантии, что вы можете вывести выравнивание из представления uintptr_t, арифметика указателя не должна работать...). - person tc.; 11.06.2012
comment
@тс. С каких пор стандарт C гарантирует, что нулевой указатель преобразуется в нулевое целое число? - person Deduplicator; 25.08.2020
comment
Хах, действительно нет. C89/99, по-видимому, гарантирует только то, что преобразование целочисленного константного выражения со значением 0 в указатель дает нулевой указатель, поэтому, например. int x = 0; if ((void*)x) { ... } определяется реализацией. Было бы действительно странно, если бы поведение резко изменилось в тот момент, когда компилятор решил, что что-то больше не является постоянным. - person tc.; 19.06.2021

Проще говоря, нет. Не гарантируется для всех архитектур.

Мой вопрос: почему? Если вы хотите выделить тип, достаточно большой для хранения void*, лучше всего выделить (как ни странно :-) тип void*. Зачем нужно помещать его в int?

РЕДАКТИРОВАТЬ: Основываясь на ваших комментариях к дублирующемуся вопросу, вы хотите сохранить специальные значения указателя (1,2,3) для указания дополнительной информации.

НЕТ!! Не делайте этого!!. Нет никакой гарантии, что 1, 2 и 3 не являются правильными указателями. Это может иметь место в системах, где вам необходимо выровнять указатели по 4-байтовым границам, но, поскольку вы спрашивали обо всех архитектурах, я предполагаю, что переносимость у вас имеет большое значение.

Найдите другой способ сделать это правильно. Например, используйте союз (синтаксис по памяти, может быть неверный):

typedef struct {
    int isPointer;
    union {
        int intVal;
        void *ptrVal;
    }
} myType;

Затем вы можете использовать логическое значение isPointer, чтобы решить, следует ли вам рассматривать объединение как целое число или указатель.

РЕДАКТИРОВАТЬ:

Если скорость выполнения имеет первостепенное значение, то решение typedef — это то, что вам нужно. По сути, вам нужно будет определить целое число, которое вы хотите для каждой платформы, на которой вы хотите работать. Вы можете сделать это с помощью условной компиляции. Я бы также добавил проверку во время выполнения, чтобы убедиться, что вы правильно скомпилировали для каждой платформы (я определяю это в исходном коде, но вы бы передали это как флаг компилятора, например «cc -DPTRINT_INT»):

#include <stdio.h>
#define PTRINT_SHORT
#ifdef PTRINT_SHORT
    typedef short ptrint;
#endif
#ifdef PTRINT_INT
    typedef int ptrint;
#endif
#ifdef PTRINT_LONG
    typedef long ptrint;
#endif
#ifdef PTRINT_LONGLONG
    typedef long long ptrint;
#endif

int main(void) {
    if (sizeof(ptrint) != sizeof(void*)) {
        printf ("ERROR: ptrint doesn't match void* for this platform.\n");
        printf ("   sizeof(void*    ) = %d\n", sizeof(void*));
        printf ("   sizeof(ptrint   ) = %d\n", sizeof(ptrint));
        printf ("   =================\n");
        printf ("   sizeof(void*    ) = %d\n", sizeof(void*));
        printf ("   sizeof(short    ) = %d\n", sizeof(short));
        printf ("   sizeof(int      ) = %d\n", sizeof(int));
        printf ("   sizeof(long     ) = %d\n", sizeof(long));
        printf ("   sizeof(long long) = %d\n", sizeof(long long));
        return 1;
    }

    /* rest of your code here */

    return 0;
}

В моей системе (Ubuntu 8.04, 32-разрядная версия) я получаю:

ERROR: ptrint typedef doesn't match void* for this platform.
   sizeof(void*    ) = 4
   sizeof(ptrint   ) = 2
   =================
   sizeof(short    ) = 2
   sizeof(int      ) = 4
   sizeof(long     ) = 4
   sizeof(long long) = 8

В этом случае я бы знал, что мне нужно скомпилировать с PTRINT_INT (или long). Возможно, есть способ поймать это во время компиляции с помощью #if, но я не мог утруждаться его исследованием в данный момент. Если вы столкнетесь с платформой, где нет целочисленного типа, достаточного для хранения указателя, вам не повезло.

Имейте в виду, что использование специальных значений указателя (1,2,3) для представления целых чисел также может работать не на всех платформах — на самом деле это могут быть допустимые адреса памяти для указателей.

Тем не менее, если ты собираешься проигнорировать мой совет, я мало что могу сделать, чтобы остановить тебя. В конце концов, это ваш код :-). Одна из возможностей состоит в том, чтобы проверить все ваши возвращаемые значения из malloc и, если вы получите 1, 2 или 3, просто снова использовать malloc (т. е. иметь mymalloc(), который делает это автоматически). Это будет небольшая утечка памяти, но она гарантирует отсутствие конфликтов между вашими специальными указателями и реальными указателями.

person paxdiablo    schedule 02.02.2009
comment
Проблема здесь в том, что вам нужно дважды проверить память/кэшлайн в решении объединения (для isPointer и ptrVal). Это самая внутренняя петля цикла - наносекунды имеют реальное значение. - person ; 02.02.2009
comment
Вы можете хранить всю необходимую информацию в поле указателя, но только если вы отбросите заботу о переносимости. Специальный случай вашего кода, если вам это действительно нужно. - person Magnus Hoff; 02.02.2009
comment
Хорошо понял. У меня не может быть одновременно переносимости и микрооптимизации. Тем не менее, кто-нибудь знает о платформе, на которой malloc действительно может возвращать значения 1, 2 или 3? - person ; 02.02.2009
comment
Смотрите обновления, чтобы ответить, Мартин. Если вы действительно хотите избежать конфликтов, напишите свой собственный mymalloc(), который не проходит через эти адреса. Это кладж, но он должен работать. - person paxdiablo; 02.02.2009
comment
Пакс, спасибо за ответ. Однако позвольте мне предложить другой способ решения проблемы: unsigned char *dummy = malloc(4); void *special_value1 = фиктивный + 0; .... Это сохранит специальные значения в кеше L1d, а не в L1i, но я думаю, что с этим можно справиться. - person ; 02.02.2009
comment
Подобные проверки предпочтительно должны быть утверждениями времени компиляции, а не выполняться для обнаружения проблемы. - person Erik Ekman; 19.10.2012
comment
Эрик, проверки во время выполнения — это всего лишь дополнительная защита от ввода разработчиком неправильного типа во время компиляции, и они необходимы только до тех пор, пока ваш make-файл (или другой инструмент сборки) не будет настроен правильно — я обычно оборачиваю все это в макрос #ifdef DEVMODE, чтобы он не влиял на производственный код. - person paxdiablo; 20.10.2012

Стандарт C99 определяет стандартные типы int:

7.18.1.4 Целочисленные типы, способные содержать указатели на объекты Следующий тип обозначает целочисленный тип со знаком, обладающий тем свойством, что любой действительный указатель на void может быть преобразован в этот тип, а затем преобразован обратно в указатель на void, и результат будет равен оригинальный указатель:

    intptr_t

Следующий тип обозначает беззнаковый целочисленный тип со свойством, согласно которому любой допустимый указатель на void может быть преобразован в этот тип, а затем преобразован обратно в указатель на void, и результат будет равен исходному указателю:

   uintptr_t

Эти типы являются необязательными.

C99 также определяет size_t и ptrdiff_t:

Типы

  ptrdiff_t

который представляет собой целочисленный тип со знаком результата вычитания двух указателей;

  size_t

который представляет собой беззнаковый целочисленный тип результата оператора sizeof; и

Архитектуры, которые я видел, имеют максимальный размер объекта, равный всей памяти, поэтому sizeof(size_t) == sizeof(void*), но я не знаю ничего, что было бы одновременно переносимо на C89 (который size_t является ) и гарантированно будет достаточно большим (что uintptr_t ).

person Pete Kirkham    schedule 02.02.2009
comment
ptrdiff_t и size_t также есть в C89, в отличие от intptr_t и uintptr_t. - person Jonathan Leffler; 03.02.2009
comment
Я добавил в сторону, чтобы указать, какие типы терпят неудачу в каждом плече «как переносимого на C89, так и гарантированно достаточно большого». - person Pete Kirkham; 03.02.2009

Это было бы верно для стандартной 32-битной системы, но, безусловно, нет никаких гарантий, и вы можете найти множество архитектур, где это не так. Например, распространенное заблуждение состоит в том, что sizeof(int) на x86_64 будет равен 8 (поскольку я думаю, что это 64-битная система), что не так. В x86_64 sizeof(int) по-прежнему равен 4, но sizeof(void*) равен 8.

person Liedman    schedule 02.02.2009

Стандартным решением этой проблемы является написание небольшой программы, которая проверяет размеры всех типов int (short int, int, long int) и сравнивает их с void*. Если есть совпадение, он выдает фрагмент кода, определяющий тип intptr. Вы можете поместить это в заголовочный файл и использовать новый тип.

Этот код легко включить в процесс сборки (например, используя make)

person Aaron Digulla    schedule 02.02.2009

Нет, ближе всего к переносимому целочисленному типу с поддержкой указателя будут intptr_t и ptrdiff_t.

person Ronny Vindenes    schedule 02.02.2009

No.

Я не верю, что стандарт C даже определяет стандартные размеры целых чисел. Объедините это со всеми существующими архитектурами (8/16/32/64 бит и т. д.), и нет никакого способа гарантировать что-либо.

person Rob McCready    schedule 02.02.2009
comment
Правильно, для этого просто требуется sizeof(short) ‹= sizeof(int) ‹= sizeof(long), но гарантии есть, если использовать ограничения, предоставленные stdint.h - person Friedrich; 02.02.2009

тип данных int был бы ответом на большинстве архитектур.

Но нет НИКАКИХ гарантий для ЛЮБОЙ (микро)архитектуры.

person AlexDrenea    schedule 02.02.2009
comment
Но обратите внимание, что int обычно составляет 32 бита на x86-64, поэтому в некоторых случаях вы можете предпочесть использовать long. - person Magnus Hoff; 02.02.2009
comment
Использование int на 64-битной машине было бы неправильным, за исключением исключительного случая ILP64. DEC Alpha был ведущим примером ILP64; Я не знаю ни одной современной архитектуры, использующей ILP64, а не LP64 или (для Windows 64) LLP64. - person Jonathan Leffler; 03.02.2009

Ответ кажется «нет», но если все, что вам нужно, это тип, который может действовать как оба, вы можете использовать объединение:

union int_ptr_t {
    int i;
    void* p;
};
person Magnus Hoff    schedule 02.02.2009
comment
Хотя вы должны быть осторожны с проблемами выравнивания здесь. Например, если void* — 64-разрядный, а int — 32-разрядный, какие 32-разрядные значения void* соответствуют типу int? - person Zooba; 02.02.2009
comment
Конечно, здесь нет никакой гарантии, что вы можете получить доступ ко всем битам члена указателя через целочисленный член. Надеюсь, это очевидно, но я все равно решил указать на это. - person unwind; 02.02.2009

Обычно sizeof(*void) зависит от ширины шины памяти (хотя это и не обязательно — AS/400 до RISC имела 48-битную адресную шину, но 64-битные указатели), а int обычно равен размеру регистра общего назначения ЦП (есть также исключения - SGI C использовала 32-битные целые числа на 64-битных MIPS).

Так что гарантии нет.

person qrdl    schedule 02.02.2009