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 – это язык культа страданий во имя страданий.

по логике - да, что может пойти не так от распечатки значения освобожденного указателя? Но на практике к сожалению надо иметь дело не с логикой, а с тем, что там намутили разработчики gcc. А зная, на что способны эти товарищи, я вообще не удивлюсь если они специально вставили какой-нибудь код, делающий этот кейс настоящим UB. И ответ на багрепорт: «ну мы же вам показали варнинг? Да и в стандарте написано».

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

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

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

по логике - да, что может пойти не так от распечатки значения освобожденного указателя?

Что может пойти не так от передачи невалидного указателя в стороннюю функцию? Нууууу… каааааааак тебе объяснить?

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

Пусть адрес 4 байта, структура занятого блока 4 байта (только размер), структура свободного блока 8 байт (размер и указатель на следующий свободный блок).

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

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

Зачем пузырек? Я поддерживал упорядоченность свободных блоков по базовым адресам. Таким образом free должен был просто пробежаться по списку свободных блоков, пока не найдёт нужное место для вставки, а затем проверить ровно два кандидата на слияние - предыдущий и следующий свободный блок. Никакого O(N^2), обычный O(N) для худшего случая. Malloc же брал первый попавшийся блок подходящего размера, опять же O(N) в худшем случае, на практике если куча не совсем фрагментирована, то гораздо быстрее (обычно в начале кучи был огромный блок, который никак не мог кончиться, поэтому поиск на нём оканчивался).

O(N) вполне приемлемо для базового аллокатора. Конечно, дальше можно наворачивать оптимизации типа поддержания разных списков свободных блоков для разных размеров (slab allocator) или сделать дерево вместо линейного списка (для уменьшения сложности free с O(N) до O(logN), не знаю используется ли это где-то, просто первое что пришло в голову), но для игрушечной ОС это избыточно. И такие менеджеры точно так же могут часть своих структур хранить в освобождённый памяти.

Мой пойнт в том, что free может портить данные в освобожденной памяти даже в очень триаиальной реализации аллокатора (и раз я до неё сходу догадался в 17 лет и она даёт выигрыш по памяти, то деды 30 лет назад тоже должны были). Так что без всяких mmap обращаться по освобожденному адресу плохая идея. Там может быть какая-то структура учёта свободных блоков вместо твоих данных.

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

Тем не менее, многие фишки C растут ногами оттуда. Строки с нулём в конце, например. В PDP-11 была почти бесплатная проверка на нулевой байт, поэтому их и запилили.

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

Мой пойнт в том, что free может портить данные в освобожденной памяти даже в очень триаиальной реализации аллокатора. Так что без всяких mmap обращаться по освобожденному адресу плохая идея.

Я пытался уже его к этому подвести. Там виляние хвостом и ничего более. Механики работы mmap я тоже не дождался. И как mmap может забирать виртуальные адреса уже выделенных приложению страниц памяти, мне тоже не объяснили.

Вообще, классное у тебя терпение.

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

Не, ну понятно освободить страницу памяти и вернуть его ядру. Но, чтоб переназначить внутренние адреса страницы на другую страницу… гм… опасненько вообще-то.

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

Я просто вёл один семестр системного программирования у французских студентов :-) Но они были сообразительнее в среднем.

Касательно mmap я знаю, что многие аллокаторы куски памяти больше какого-то порога округляют до размера страницы и напрямую запрашивают у ОС через mmap. Нет смысла искать гигабайтный блок в пуле. Соответственно, такой блок при освобождении будет освобожден через munmap и это хорошо, потому что чем быстрее вернётся такой огромный кусок памяти ОС, тем лучше.

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

Но в любом случае гораздо выше сегфолта шанс наткнуться на какие-то структуры аллокатора. И этот шанс был ещё выше во времена малых объёмов ОЗУ, потому что хранить отдельно то что можно хранить в свободной памяти - очень расточительно. Поэтому я не верю, что хоть сколько-нибудь адекватные разработчики осознанно использовали память после free даже 30 лет назад.

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

