размер указателей и архитектура - PullRequest
17 голосов
/ 19 мая 2019

Проводя базовый тест, запуская простую программу на С ++ на обычном настольном ПК, кажется правдоподобным предположить, что размеры указателей любого типа (включая указатели на функции) равны битам целевой архитектуры?

Например: в 32-битных архитектурах -> 4 байта и в 64-битных архитектурах -> 8 байтов.

Однако я помню, что читал это, в общем-то это не так!

Так что мне было интересно, каковы будут такие обстоятельства?

  • Для равенства размеровуказатели на типы данных по сравнению с размером указателей на другие типы данных
  • Для равенства размера указателей на типы данных по сравнению с размером указателей на функции
  • Для равенства размера указателей для целевой архитектуры

Ответы [ 9 ]

17 голосов
/ 19 мая 2019

Нет, это не разумно предполагать.Это предположение может привести к ошибкам.

Размеры указателей (и целочисленных типов) в C или C ++ в конечном итоге определяются реализацией C или C ++.Обычные реализации C или C ++ находятся под сильным влиянием архитектур и операционных систем, на которые они нацелены, но они могут выбирать размеры своих типов по причинам, отличным от скорости выполнения, таким как цели поддержки меньшего использования памяти, поддержки кода, который не был написан длябыть полностью переносимым для любых типов шрифтов или поддерживать более простое использование больших целых чисел.

Я видел компилятор, предназначенный для 64-битной системы, но предоставляющий 32-битные указатели, для создания программ с меньшим объемом памятииспользовать.(Было замечено, что размеры указателей были значительным фактором в потреблении памяти из-за использования многих структур со многими соединениями и ссылками, использующими указатели.) Исходный код написан с предположением, что размер указателя равен 64-битному региструразмер сломался бы.

13 голосов
/ 19 мая 2019

Разумно предположить, что в общем случае размеры указателей любого типа (включая указатели на функции) равны битам целевой архитектуры

Зависит. Если вы стремитесь к быстрой оценке потребления памяти, этого может быть достаточно.

(включая указатели на функции)

Но вот одно важное замечание. Хотя большинство указателей будет иметь одинаковый размер, указатели на функции могут отличаться. Не гарантируется, что void* сможет содержать указатель на функцию. По крайней мере, это верно для C. Я не знаю о C ++.

Так что мне было интересно, какие будут такие обстоятельства, если таковые имеются?

Может быть множество причин, по которым он отличается. Если правильность ваших программ зависит от этого размера, НИКОГДА не стоит делать такие предположения. Проверьте это вместо этого. Это не должно быть трудно.

Вы можете использовать этот макрос для проверки таких вещей во время компиляции в C:

#include <assert.h>
static_assert(sizeof(void*) == 4, "Pointers are assumed to be exactly 4 bytes");

При компиляции выдается сообщение об ошибке:

$ gcc main.c 
In file included from main.c:1:
main.c:2:1: error: static assertion failed: "Pointers are assumed to be exactly 4 bytes"
 static_assert(sizeof(void*) == 4, "Pointers are assumed to be exactly 4 bytes");
 ^~~~~~~~~~~~~

Если вы используете C ++, вы можете пропустить #include <assert.h>, потому что static_assert является ключевым словом в C ++. (И вы можете использовать ключевое слово _Static_assert в C, но оно выглядит ужасно, поэтому используйте вместо него include и макрос.)

Поскольку эти две строки очень легко включить в ваш код, нет НИКАКИХ оправданий, если вы не будете правильно работать с неверным размером указателя.

9 голосов
/ 19 мая 2019

Разумно предположить, что в общем случае размеры указателей любого типа (включая указатели на функции) равны битам целевой архитектуры?

Это может быть разумно, но это не совсем правильно.Поэтому я думаю, что ответ «нет, кроме случаев, когда вы уже знаете, что ответ« да »(и не беспокоитесь о переносимости)» .

Потенциально:

  • системы могут поддерживать разные модели указателей, такие как старые указатели near, far и huge;в этом случае вам нужно знать, в каком режиме компилируется ваш код (и тогда вы знаете ответ, для этого режима)

  • системы могут поддерживать указатели разных размеров, такие как X32 ABI, уже упомянутыйили любой из других популярных 64-битных моделей данных, описанных здесь

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

Если вы хотите конвертировать между целыми числами и указателями, используйте intptr_t.Если вы хотите хранить целые числа и указатели в одном и том же пространстве, просто используйте union.

7 голосов
/ 19 мая 2019

«Биты» целевой архитектуры говорят о размере регистров.Ex.Intel 8051 является 8-разрядным и работает с 8-разрядными регистрами, но доступ к (внешнему) ОЗУ и (внешнему) ПЗУ осуществляется с помощью 16-разрядных значений.

5 голосов
/ 19 мая 2019

Разумно предположить, что в общем случае размеры указателей любого типа (включая указатели на функции) равны битам целевой архитектуры?

Если вы посмотрите на все типыПроцессоры (включая микроконтроллеры), которые в настоящее время производятся, я бы сказал, нет.

Экстремальные контрпримеры - это архитектуры, в которых два разных размера указателя используются в той же программе :

x86, 16-битный

В MS-DOS и 16-битной Windows «обычная» программа использует 16- и 32-битные указатели.

x86, 32-разрядная сегментация

Было только несколько менее известных операционных систем, использующих эту модель памяти.

Программы обычно использовали 32- и 48-разрядные-битные указатели.

STM8A

Этот современный автомобильный 8-разрядный ЦП использует 16- и 24-разрядные указатели.Конечно, и в одной и той же программе.

AVR, крошечная серия

