LINUX.ORG.RU

Навеяно свежей дырой в Xorg

 , ,


9

7

Привет, ЛОР!

Ты, наверное, уже видел свежую дыру в Xorg, патч для которой выглядит буквально вот так:

-        else
+        else {
             free(to->button->xkb_acts);
+            to->button->xkb_acts = NULL;
+        }

В связи с этим у меня возник вопрос: а почему в стандартной библиотеке C нет макроса SAFE_FREE()?

#define SAFE_FREE(ptr) do{free(ptr);(ptr)=NULL;}while(0)

Напомню, что значение указателя после вызова free() является неопределённым согласно стандарту. Не только значение памяти, на которое он указывает, но и значение самого указателя, и работа с ним представляет собой жуткое undefined behaviour, а значит единственное что можно сделать – занулить его.

Так вот, почему даже таких банальных вещей нет? Я уже не говорю про строковый тип, а то даже Эдичка тут строки не осилил.

Моя гипотеза тут: C – это язык культа страданий во имя страданий.

Ответ на: комментарий от alysnix

а каков смысл этого?

Компилятор глупый. Он не понимает твоей задумки, он просто проследил за потоком исполнения и обнаружил, что p и q указывают на разные объекты (ну, если он знает что malloc выделяет новую память, если для него это просто функция то он может предположить, что она вернула одно и то же значение дважды, но это можно изменить заменив на malloc на new). Почему так получилось - не его дело, может макрос препроцессорный или шаблон/инлайн функции так развернулся. Если хочешь оптимизации, компилятор должен удалять бессмысленные проверки и недостижимый код.

khrundel ★★★★
()
Ответ на: комментарий от khrundel

что p и q указывают на разные объекты

какие еще «разные обьекты», это вообще void* и указывает на нетипизированную память. просто кусок физической памяти, а не «разные обьекты».

опять же по стандарту malloc может вернуть NULL и значения обоих вызовов будут одинаковыми.

однако разрабы llvm утверждают, что их malloc особенен и NULL вернуть не может. в этом месте зарыдали все эмбедщики у которых мало физ памяти и виртуалить им некуда.

alysnix ★★★
()
Ответ на: комментарий от alysnix

какие еще «разные обьекты», это вообще void* и указывает на нетипизированную память. просто кусок физической памяти, а не «разные обьекты».

object
region of data storage in the execution environment…

utf8nowhere ★★★
()
Последнее исправление: utf8nowhere (всего исправлений: 1)
Ответ на: комментарий от alysnix

однако разрабы llvm утверждают, что их malloc особенен и NULL вернуть не может. в этом месте зарыдали все эмбедщики у которых мало физ памяти и виртуалить им некуда.

Там написано, что в случае с -ffreestanding этого поведения нет.

hateyoufeel ★★★★★
() автор топика
Ответ на: комментарий от alysnix

однако разрабы llvm утверждают, что их malloc особенен и NULL вернуть не может. в этом месте зарыдали все эмбедщики у которых мало физ памяти и виртуалить им некуда.

Если указатель проверяется на NULL и разыменовывается, тогда используется malloc из libc. Если разыменования нет, то используется специальная версия, которая никогда не возвращает NULL и не потребляет память (разыменования нет, зачем её тратить).

monk ★★★★★
()
Ответ на: комментарий от alysnix

какие еще «разные обьекты», это вообще void* и указывает на нетипизированную память.

Разные объекты по разным адресам.

однако разрабы llvm утверждают, что их malloc особенен и NULL вернуть не может.

Это старая проблема в реале вроде так и есть, маллок всегда вернёт адрес, а дальше упадёт по SIGBUS при обращении к ней.

В общем, я не в курсе как у них это реализовано, может для if (p == NULL) делают где-то исключение, но в общем и целом оптимизирующий компилятор делает много предположений о коде и UB ему в этом помощник. И если он считает, что malloc выделяет свободную память, он имеет право предполагать, что эта память не будет совпадать с другой. Вообще, гипотетически можно представить какой-нибудь бэкенд для компилятора, в котором malloc возвращает массив в какой-нибудь виртуальной машине, а указатель будет указателем на буфер + индекс в нём. Или даже ещё проще, реальный режим x86 или protected в 286, указатель представляет собой пару сегмент+смещение. В таком случае UB при сравнении/арифметике указателей, полученных из разных мест оправдан, причём если речь идёт о 286, то принципиально нет никакого способа определить, что 2 разных смещения при 2х разных селекторах не указывают на один и тот же объект.

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

khrundel ★★★★
()
Ответ на: комментарий от khrundel

