Подтвердить что ты не робот

Может ли calloc() выделять больше, чем SIZE_MAX?

В недавнем обзоре кода было заявлено, что

В некоторых системах calloc() может выделять больше байтов SIZE_MAX тогда как malloc() ограничен.

Я утверждаю, что это ошибочно, потому что calloc() создает пространство для массива объектов, который, будучи массивом, сам по себе является объектом. И ни один объект не может быть большего размера, чем SIZE_MAX.

Итак, кто из нас прав? На (возможно гипотетической) системе с адресным пространством, большим, чем диапазон size_t, разрешено ли calloc() преуспеть при вызове с аргументами, чей продукт больше SIZE_MAX?

Чтобы сделать его более конкретным: выйдет ли следующая программа с ненулевым статусом?

#include <stdint.h>
#include <stdlib.h>

int main()
{
     return calloc(SIZE_MAX, 2) != NULL;
}
4b9b3361

Ответ 1

SIZE_MAX не обязательно указывает максимальный размер объекта, а скорее максимальное значение size_t, что не обязательно одно и то же. См. Почему максимальный размер массива "слишком велик"? ,

Но, очевидно, недостаточно четко передать большее значение, чем SIZE_MAX функции, ожидающей параметра size_t. Поэтому теоретически SIZE_MAX является пределом, и в теории calloc позволяет SIZE_MAX * SIZE_MAX байты SIZE_MAX * SIZE_MAX.

Дело с malloc/calloc заключается в том, что они выделяют объекты без типа. Объекты с типом имеют ограничения, например, никогда не превышающие определенный предел, такой как SIZE_MAX. Но данные, указанные в результате этих функций, не имеют типа. Это еще не массив.

Формально данные не имеют объявленного типа, но поскольку вы храните что-то внутри выделенных данных, он получает эффективный тип доступа к данным, используемого для хранения (C17 6.5 §6).

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

Поэтому, что касается стандарта C, для calloc(SIZE_MAX, 2) вполне нормально возвращать значение, отличное от NULL. Как на самом деле использовать выделенную память разумным способом или какие системы, которые поддерживают такие большие куски памяти в куче, - это еще одна история.

Ответ 2

Может ли calloc() выделять больше, чем SIZE_MAX?

В качестве утверждения "В выбранных системах calloc() может выделять больше байтов SIZE_MAX тогда как malloc() ограничен". пришел из комментария, который я опубликовал, я объясню свое обоснование.


size_t

size_t - это какой-то неподписанный тип не менее 16 бит.

size_t который является целым числом без знака результата оператора sizeof; C11dr §7.19 2

"Его значение, определяемое реализацией, должно быть равно или больше по величине... чем соответствующее значение, приведенное ниже"... limit size_t SIZE_MAX... 65535 §7.20.3 2

размер

Оператор sizeof дает размер (в байтах) своего операнда, который может быть выражением или именем в скобках типа. §6.5.3.4 2

calloc

void *calloc(size_t nmemb, size_t size);

calloc функция выделяет пространство для массива nmemb объектов, каждый из которых size является размер. §7.22.3.2 2


Рассмотрим ситуацию, когда nmemb * size значительно превышает SIZE_MAX.

size_t alot = SIZE_MAX/2;
double *p = calloc(alot, sizeof *p); // assume 'double' is 8 bytes.

Если calloc() действительно выделил nmemb * size bytes и если p != NULL истинно, то какая спецификация нарушила это?

Размер каждого элемента (каждый объект) является представимым.

// Nicely reports the size of a pointer and an element.
printf("sizeof p:%zu, sizeof *p:%zu\n", sizeof p, sizeof *p); 

Доступ к каждому элементу.

// Nicely reports the value of an 'element' and the address of the element
for (size_t i = 0; i<alot; i++) {
  printf("value a[%zu]:%g, address:%p\n", i, p[i], (void*) &p[i]); 
}

calloc() подробнее

"пространство для массива объектов nmemb ": это, безусловно, ключевой момент раздора. SIZE_MAX ли "выделить пространство для массива" <= SIZE_MAX? Я не нашел ничего в спецификации C, чтобы потребовать этот предел, и поэтому заключаем:

calloc() может выделять больше, чем SIZE_MAX.


Для calloc() с большими аргументами, редко встречается non- NULL - совместимый или нет. Обычно такие распределения превышают доступную память, поэтому проблема спорная. Единственный случай, с которым я столкнулся, - это модель size_t памяти, где size_t был 16 бит, а указатель объекта - 32 бит.

Ответ 3

От

7.22.3.2 Функция calloc

конспект
1

 #include <stdlib.h>
 void *calloc(size_t nmemb, size_t size);'