обычно в начале кучи был огромный блок, который никак не мог кончиться, поэтому поиск на нём оканчивался).

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

обращаться по освобожденному адресу плохая идея.

Ok, кажется я начинаю припоминать как там рекомендовали. Надо было начинать писать пользовательские данные со смещения от выданного malloc и гарантировалось, что оно не более sizeof(char *).

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

Смотрю, тут олелукое сдулся. Можно я за него постою.

Что такое указатель?

Чем отличается «взять значение указателя» и «разъименовать указатель»?

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

Так то да, но наихудшая стратегия

Не наихудшая. Какие альтернативы? Поиск блока наиболее подходящего размера приводит к появлению большого количества маленьких огрызков блоков, которые невостребованы (malloc(1) не самая распространеная команда). Тоже не идеал. И что самое главное - меняется только условие выхода из цикла поиска в malloc, структура данных вообще не меняется.

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

Лучше будут только более навороченные алгоритмы типа slab allocator, но они точно так же могут хранить часть данных в свободной памяти.

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

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

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

Ага. То, что C придуман инопланетными наркоманами, даже не обсуждается. Это чистая правда, и более ублюдочных языков довольно мало. Разве что какой-нибудь пердл. Тем не менее, за передачу невалидного указателя в стороннюю функцию нужно бить с разбега по яйцам в любом случае.

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

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

в расчете на то, что их прога будет востребована через 30 лет?

очень много программ тогда, да и сейчас, писалось и пишется на «вот щас пабырому ляп-тяп, вот то поднимем - потом это перепишем».

а потом - нет ничего более постоянного, чем временное (с).

так что использовали. не вслепую, конечно, понимая механику того, как и что там работает «под капотом», как некий аналог defer. зааллоцировал, тут же освободил и обращайся к данным сколько влезет ) переносимость кода приблизительно 0, but who cares?

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

Строки с нулём в конце, например.

Конечно, конечно, надо было как в int 21 ah=09 юзать '$', правда же как удобнее...

В PDP-11 была почти бесплатная проверка на нулевой байт

Вы так говорите, как-будто это что-то плохое. Скорее отсуствие отдельной короткой инструкции на проверку с нулем (особенно слова, а не байта, с байтами на PDP как раз было так себе) - назло папе отморожу уши.

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

И получить ограничение в 255 символов. В целом решение, конечно, спорное, но интересно, что оно оказалось бесконечно месштабируемым, что до сих пор используется, когда памяти стало гораздо больше и строки длиннее.

На маленьких строках минимальный оверхед (в отличии от использования size_t длины), но при этом хоть как-то будут работать без смены формата даже гигабайтные строки, о которых и думать не могли авторы формата.

Так что своя гениальность тут есть.

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

А еще есть деструктивные парсеры, расставляющие в строке с документом \0 в нужных местах, получая таким образом нативное разбиение документа на подстроки без каких-либо аллокаций и копирований.

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

Поиск блока наиболее подходящего размера приводит к появлению большого количества маленьких огрызков блоков

Во-первых, можно для начала было убедиться, что оно равно по размеру, во-вторых, помещать ли в список свободных огрызки - тоже опция стратегии.

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

На что только не пойдёшь, когда у тебя 64k, ну реально же было! И совершенно при выполнеии рекомендаций - работало, код становился короче и быстрее, понятнее и более обозримый для того, чтобы не ошибиться, так как без пачки goto на разные метки в зависимости от текущей ситуации куда вывалились.

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

взять значение указателя

ага.

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

так что я изучаю вопрос, но вы тут пишите-пишите )

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

И получить ограничение в 255 символов.

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

В Паскале и потомках типа Ады так в общем и сделано. И ничо, никто не умер, производительность тоже не пострадала. Даже наоборот, не надо strlen() на каждый чих по тыще раз дёргать.