И если он считает, что malloc выделяет свободную память, он имеет право предполагать, что эта память не будет совпадать с другой

Уточненение: он это в любом случае знает благодаря attribute((malloc)).

unsigned ★★★★
()
Ответ на: комментарий от alysnix

опять же по стандарту malloc может вернуть NULL и значения обоих вызовов будут одинаковыми.

А может и не вернуть, и тогда будут разными.

однако разрабы llvm утверждают, что их malloc особенен и NULL вернуть не может

И что не так? «может» ≠ «должен». По-твоему, чтобы соответствовать стандарту, надо хоть раз за время выполнения программы вернуть NULL?

utf8nowhere ★★★
()
Последнее исправление: utf8nowhere (всего исправлений: 1)

Саттер пост выложил по случаю принятия С++23

https://herbsutter.com/2023/02/13/c23-pandemic-edition-is-complete-trip-report-winter-iso-c-standards-meeting-issaquah-wa-usa/

Пишет что в комитете появилась новая подгруппа.

The newly-created SG23 Safety and Security subgroup met on Thursday for a well-attended session on hitting the ground running for making a targeted improvement in safety and security in C++, including that it approved the first two safety papers to progress to review next meeting by the full language evolution group.

Вот вроде первая их бумага: https://isocpp.org/files/papers/D2795R0.html

fsb4000 ★★★★★
()
Последнее исправление: fsb4000 (всего исправлений: 1)
Ответ на: комментарий от fsb4000

Да, я видел. Но там очень малая часть. Плюс, вот это что за хрень?

Do you really mean that there can never be any UB in any correct code? There is of course always room for nuance and detail. If a particular construction is known to be UB, but still appropriate on some platform or under some additional assumptions, it is perfectly fine to use it. It should be documented/annotated sufficiently, and perhaps tools that detect UB need to be informed that the construction is intentional.

Типа, UB можно если очень хочется, но мы всё равно ничего не будем гарантировать? Нахрен такое?

hateyoufeel ★★★★★
() автор топика
Последнее исправление: hateyoufeel (всего исправлений: 1)
Ответ на: комментарий от fsb4000

Ну я ставлю бутылку вина, что ничего кардинально за 3 года не изменится.

Не в последнюю очередь тут можно винить сишную культуру «хренак-хренак-и-похреначили». Вот как выше в треде разные регистранты ныли, что им компилятор код ломает.

hateyoufeel ★★★★★
() автор топика
Последнее исправление: hateyoufeel (всего исправлений: 1)
Ответ на: комментарий от hateyoufeel

Типа, UB можно если очень хочется, но мы всё равно ничего не будем гарантировать? Нахрен такое?

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

Например, разыменование NULL это UB, но на MS DOS вполне корректный адрес нулевого прерывания.

monk ★★★★★
()
Ответ на: комментарий от monk

UB можно (и всегда было можно) на конкретной версии конкретного компилятора,

Нет, нельзя.

Например, разыменование NULL это UB, но на MS DOS вполне корректный адрес нулевого прерывания.

Значит, эта реализация не соответствует стандарту. Что не удивительно, потому что DOS родился задолго до стандарта C и тем более C++. А к моменту их появления он уже дохнуть начал.

hateyoufeel ★★★★★
() автор топика
Ответ на: комментарий от hateyoufeel

Значит, эта реализация не соответствует стандарту.

Соответствует. Она делает на UB произвольное поведение. В конкретном компиляторе пишет/читает нулевой адрес.

UB на то и UB, что компилятор при нём может делать что угодно. В том числе как-то осмысленно обрабатывать для конкретной платформы.

Также как в gcc всегда INT_MAX+1 == INT_MIN, несмотря на то, что это тоже UB.

monk ★★★★★
()
Ответ на: комментарий от monk

UB на то и UB, что компилятор при нём может делать что угодно. В том числе как-то осмысленно обрабатывать для конкретной платформы.

Ты путаешь undefined behaviour и unspecified behaviour. В первом случае, нет некаких гарантий на работу программы в принципе.

Также как в gcc всегда INT_MAX+1 == INT_MIN, несмотря на то, что это тоже UB.

Правда, что ли? Ещё скажи, что INT_MAX > INT_MIN тогда.

https://godbolt.org/z/TGKPhexWj

hateyoufeel ★★★★★
() автор топика
Последнее исправление: hateyoufeel (всего исправлений: 2)
Ответ на: комментарий от monk

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

+100500. Вот не в бровь, а в глаз просто. Одна из причин почему мы очень (и я действительно имею в виду - «очень») консервативны в плане compiler upgrades. Нам не на стандарты передёргиватьшашечки, нам ехать ;)