Описание
2 Функция calloc выделяет пространство для массива объектов nmemb, каждый из которых имеет размер. Пробел инициализируется ко всем битам нуль.

Возвращает
3 Функция calloc возвращает либо нулевой указатель, либо указатель на выделенное пространство.

Я не понимаю, почему выделенное пространство должно быть ограничено байтами SIZE_MAX.

Ответ 4

Если программа превышает пределы реализации, поведение не определено. Это следует из определения предела реализации как ограничения, налагаемого на программы реализацией (3.13 в C11). В стандарте также говорится, что строго соответствующие программы должны придерживаться пределов реализации (4p5 в C11). Но это также подразумевает программы в целом, потому что стандарт не говорит, что происходит, когда большинство пределов реализации превышено (так что это другой тип неопределенного поведения, когда стандарт не определяет, что происходит).

Стандарт также не определяет, какие ограничения реализации могут существовать, так что это немного карт-бланш, но я думаю, что разумно, что максимальный размер объекта действительно имеет отношение к распределению объектов. (Максимальный размер объекта обычно меньше, чем SIZE_MAX, между прочим, потому что разница указателей на char внутри объекта должна быть представлена в ptrdiff_t.)

Это приводит нас к следующему наблюдению: вызов calloc (SIZE_MAX, 2) превышает максимальный предел размера объекта, поэтому реализация может вернуть произвольное значение, все еще соответствующее стандарту.

Некоторые реализации фактически возвращают указатель, который не является нулевым для вызова типа calloc (SIZE_MAX/2 + 2, 2) потому что реализация не проверяет, что результат умножения не вписывается в значение size_t. Является ли это хорошей идеей для другого вопроса, учитывая, что предел реализации можно легко проверить в этом случае, и есть прекрасный способ сообщить об ошибках. Лично я считаю отсутствие проверки переполнения в calloc ошибкой реализации и сообщал об ошибках разработчикам, когда я их видел, но технически это просто проблема с качеством реализации.

Для массивов переменной длины в стеке правило о превышении пределов реализации, приводящее к неопределенному поведению, более очевидно:

size_t length = SIZE_MAX / 2 + 2;
short object[length];

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

Ответ 5

По тексту стандарта, может быть, потому что стандарт (кто-то сказал бы намеренно) смутно об этом.

По 6.5.3.4 ¶2:

Оператор sizeof дает размер (в байтах) его операнда

и на 7.19 ¶2:

size_t

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

Первое не может быть выполнено в общем случае, если реализация допускает любой тип (включая типы массивов), размер которого не представлен в size_t. Обратите внимание, что независимо от того, интерпретируете ли вы текст указателя, возвращаемого calloc указывающего на "массив", всегда существует массив, связанный с любым объектом: наложенный массив типа unsigned char[sizeof object] который является его представлением.

В лучшем случае реализация, которая позволяет создавать любой объект размером более SIZE_MAX (или PTRDIFF_MAX, по другим причинам), имеет смертельно плохие проблемы с качеством обслуживания (QoI). Требование о пересмотре кода, в котором вы должны учитывать такие плохие реализации, является фиктивным, если вы специально не пытаетесь обеспечить совместимость с определенной нарушенной реализацией C (иногда это актуально для встроенных и т.д.).

Ответ 6

В Стандарте ничего не говорится о возможности создания какого-либо указателя таким образом, чтобы ptr+number1+number2 мог быть допустимым указателем, но number1+number2 SIZE_MAX превысил SIZE_MAX. Это, безусловно, позволяет использовать число 1 number1+number2 превышающее PTRDIFF_MAX (хотя по какой-то причине C11 решил потребовать, чтобы даже реализации с 16-разрядным адресным пространством должны использовать 32-разрядный ptrdiff_t).

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

Однако способность выделять любой объект полезнее, это проблема качества выполнения. Стандарт никогда не потребовал бы, чтобы какой-либо конкретный запрос на распределение был успешным, и не запретил бы реализации возвращать указатель, который может оказаться непригодным (в некоторых Linux-средах malloc() может дать указатель на переполненный регион адресное пространство, попытка использования указателя при наличии достаточного физического хранилища может вызвать фатальную ловушку). Разумеется, было бы лучше, если бы не капризная реализация calloc(x,y) для возврата null, если числовой продукт x и y превышает SIZE_MAX, чем для того, чтобы он дал указатель, который не может использоваться для доступа к этому числу байтов, Однако в стандарте не говорится о том, следует ли считать, что возврат указателя, который может использоваться для доступа к y объектам из x байтов, лучше или хуже, чем возврат null. Каждое поведение было бы выгодным в некоторых ситуациях, а в других - невыгодным.