ОЗУ адресуется с помощью 8-разрядных указателей, Flash - с помощью 16-разрядных указателей.

(Однако, насколько я знаю, AVR tiny не может быть запрограммирован на C ++.)

5 голосов
/ 19 мая 2019

Для правильности вы не можете ничего предположить.Вы должны проверить и быть готовым справляться со странными ситуациями.

Как правило общее , это разумное по умолчанию предположение ,

Это не всегда правда.См., Например, X32 ABI , который использует 32-битные указатели на 64-битных архитектурах, чтобы сэкономить часть памяти и объем кэша.То же самое для ILP32 ABI на AArch64.

Таким образом, для прогнозирования использования памяти вы можете использовать свое предположение, и оно часто будет правильным.

4 голосов
/ 19 мая 2019

Это не правильно, например указатели DOS (16 бит) могут быть далеко (seg + ofs).

Однако для обычных целей (Windows, OSX, Linux, Android, iOS) это правильно. Потому что все они используют плоскую модель программирования, которая опирается на пейджинг.

Теоретически, у вас также могут быть системы, которые используют только младшие 32 бита в x64. Примером является исполняемый файл Windows, связанный без LARGEADDRESSAWARE. Однако это поможет программисту избежать ошибок при переходе на x64. Указатели усекаются до 32 бит, но они все еще 64-битные.

В операционных системах x64 это предположение всегда верно, потому что плоский режим является единственным допустимым. Длинный режим в ЦП приводит к тому, что записи GDT становятся 64-битными.

Также упоминается ABI x32, я полагаю, что он основан на той же технологии пейджинга, заставляя все указатели отображаться на более низкие 4 ГБ. Однако это должно основываться на той же теории, что и в Windows. В x64 у вас может быть только плоский режим.

В 32-битном защищенном режиме вы можете иметь указатели до 48 бит. (Сегментированный режим). Вы также можете иметь callgates. Но ни одна операционная система не использует этот режим.

2 голосов
/ 20 мая 2019

Исторически на микрокомпьютерах и микроконтроллерах указатели часто были шире, чем регистры общего назначения, поэтому ЦП мог адресовать достаточно памяти и при этом соответствовать бюджету транзисторов.Большинство 8-битных процессоров (например, 8080, Z80 или 6502) имели 16-битные адреса.

Сегодня несоответствие более вероятно, потому что приложению не требуется несколько гигабайт данных, поэтому экономиячетыре байта памяти на каждый указатель - выигрыш.

И C, и C ++ предоставляют отдельные типы size_t, uintptr_t и off_t, представляющие максимально возможный размер объекта (который может быть меньше размерауказателя, если модель памяти не плоская), целочисленного типа, достаточно широкого, чтобы содержать указатель, и смещения файла (часто шире, чем самый большой объект, допустимый в памяти), соответственно.A size_t (без знака) или ptrdiff_t (со знаком) - это самый переносимый способ получить собственный размер слова.Кроме того, POSIX гарантирует, что системный компилятор имеет некоторый флаг, который означает, что long может содержать любой из них, но вы не всегда можете предположить, что так.

1 голос
/ 20 мая 2019

Обычно указатели имеют размер 2 в 16-битной системе, 3 в 24-битной системе, 4 в 32-битной системе и 8 в 64-битной системе. Это зависит от реализации ABI и C. У AMD есть длинный и унаследованный режимы, и есть различия между AMD64 и Intel64 для программистов на ассемблере , но они скрыты для языков более высокого уровня.

Любые проблемы с кодом C / C ++ могут быть связаны с плохой практикой программирования и игнорированием предупреждений компилятора. См .: « 20 проблем переноса кода C ++ на 64-битную платформу ».

См. Также: " Могут ли указатели быть разных размеров? " и Ответ LRiO :

... вы спрашиваете о C ++ и его совместимых реализациях, а не о какой-то конкретной физической машине. Мне бы пришлось процитировать весь стандарт, чтобы доказать это , но простой факт заключается в том, что он не дает никаких гарантий относительно результата sizeof (T *) для любого T и (как следствие) нет гарантий, что sizeof (T1 *) == sizeof (T2 *) для любых T1 и T2).

Примечание: Где - это , на который отвечает JeremyP , C99 раздел 6.3.2.3, подраздел 8:

Указатель на функцию одного типа может быть преобразован в указатель на функцию другого типа и обратно; результат должен сравниваться равным исходному указателю. Если преобразованный указатель используется для вызова функции, тип которой не совместим с указанным типом, поведение не определено.

В GCC вы можете избежать неправильных предположений, используя встроенные функции: " Проверка размера объекта встроенных функций ":

Встроенная функция: size_t __builtin_object_size (const void * ptr, int type)

- это встроенная конструкция, которая возвращает постоянное число байтов от ptr до конца указателя ptr объекта, на который указывает (если он известен во время компиляции). Для определения размеров динамически размещаемых объектов функция опирается на функции выделения, вызываемые для получения хранилища, которое будет объявлено с атрибутом alloc_size (см. Общие атрибуты функции). __builtin_object_size никогда не оценивает свои аргументы для побочных эффектов. Если в них есть побочные эффекты, он возвращает (size_t) -1 для типа 0 или 1 и (size_t) 0 для типа 2 или 3. Если есть несколько объектов, на которые может указывать ptr, и все они известны во время компиляции возвращаемое число - это максимальное число оставшихся байтов в этих объектах, если тип & 2 равен 0, и минимум, если он не равен нулю. Если невозможно определить, на какие объекты указывает ptr во время компиляции, __builtin_object_size должен возвращать (size_t) -1 для типа 0 или 1 и (size_t) 0 для типа 2 или 3.

...