ПыСы: на любителей всего «последнего-распоследнего» давно уже смотрю как на «guinea pigs»… Но, «каждому своё», наверное. Проф-деформация имеет место быть, однозначно :)

bugfixer ★★★★★
()
Ответ на: комментарий от hateyoufeel

В первом случае, нет некаких гарантий на работу программы в принципе.

Верно. Компилятор вправе в этом случае делать всё, что пожелает разработчик компилятора.

Правда, что ли? Ещё скажи, что INT_MAX > INT_MIN тогда.

n + 1 > n вторая аксиома. Так что иногда INT_MIN > INT_MAX.

В твоём же примере в printf n+1 ведь вычислился и вывелся. А на каком-то другом компиляторе мог бы упасть с ошибкой и это тоже было бы правильно.

monk ★★★★★
()
Ответ на: комментарий от hateyoufeel

Ты уж оперделись.

Зачем? При UB возможно всё, что угодно. А переполнение знакового целого даёт именно его.

И можешь добавить ключ -fwrapv и тогда оптимизатор будет тоже уверен, что INT_MAX+1 == INT_MIN < INT_MAX

monk ★★★★★
()
Ответ на: комментарий от hateyoufeel

Типа, UB можно если очень хочется, но мы всё равно ничего не будем гарантировать? Нахрен такое?

Даже в стандартной библиотеке достаточно много мест которые не соответствуют стандарту.

А пофиксить нельзя так как сломает ABI.

Кроме стандартной библиотеки и операционной системы, есть ещё и клиенты, которые тоже не могут что-то исправить по каким-то причинам.

Как пример, в MSVC STL, libc++, libstdc++ в качестве буфера для хранения Callable в std::function используют массив char.

А теперь так нельзя, нужно unsigned char: https://cplusplus.github.io/CWG/issues/2489.html

fsb4000 ★★★★★
()
Ответ на: комментарий от fsb4000

А пофиксить нельзя так как сломает ABI.

Может, пора? Раз в 20 лет-то можно, и с gcc 2.95 уже достаточно времени прошло.

Но вообще, со стандартами в C и C++ абсолютно шизофреничная ситуация выходит. Сишники носятся с ними как с писанной торбой, заявляя, что язык без стандарта – не язык. И те же сишники потом орут и воют, что стандарт писали наркоманы, и его надо игнорировать, потому что если писать всё по стандарту, нифига работать не будет.

hateyoufeel ★★★★★
() автор топика
Ответ на: комментарий от hateyoufeel

Может, пора?

В libc++ те кому не важно ABI, могут собирать сами указав в CMAKE LIBCXX_ABI_UNSTABLE: https://libcxx.llvm.org/DesignDocs/ABIVersioning.html и тогда в стандартной библиотеке будет большее соответствие стандарту С++.

В Microsoft пока нет другого ABI, но есть планы чтобы в Visual Studio 2025 был новый ABI, пока есть только список задач, которые нужно будет решить если руководство разрешит сломать ABI: https://github.com/microsoft/STL/issues?page=1&q=is%3Aissue+is%3Aopen+label%3AvNext

fsb4000 ★★★★★
()
Ответ на: комментарий от monk

То есть нельзя использовать значение указателя, ссылающегося на место, попавшее в free или realloc.

Вынужден признать что Вы таки правы: имел возможность пообщаться с одним из C++ committee members, и Ваша интерпретация этого параграфа это именно то что они имели в виду - нельзя pointer использовать после free(), от слова совсем. Более того - в плюсовом мире это же справедливо для delete. И более того - это же справедливо для случая когда pointer показывает куда-то на стек и это что-то gets destroyed. Мне это всё пока на голову не налезает, но мне (а) обещали показать внутреннюю переписку и документы «комитетчиков» проливающие свет на то как они «докатились до жизни такой» (если никто из комитета не будет против, и не факт что опубликовать смогу, но «своими словами» перескажу так точно), и (б) сказали что вроде как в gcc конкретно на этот UB подвязок пока нет, а вот в clang «уже» (какое счастье что мы clang для прод сборок не используем [пока]). В общем: снимаю шляпу - «век живи - век учись»…

bugfixer ★★★★★
()
Ответ на: комментарий от bugfixer

обещали показать внутреннюю переписку и документы «комитетчиков» проливающие свет на то как они «докатились до жизни такой»

Логика за этим решением понятна. Сразу после free/delete/realloc компилятор вправе переиспользовать место под указателем, экономя несколько байт на стеке. Возможно ещё какие-то оптимизации.