На маленьких строках минимальный оверхед (в отличии от использования size_t длины), но при этом хоть как-то будут работать без смены формата даже гигабайтные строки, о которых и думать не могли авторы формата.

Чо? Формат можно менять хоть 100500 раз в день, потому что это всё существует только в памяти. Если ты на диск пишешь строки с нулём в конце, то становись в очередь на удар по яйцам, потому что так делать ну вообще нельзя.

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

А чего ужас-то? Что строка не константная? Ну так это не джава какая-нибудь, в сишке все заточено на то, что строки могут меняться.

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

Тогда пользовательский код не сможет использовать эти строки как обычные сишные.

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

У меня есть гипотеза, что раньше менеджеры памяти гораздо чаще хранили свои структуры в освобожденной памяти (и какое там условие выхода из цикла поиска malloc и сколько именно иметь списков - вообще не важно), потому что как раз на что только не пойдёшь, когда у тебя 64 КБ ОЗУ. Как бы оптимизация начинается с менеджера памяти гораздо раньше, чем доходит до пользовательского кода. То есть код обращающийся к free памяти был бы нерабочим на многих достаточно оптимизированных по памяти аллокаторах.

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

Удобнее было бы потратить этот байт на хранение длины строки.

Строки до 256 байт даже на 8-битных компах были ущербные. А на современных компах, когда вам запросто выравнивают начало строки чуть ли не по границе страницы, если захотите производительности, иметь вначале по выравненому адресу длинну, а не после данных следующим полем структуры - вся эта машинерия по сравнению по большим словам полетит к черту. Ну и накой оно надо?

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

Для примера, парсер pugixml расставляет нули на месте кавычек и угловых скобок, где заканчиваются соответствующие подстроки. Называть это «порчей» у меня язык не повернется - это, скорее, облагораживание.

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

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

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

Обычно парсят то что пришло извне (от юзера, из файла, по сети) и оно не в rodata. Либо используется другой режим или даже другая библиотека парсинга. Но парсинг изменяемой строки, наверное, самое частое.

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

Говорю же, сишники не могут представить себе, что можно иметь больше одного вида строк.

В C есть 5 видов встроенных строк:

"hello";   // const char[6]
L"hello";  // const wchar_t[6]
u8"hello"; // const char8_t[6]
u"hello";  // const char16_t[6]
U"hello";  // const char32_t[6]
fsb4000 ★★★★★
()
Ответ на: комментарий от KivApple

и сколько именно иметь списков - вообще не важно

Понял, в чём у нас камень преткновения. Документирование malloc было очень подробное, так как от этого зависило всё — скорость, возможность получения жалких остатков памяти и прочих мельчайших тонкостей, начиная в какой последовательности лучше вызвать free у данной реализации в libc до вот как можно, но не желательно вначале его вызвать, а потом спокойно работать. Сырцы malloc - давали, даже MS у Xenix-а давало, правда грозно предупреждали, что смотреть можно, а копировать хрен и мы вас засудим как Линуса за идентичный коментарий от туда.

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

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

именно значение самого указателя?

не данных по адресу?

Я даже представить себе не могу такую архитектуру. Хотя нет, это же обычный дебаг - обратились по адресу (не важно что там, указатель или просто константа), сработало прерывание, а там можно и сегфолт перевызвать. Но семантически это не относится к работе с указателем, дебаг не в курсе, как интерпретировать адрес в памяти на который повесили хук.

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

даже MS у Xenix-а давало

Исходники UCRT устанавливаются вместе с установкой Visual Studio. Правда лицензия не свободная, но всё видно, и malloc, и free.

Путь куда устанавливается: C:\Program Files (x86)\Windows Kits\10\Source\10.0.22621.0\ucrt

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

MS не давала исходники по секрету кому-то под страхом суда,

Может читать внимательнее надо? Как раз для malloc - давала, впрочем там был (c) SCO, а Линуса таки засудили.

vodz ★★★★★
()