В этом видео упоминается, что компилятор может предположить, что malloc
никогда не возвращает NULL
, и ему разрешено соответствующим образом оптимизировать. Я никогда не слышал об этом и не мог найти никаких упоминаний об этом в C-Standard. Может ли кто-нибудь сказать мне, правда ли это, и если да, то где указано это поведение?
Код, показанный в видео на данный момент:
if ((a = malloc(1024)) == NULL)
printf("We are out of memory!\n");
else
free(a);
Возьмем более простой пример: int a = 10; if (a == 10) { a = 5; }
. Довольно легко увидеть, что это можно преобразовать в int a = 5;
. Это в основном то, о чем эта часть видео: компилятор может просто удалить (или не включать) части кода, если это не имеет значения для программы. Это допустимо из-за правила как будто. По крайней мере, это то, что, я надеюсь, имеет в виду автор видео. Потому что нет «предположения», что память всегда можно выделить.
Это не означает, что malloc
может когда-либо возвращать ненулевой указатель, потому что в некоторых операционных системах это почти никогда не происходит. Возьмем, к примеру, Linux, который использует избыточное выделение памяти, где выделение почти никогда не дает сбоев, даже если виртуальная память избыточна. Это связано с тем, что немногие приложения используют все страницы выделенной им памяти или используют их таким образом, чтобы страницы можно было повторно использовать для других процессов, и поэтому ОС может жонглировать страницами между процессами для удовлетворения большинства случаев использования.
@ Мэт, я не понимаю, почему. Если значение указателя на выделенный объект не наблюдается снаружи, то malloc()
можно оптимизировать, предполагая, что он всегда выполняется успешно.
@Someprogrammerdude, я думаю, что на практике для программ пользовательского пространства, работающих в современных ОС, всегда можно предположить, что выделение меньше страницы памяти всегда выполняется успешно. Неудачный malloc()
просто не вернется.
Четыре причины проверить, что вернула функция malloc - pvs-studio.com/ru/blog/posts/cpp/0938
Спикер не означает, что компилятор может предположить, что malloc
никогда не возвращает нулевой указатель. Они означают, что в показанном конкретном случае компилятор может видеть, что делает код, и что его можно реализовать вообще без вызова malloc
и, что эквивалентно, его можно оптимизировать так, как если бы malloc
никогда не возвращал null в этой конкретной ситуации.
Стандарт C позволяет компилятору реализовывать код любым способом, обеспечивающим указанное наблюдаемое поведение, то есть из C 2018 5.1.2.3 6:
- Доступы к volatile-объектам оцениваются строго по правилам абстрактной машины.
- При завершении программы все данные, записываемые в файлы, должны быть идентичны тому результату, который был бы получен при выполнении программы в соответствии с абстрактной семантикой.
- Динамика ввода и вывода интерактивных устройств должна осуществляться, как указано в 7.21.3. Цель этих требований состоит в том, чтобы небуферизованный или линейно-буферизованный вывод появлялся как можно скорее, чтобы гарантировать, что сообщения с подсказками действительно появляются до того, как программа ожидает ввода.
Обратите внимание, что malloc
не является частью наблюдаемого поведения. Он находится внутри реализации C, что означает, что компилятору разрешено оптимизировать его поведение. Даже если существует отдельная библиотека, предоставляющая malloc
, компилятору разрешено рассматривать это как помощь в реализации C и оптимизировать использование malloc
в программе.
Спикер на самом деле явно говорит (около 1:04:19), что компилятор может предположить, что память может быть выделена, и поэтому malloc не вернет NULL. Что мне кажется неправильным.
@wohlstad: это утверждение сделано в контексте отображаемого кода, и это оптимизация, разрешенная стандартом C в этом контексте.
Насколько я видел, спикер не показал никакого кода, связанного с обработкой malloc, чтобы дать контекст, и не упомянул об этом.
@wohlstad: Код, показанный в видео в этот момент, — if ((a = malloc(1024)) == NULL) printf("We are out of memory!\n"); else free(a);
. Тот факт, что физическому компьютеру в этот момент может не хватать памяти, не имеет значения; стандарт C определяет C внутри абстрактной машины. Ограничения по окружающей среде разрешены стандартом C, но не являются обязательными. Допустимо разработать компилятор для абстрактной машины, которая концептуально имеет память, доступную в этой точке кода, даже если в физической реализации ее нет. Поэтому компилятор может оптимизировать так, как будто malloc
работает.
@SupportUkraine: Пока malloc
работает, что может быть правдой внутри абстрактной машины (см. Комментарий выше), вызов printf
is никогда не выполняется, поэтому его можно оптимизировать.
@EricPostpischil Думаю, я понимаю. Вы имеете в виду, что это может быть поведение в конкретном теоретическом компиляторе (т.е. этот компилятор не будет нарушать стандарт C), а не то, что вы ожидаете, что «обычные»/обычные компиляторы будут делать это, верно?
Вызов malloc()
может возвращать NULL
далеко за пределы контроля компилятора или реализации библиотеки, если только эта реализация библиотеки сама не оптимизировала системный вызов из своего malloc()
и не сообщила компилятору, что сделала это. Механика, как это было бы возможно в рамках стандарта, на данный момент ускользает от меня. В любом случае, в общем случае это не может быть действительной оптимизацией, потому что общий случай таков, что malloc()
вполне может вернуть NULL
.
Спасибо всем за обсуждение. Это очень полезно и интересно!
@wohlstad: Да.
@SupportUkraine: компилятору просто никогда не требуется вызывать внешнюю процедуру malloc
. Что стандарт C требует от реализации C, так это предоставление программе функций управления памятью, указанных в C 2018 7.22.3. Если компилятор считает: «Эй, я могу дать вам нужную память без вызова внешнего malloc
», это действительная оптимизация. Представьте, что программа выполняется внутри абстрактной машины, как это определено в стандарте C. Внутри этой абстрактной машины программа вызывает malloc
, получает указатель на 1024 байта,…
… видит, что указатель не нулевой (поскольку эта абстрактная машина всегда имеет доступную память в этой точке исходного кода), и вызывает free
. Таким образом, стандарт C полностью разрешает программе вести себя таким образом. Как бы вы реализовали такое поведение абстрактной машины? Вам не нужно вызывать внешний malloc
на физической машине, чтобы сделать это. Мы знаем, как ведет себя программа: этот конкретный код не имеет наблюдаемых побочных эффектов. Так что мы можем реализовать это, ничего не делая, в том числе не вызывая внешний malloc
.
@SupportUkraine: Clang делает эту оптимизацию.
Не означает, что это правильное поведение в общем случае.
Итак, чтобы прояснить это. Я мог бы использовать собственную реализацию библиотечных функций, включая malloc()
. Я мог бы, предположительно, реализовать void * malloc( size_t size ) { return NULL; }
, что соответствовало бы букве стандарта. И вы говорите, что компилятор может игнорировать это и оптимизировать это printf()
. Я нахожу это... смелым.
@DevSolar: О «правильном поведении в общем случае»: никто не утверждает, что malloc
можно оптимизировать в «общем случае». Насчет того, что «компилятор может игнорировать это»: это довольно просто, внешняя библиотека не является реализацией C. Это помощь реализации C. Компилятор, библиотека, оборудование и операционная система вместе составляют реализацию C. Компилятор может использовать их по своему усмотрению, пока он реализует абстрактную семантику, заданную стандартом C...
… Например, процедуры ввода-вывода и преобразования не требуются для преобразования чисел с плавающей запятой с полностью правильным округлением, но компилятор C может обеспечить свои собственные преобразования с правильным округлением, игнорируя внешние процедуры, и результатом все равно будет C реализация, соответствующая стандарту C. Или, с другой точки зрения, если ссылка на совершенно другую соответствующую реализацию стандартной библиотеки C, а не на системную по умолчанию, это все равно будет соответствующей реализацией C, потому что нет никаких обязательств использовать какую-либо конкретную реализацию…
… Неважно, находите вы это смелым или нет; ответственность реализации C заключается в реализации семантики, определенной абстрактной машиной, а не в механическом вызове внешней библиотеки. Если вы считаете, что оптимизация вызова malloc
не разрешена стандартом C, то не говорите нам, что считаете это жирным шрифтом, сообщите нам, какой параграф стандарта запрещает это.
Давайте просто согласимся не согласиться. Я считаю эту интерпретацию стандарта крайне ошибочной.
Давайте продолжим обсуждение в чате.
@DevSolar: Re «Давайте просто согласимся не соглашаться»: нет, это определяется стандартом C. Стандарт требует, чтобы наблюдаемое поведение программы было таким, как если бы она выполнялась на абстрактной машине, и не требует, чтобы malloc
в абстрактной машине реализовывался путем вызова какой-либо конкретной malloc
реализации. Наблюдаемое поведение — это доступ к изменчивым объектам, данные, записываемые в файлы при завершении программы, а также динамика ввода и вывода интерактивных устройств.
Это неправда, по крайней мере, в общем смысле. Возможно, существуют определенные среды, в которых malloc гарантированно сработает, но я с ними не знаком.