monk ★★★★★
()
Ответ на: комментарий от bugfixer

Более того - в плюсовом мире это же справедливо для delete.

В плюсовом мире invalid pointer value, и там всё, кроме применения * и удоления, implementation-defined.

И более того - это же справедливо для случая когда pointer показывает куда-то на стек и это что-то gets destroyed.

Destroyed недостаточно.

utf8nowhere ★★★
()
Последнее исправление: utf8nowhere (всего исправлений: 1)

Ему можно присвоить новое значение. Нулевое - лишь одно из них. Ptr = malloc(); free(Ptr); Ptr = malloc(); Дань эффективности. Типа. Хотя я сразу пишу некий freep: freep(&Ptr);

А на плюсах, вообще использовать передачу по ссылкам (не панацея). На последнем проекте вообще случайно узнал, что у меня на кучу (heap) не выделено памяти (embedded): память на все нужды статически нарезана была)

hatred ★★★
()
Ответ на: комментарий от khrundel

Это старая проблема в реале вроде так и есть, маллок всегда вернёт адрес, а дальше упадёт по SIGBUS при обращении к ней.

реальный режим x86 или protected в 286

Я бы очень посмотрел, как четыре malloc-а по 1G даже не на 286, а на 386 вернет там не NULL.

vodz ★★★★★
()
Ответ на: комментарий от vodz

Проблема в том, что C должен вменяемо работать и под досом, и под win16 и под 64битным линупсом. Так что множество гарантий программисту со стороны языка будут пересечением гарантий, которые можно обеспечить каждой из этих систем.

khrundel ★★★★
()
Ответ на: комментарий от khrundel

Проблема в том, что C должен вменяемо работать и под досом, и под win16 и под 64битным линупсом. Так что множество гарантий программисту со стороны языка будут пересечением гарантий, которые можно обеспечить каждой из этих систем.

Сказать то что хотели? В любой системе в 32-битном вы получите отлуп от системы, когда кончится адресное пространство в 4Г, есть еще задаваемые лимиты, включая такие сложные настройки в нормальных ОС как максимальное превышение ограничения на возможность получить mapping на размер оставшейся памяти+своп. А вот язык C как раз совсем не виноват, что вполне обычную функцию ради дурацкой оптимизации на спичках превратили чуть ли не во встроенную магию.

vodz ★★★★★
()
Ответ на: комментарий от vodz

Хотел сказать то что сказал: язык C проектировался как кроссплатформенный, поэтому поведение на одной платформе мало что значит. Язык гарантирует вот это, а вот это не гарантирует, в том числе компилятору позволено думать, что таких ситуаций не произойдёт.

На остальной бред отвечать не буду: если человек пишет чушь и сам тут же приводит опровержение, то приписка о том, что пример из опровержения кажется ему плохим решением ни о чём не говорит. По факту вот так есть и с этим «есть» придётся работать.

Что до вашей оценки правильности аллокации: снявши голову по волосам не плачут. Представьте, что в ведре бы сделали обязательное резервирование под аллокацию. Это было бы тупо, неоптимально, и по факту ничего бы не решило, так как первый же fork с последующей записью в память создал бы запрос на свободную страницу памяти, ничем не лучший чем «необеспеченный» аллок.

khrundel ★★★★
()
Ответ на: комментарий от khrundel

Хотел сказать то что сказал: Язык гарантирует вот это, а вот это не гарантирует, в том числе компилятору позволено думать, что таких ситуаций не произойдёт.

Да нихрена вы ничего не говорите, вообще ни о чём. Даже после того, когда вам дали конкретное возражение об абсолютном ограничении адресного пространства.

и по факту ничего бы не решило, так как первый же fork с последующей записью в память создал бы запрос на свободную страницу памяти, ничем не лучший чем «необеспеченный» аллок.

Нет. Это ровно таже ситуация с окончанием памяти в системе, которая может кончиться и без всякого fork. Единсвенная разница по errno=ENOMEM для fork, это если памяти не хватило для самого ядра при этом вызове.

vodz ★★★★★
()
Ответ на: комментарий от vodz

Да нихрена вы ничего не говорите, вообще ни о чём. Даже после того, когда вам дали конкретное возражение об абсолютном ограничении адресного пространства

Я же ответил: «бред поскипал». Я думал зачем мне писать возражение, если вы сами его ниже написали: ограничение памяти + свопа и/или квоты. Ограничение адресного пространства в условиях практически неограниченного объема физической памяти - это не то что бы невозможная ситуация, но редкая. Когда 32битный код был актуален никаких 4 гигов на типичной машине не стояло, да и общий объем всех жёстких дисков мог быть меньше 4 гигов, так что и своп бы не помог. Сейчас, при 64битах, имеем ту же ситуацию. Так какого хрена нам вообще рассматривать нереалистичным сценарий, при котором физическая память кончится позже адресного пространства процесса? Так что реалистичный сценарий именно такой: память будет выделена, а при попытке отмапить её на физическую программу прибьют сигналом.

