размер указателей и архитектура

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

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

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

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

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

Ответ 1

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

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

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

Ответ 2

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

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

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

Но вот одно важное замечание. Хотя большинство указателей будет иметь одинаковый размер, указатели на функции могут отличаться. Не гарантируется, что 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 и макрос.)

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

Ответ 3

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

Ответ 4

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

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

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

  • системы могут иметь разные размеры регистров и использовать различную базовую ширину для данных и адресации: непонятно, что "биты целевой архитектуры" даже означают для такой системы, поэтому вам нужно выбрать конкретный ABI (и как только вы это сделаете, вы знать ответ, для этого ABI).

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

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

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

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

Ответ 5

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

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

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

x86, 16- бит

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

x86, бит 32- сегментирован

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

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

STM8A

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

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

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

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

Ответ 6

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

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

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

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

Ответ 7

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

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

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

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

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

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

Ответ 8

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

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

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

Ответ 9

Обычно указатели будут иметь размер 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.