Почему максимальный размер массива "слишком велик"?
У меня такое же впечатление, как этот ответ, что size_t
всегда гарантируется стандартом, чтобы быть достаточно большим, чтобы удерживать максимально возможный тип данной системы.
Однако этот код не скомпилирован в gcc/Mingw:
#include <stdint.h>
#include <stddef.h>
typedef uint8_t array_t [SIZE_MAX];
error: размер массива 'array_t' слишком велик
Я что-то не понимаю в стандарте? Разрешено ли size_t
быть слишком большим для данной реализации? Или это еще одна ошибка в Mingw?
EDIT: дальнейшие исследования показывают, что
typedef uint8_t array_t [SIZE_MAX/2]; // does compile
typedef uint8_t array_t [SIZE_MAX/2+1]; // does not compile
Что происходит, как
#include <limits.h>
typedef uint8_t array_t [LLONG_MAX]; // does compile
typedef uint8_t array_t [LLONG_MAX+(size_t)1]; // does not compile
Итак, теперь я склонен полагать, что это ошибка в Mingw, потому что установка максимально допустимого размера на основе целочисленного типа со знаком не имеет никакого смысла.
Ответы
Ответ 1
Предел SIZE_MAX/2 определяется определениями size_t и ptrdiff_t для вашей реализации, которые выбирают, что типы ptrdiff_t и size_t имеют одинаковую ширину.
C Стандартные мандаты 1 что тип size_t не указан и тип ptrdiff_t подписан.
Результат разницы между двумя указателями всегда будет 2 иметь тип ptrdiff_t. Это означает, что при вашей реализации размер объекта должен быть ограничен
PTRDIFF_MAX, в противном случае допустимое различие двух указателей не может быть представлено в типе ptrdiff_t, что приведет к поведению undefined.
Таким образом, значение SIZE_MAX/2 равно значению PTRDIFF_MAX. Если реализация решит, что максимальный размер объекта будет SIZE_MAX, тогда ширина типа ptrdiff_t должна быть увеличена. Но гораздо проще ограничить максимальный размер объекта SIZE_MAX/2, тогда он должен иметь тип ptrdiff_t с большим или равным положительным диапазоном, чем тип size_t.
Стандарт предлагает эти 3 комментарии 4 в теме.
(Цитируется по ISO/IEC 9899: 201x)
1 (7.19 Общие определения 2)
Типы -
ptrdiff_t
который является признанным целочисленным типом результата вычитания двух указателей;
size_t
который представляет собой целочисленный тип без знака результата оператора sizeof;
2 (6.5.6 Аддитивные операторы 9)
Когда два указателя вычитаются, оба должны указывать на элементы одного и того же объекта массива,
или один за последним элементом объекта массива; в результате возникает разница
индексы двух элементов массива. Размер результата определяется реализацией,
и его тип (целочисленный тип со знаком) является ptrdiff_t, определенным в заголовке.
Если результат не представлен в объекте этого типа, поведение undefined.
3 (K.3.4 Целочисленные типы 3)
Чрезвычайно большие размеры объектов часто являются признаком того, что был рассчитан размер объекта
неправильно. Например, отрицательные числа появляются как очень большие положительные числа, когда
преобразуется в неподписанный тип типа size_t. Кроме того, некоторые реализации не поддерживают
объекты, максимальные значения которых могут быть представлены типом size_t.
4 (K.3.4 Целочисленные типы 4)
По этим причинам иногда полезно ограничивать диапазон размеров объектов для обнаружения
ошибки программирования. Для реализаций, ориентированных на машины с большими адресными пространствами,
рекомендуется, чтобы RSIZE_MAX определялся как меньший размер самого большого
объект (или SIZE_MAX → 1), даже если этот предел меньше размера
некоторые законные, но очень большие объекты. Реализации, ориентированные на машины с небольшими
адресные пространства могут пожелать определить RSIZE_MAX как SIZE_MAX, что означает, что нет размера объекта, который считается нарушением ограничения времени выполнения.
Ответ 2
Диапазон size_t
гарантированно будет достаточным для хранения размера самого большого объекта, поддерживаемого реализацией. Обратное неверно: вы не можете создать объект, размер которого заполняет весь диапазон size_t
.
В таких условиях возникает вопрос: что означает SIZE_MAX
? Самый большой размер поддерживаемого объекта? Или наибольшее значение, представленное в size_t
? Ответ таков: последний, т.е. SIZE_MAX
равен (size_t) -1
. Вам не гарантируется возможность создания объектов SIZE_MAX
больших байтов.
Причиной этого является то, что в дополнение к size_t
реализации должны также предоставлять ptrdiff_t
, который предназначен (но не гарантирован) для хранения разницы между двумя указателями, указывающими на один и тот же объект массива. Поскольку тип ptrdiff_t
подписан, реализации сталкиваются со следующими вариантами:
-
Разрешить объекты массива размером SIZE_MAX
и сделать ptrdiff_t
шире, чем size_t
. Он должен быть шире как минимум на один бит. Такой ptrdiff_t
может вместить любую разницу между двумя указателями, указывающими на массив размером SIZE_MAX
или меньше.
-
Разрешить объекты массива размером SIZE_MAX
и использовать ptrdiff_t
той же ширины, что и size_t
. Примите тот факт, что вычитание указателя может переполняться и вызывать поведение undefined, если указатели расположены дальше, чем элементы SIZE_MAX / 2
. Спецификация языка не запрещает этот подход.
-
Используйте ptrdiff_t
той же ширины, что и size_t
, и ограничьте максимальный размер объекта массива на SIZE_MAX / 2
. Такой ptrdiff_t
может вместить любую разницу между двумя указателями, указывающими на массив размером SIZE_MAX / 2
или меньше.
Вы просто имеете дело с реализацией, которая решила следовать третьему подходу.
Ответ 3
Он очень похож на поведение, специфичное для реализации.
Я запускаю здесь Mac OS, а с gcc 6.3.0 самый большой размер, с которым я могу скомпилировать ваше определение, - SIZE_MAX/2
; с SIZE_MAX/2 + 1
он больше не компилируется.
С другой стороны, ведьма clang 4.0.0 самая большая - SIZE_MAX/8
, а SIZE_MAX/8 + 1
ломается.
Ответ 4
Прежде всего, size_t
используется для хранения результата оператора sizeof
. Таким образом, гарантируется размер, который может содержать "значение" SIZE_MAX
. Теоретически это должно позволить вам определить любой объект с размером SIZE_MAX
.
Затем, если я правильно помню, вы ограничены верхним пределом общесистемных ресурсов. Это не ограничения, налагаемые стандартом C, а скорее OS/environment.
Проверьте вывод ulimit -a
. Вы также можете изменить лимиты с помощью ulimit -s <size>
для стека, пока массив, который вы определяете, сохраняется в стеке. В противном случае для глобальных массивов, вероятно, вам нужно проверить разрешенный размер в .DATA или .BSS в соответствии с вашей ОС. Таким образом, это зависит от среды (или зависит от реализации).
Ответ 5
Просто рассуждая с нуля, size_t
- это тип, который может содержать размер любого объекта. Размер любого объекта ограничен шириной адресной шины (игнорируя мультиплексирование и системы, которые могут обрабатывать, например, 32 и 64-битный код, вызывают это "ширина кода" ). Анологично MAX_INT
, которое является наибольшим целочисленным значением, SIZE_MAX
является наибольшим значением size_t
. Таким образом, объект размером SIZE_MAX
является всей адресной памятью. Разумеется, флаг реализации, который, как ошибка, однако, я согласен с тем, что это ошибка только в том случае, когда выделен фактический объект, будь то в стеке или в глобальной памяти. (Вызов malloc
для этой суммы будет сбой в любом случае)