khrundel ★★★★
()
Ответ на: комментарий от khrundel

Я же ответил: «бред поскипал».

Нормально, чо. Сами решили блеснуть историческими «познаниями» насчёт реального режима и прочих 286, а потом, быстро перебулись и в миг снеся с горизонта все многочисленные оставшиеся 32-битные системы. А теперь еще доказывает, что все эти лимиты - чушь собачья и нереалистичные сценарии.

Так что реалистичный сценарий именно такой: память будет выделена, а при попытке отмапить её на физическую программу прибьют сигналом.

Способ и причина прибития процесса никакого отношения к разговору о том, что возвращает malloc не имеет, это просто доказывает, что либо вы натурального не понимаете ни в предмете, ни в логике.

vodz ★★★★★
()
Ответ на: комментарий от vodz

Нормально, чо. Сами решили блеснуть историческими «познаниями» насчёт реального режима и прочих 286,

Ещё раз, для тупых: я описывал разные архитектуры с разными свойствами и указывал, что стандарт языка должен позволять эффективно работать со всеми ними. Только для этого и приводил примеры.

Я понимаю, что дебилу, которому показалось что он «поймал» очень сложно поверить в то, что он сморозил чушь, но это не отменяет реальности.

Ситуация, когда маллок вернёт nullptr, безусловно, возможна. И не только в случае, когда кончилось адресное пространство, можно тупо отрицательный размер передать. Но в реалистичном случае, если запросить объём памяти, которую система не может дать, то будет именно та ситуация, которую я описал. Причём это могли быть жалкие 50 мегабайт в середине 90х в 32битной программе, и 10 гигабайт сейчас, в 64 битах. К чему эти слёзы об обиженных мной 32битных системах я вообще не в курсе.

khrundel ★★★★
()
Ответ на: комментарий от khrundel

Всё ясно с вами. Малолетний дебил, открывший недавно для себя overcommit и COW, но даже это не состоянии донести ибо мышление не позволяет сформировать мысль, а не словесный понос ни о чём, но даже до сих пор не понимающий, что на 32 системах совершенно не важно сколько там после 4G еще есть, зато как хамить - так завсегда пожалуйста. Идите ка в игнор.

vodz ★★★★★
()
Ответ на: комментарий от khrundel

Ситуация, когда маллок вернёт nullptr, безусловно, возможна. И не только в случае, когда кончилось адресное пространство, можно тупо отрицательный размер передать. Но в реалистичном случае,…

вы там совершенно не учитываете фрагментации памяти. поскольку сишно-плюсовый хипменеджер неперемещающий, то для ЛЮБОЙ стратегии захвата памяти есть контрстратегия, с помощью которой можно память эффективно фрагментировать и не дать взять вполне себе небольшой кусок, при большом количестве свободной памяти.

и в этом случае хипменеджер возвратит null, хотя свободной памяти полно

alysnix ★★★
()
Ответ на: комментарий от alysnix

Не вижу разницы. Если «контрстратегия» подобрана так чтоб израсходовать адресное пространство - да, будет nullptr, но этого же эффекта можно добиться тупо выделяя большие куски. Если так, чтоб стандартная библиотека держала полупустые страницы - будет падение от израсходования физической памяти, но этого же эффекта можно добиться выделяя память и записывая что-то в страницы.

khrundel ★★★★
()
Ответ на: комментарий от hateyoufeel

Ну, из контекста обсуждения всякого UB логично предположить, что авторами стандарта. Впрочем, и K&R С хоть и во многом завязанный на PDP или для чего там его клепали, явно рассматривался как кроссплатформенный.

khrundel ★★★★
()
Ответ на: комментарий от khrundel

Ситуация, когда маллок вернёт nullptr, безусловно, возможна. И не только в случае, когда кончилось адресное пространство, можно тупо отрицательный размер передать.

Отрицательный размер в size_t? Что?

hateyoufeel ★★★★★
() автор топика
Ответ на: комментарий от khrundel

Ну, из контекста обсуждения всякого UB логично предположить, что авторами стандарта. Впрочем, и K&R С хоть и во многом завязанный на PDP или для чего там его клепали, явно рассматривался как кроссплатформенный.

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

hateyoufeel ★★★★★
() автор топика