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

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

Только это не значит, что деды завещали так делать.

Понятно всё с вами, внучек. Читать и понимать не обучены, но похамить — это как пить дать. Где было у меня, что так завещали? Было сказано, что так было, теперь нельзя с объяснением причин, которых вы даже прочитать не в состоянии, не то чт\бы понять и вести осмысленную и нормальную беседу.

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

Список может увеличиваться:

void *a = malloc(...);
void *b = malloc(...);
void *c = malloc(...);
...
free(b);

Допустим, конфигурация кучи такая, что a, b и c выделены последовательно. Теперь при освобождении b невозможно склеить ни со свободным блоком до него, ни после (потому что там вплотную a и c). Как следствие, образуется новый свободный блок.

Как я уже сказал, в моей реализации malloc имеет оверхед лишь sizeof(size_t), храня только размер блока, связанного списка выделенных блоков нет, только свободных. А вот free дописывает ещё указатель на следующий/предыдущий свободный блок перезаписывая первые байты данных в блоке.

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

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

Как следствие, образуется новый свободный блок.

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

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

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

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

У меня в структуре не было флага занятости. Он не особо то нужен. Просто в списке в принципе были только свободные блоки. Если блок есть в списке - значит он свободный. Занятые блоки тупо неизвестны менеджеру памяти, ему нет никакой нужды их учитывать, если мы не реализуем сантитайзер аллокаций.

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

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

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

Чувак, ты клинический идиот? Причем здесь mmap. Ты основ работы с памятью не понимаешь. Бросаешься словами, смысл которых от тебя ускользает. И хуже, что таких как ты много.

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

В C++ вон ввели ради благих намерений uniform initialization syntax. Теперь хрен поймешь лучше стало или совсем наоборот :(

Из C++ удалять надо вещи, а не добавлять их. Но если удалить из C++ всё лишнее, получится Rust.

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

Чувак, ты клинический идиот?

Тут половина треда таких, что тебя удивляет? Ты вроде не первый год на ЛОРе. Даже модератором, кажется, был.

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

Я не знаю. Бьюсь головой об стену от таких. Где-то что-то услышат и опа, им кажется, что они уже знают. А на самом деле все в кучу смешают и довольны.

Mmap действительно может оказывать некоторое влияние. Но я хотел услышать от него механизм этого влияния.

И без ммап тоже ничего хорошего не будет. Влияние ммап не такое большое.

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

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

Так учет же автоматически происходит, чтобы потом free отработало — структура всё равно у вас аллоцируется всегда :) Потому если блок свободный появляется один, то он и есть корень структур, а если брать память для последующего добавления в только в структуру для поиска освобожденного из предоставленной памяти для пользователя, то тогда p=malloc(1); free(p) у вас не даст возврата памяти вообще, так как вся память у вас уйдет только на расширение структуры :)

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

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

Потому что надо не записки теоретиков читать а поведение компиляторов. Значение указателя после free не меняется и им можно пользоваться безо всяких UB. Например, вывести его через printf %p.

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

Нет, это авторы стандарта посмотрели реализацию компиляторов и задокументировали, как могли. Стандарт в Си - не директива, а констатация имеющихся фактов. Если вдруг расходится, то виноват стандарт, а не факты.

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

malloc округляет выделение памяти, конечно же. И так было даже во времена DOS. 1 байт ни один менеджер памяти тебе не даст, а под капотом выделит чуть больше.

Но!

Допустим, размер указателя 32 бита. Мой malloc при запросе 1-4 байт реально выделит 8 байт (4 байта размер блока + 1-4 байта полезных данных + 0-3 байта округление). Если связанный список свободных блоков однонаправленный, то 8 байт вполне хватит для хранения свободного элемента в списке при освобождении (размер не трогаем, перезаписываем «1-4 байта полезных данных» указателем на следующий блок).

А если перед занятым блоком держать место для полной структуры свободного, то выделение 1-4 байт приведёт к реальному выделению 12 байт (8 байт заголовок + 1-4 байт полезных данных, размер полезных данных округляется до 4 байт из-за выравнивания на 32-битной системе).

Итого, на маленьких аллокациях мой malloc ощутимо экономит память.

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

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

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

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

Нет, не путаю. Это ты путаешь со всякими растопитонами, где разработкой руководят теоретики со стандартами. В Си всегда первична практика была, а уж потом можно и задокументировать аккуратно. POSIX такой же, да, но не он один.

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

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

А потом memcpy() начинает копировать в другую сторону, и половина лялекса ломается. Ты бредишь. Ну либо в твоих фантазиях какой-то особый C с единорогами и радугой.

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

Нет, не путаю. Это ты путаешь со всякими растопитонами, где разработкой руководят теоретики со стандартами. В Си всегда первична практика была, а уж потом можно и задокументировать аккуратно. POSIX такой же, да, но не он один.

Стандарты. В Rust’е :D

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

С memcpy (то что ему вдруг сменили логику работы) это диверсия была, да. До сих пор осуждаю. Вот что бывает когда некомпетентные графоманы дорываются до принятия решений.

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

Но если удалить из C++ всё лишнее, получится Rust.

Не-не-не, если из C++ удалить все лишнее (т.е. наследие чистой Сишечки), то получится недо-Simula.

А вот ржавый можно получить только скрестив Си с OCaml-ом, причем изрядно закинувшись особо убойными веществами и в извращенной форме.

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

debian bullseye

#include <stdio.h>
#include <stdlib.h>

int main(void) {
  char *p;
  p = malloc(1);
  free(p);
  printf("%p", p);
  return 0;
}

Прекрасно компилируется.

И даже так компилируется:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
  char *p;
  free(p);
  printf("%p", p);
  return 0;
}
Хотя этот код уже плохой, да.

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

malloc округляет выделение памяти, конечно же. И так было даже во времена DOS. 1 байт ни один менеджер памяти тебе не даст, а под капотом выделит чуть больше.

Не опускайтесь и вы в детский сад, неужели вы думали, что это для меня было б откровением? Ясен пень, что это было только ради наглядности, чтобы не писать что-то дурацкое из серии malloc(минимум около sizeof(MALLOC_LIST)).

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

Ну а я о чем говорю? Вот у вас и есть уже эта структура «учета», осталось найти память под его элемента в списке. Какую память под это использовать? Пользовательскую? Тогда для выравненной малой памяти - вся память и уйдёт.

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

Прекрасно компилируется.

https://packages.debian.org/bullseye/gcc

gcc 10.2

Это даже хорошо, вот обновится Debian и gcc до 12.2, и получишь ошибку компиляции.

https://gcc.godbolt.org/z/ar8Pr7T1P

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

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

Любая стандартная функция имеет быть право реализована макросом. Это явно разрешено, и по факту некоторые так и сделаны.

Значит, рассчитывать на то что free() не поменяет свой аргумент нельзя.

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

Значит, рассчитывать на то что free() не поменяет свой аргумент нельзя.

Просто замечательно. Значить мы имеем что? Что любая foo(arg) имеет право поменять значение arg ! А теперь живите с этим и перепишите ВСЁ!

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

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

Я вам очень детально описал алгоритм и структуры данных аллокатора, в котором free портит первые байты освобождённого блока памяти, а также почему это может давать выгоду при маленьких аллокациях. Если вы не способны понять этот алгоритм, то я очень сомневаюсь в вашем профессионализме и удивительно, что вы жалуетесь на то, что я вам объясняю про округление. Перечитайте моё описание алгоритма и попробуйте ещё раз осознать его, я не буду его разжёвывать его ещё 10 раз.

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

Сишные программисты узнают про -Wall:

$ gcc -Wall -Werror fail.c
fail.c: In function ‘main’:
fail.c:8:3: error: pointer ‘p’ used after ‘free’ [-Werror=use-after-free]
    8 |   printf("%p", p);
      |   ^~~~~~~~~~~~~~~
fail.c:7:3: note: call to ‘free’ here
    7 |   free(p);
      |   ^~~~~~~
cc1: all warnings being treated as errors
cumvillain
()
Ответ на: комментарий от KivApple

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

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

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

Она занимается самим менеджером памяти. Просто в одном и том же участке памяти по очереди хранятся то пользовательские данные (после malloc), то данные аллокатора памяти (после free). Противоречие разрешается тем, что при отдаче этого блока памяти пользователю структура данных аллокатора, которая там лежала, мгновенно становится ненужной (потому что мы не ведём учёт занятых блоков, только свободных), поэтому её можно отдать на перезапись пользователю, а значит она вполне себе свободная (т. е. может быть выделена malloc и использована пользователем, единственное ограничение - мы не можем выделять меньше размера этой структуры, иначе у free потом будет проблема куда записать свои данные). Соответственно, при free уже пользовательские данные перезаписываются аллокатором (поэтому пользовательскому коду не стоит полагаться на доступ к этой памяти после free - запись разрушит структуры данных аллокатора, а чтение вернёт мусор).

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

Не, но никто тебе не гарантирует, что free() это не compiler builtin, который может делать ваще все что хочешь.

Покажите другую compiler builtin, которая портит значение переданного параметра. И ведь самое главное то что? Если бы в языке создать free_and_null() было б проблематично, то такая языковая контсрукция в стандарте языка была б оправдана, как ранее и сказано за счёт уменьшения производительности (а кого сейчас это волнует, к сожалению). Но ведь хотят то что? Чтобы просто в реалтайме при обращении к NULL падало у тех, кто коряво пишет! А если он пишет коряво, то может он и в других местах ещё худшее нагородил?

vodz ★★★★★
()

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

а также SUPER_SAFE_FREE (где будет предварительная проверка на NULL)

SUPER_PUPER_SAFE_FREE (с проверкой на каноничность адреса)

ULTRA_SAFE_FREE (с проверкой на принадлежность к пулам)

а потом будут выспрашивать отчего это всё так тормозит :-) Зато почти безопасно

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

Вообще, не обязательно. Вроде как jemalloc (используется по умолчанию как минимум во FreeBSD) сначала пытается взаимодействовать с thread-local пулом памяти, а мьютекс захватит только если его не хватит.

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

Я знаю про -Wall только не вижу зачем его использовать, да ещё и с -Werror. У меня такие настройки:

WARNOPTS="-Wreturn-type -Wpointer-sign -Wsign-compare -Wshadow -Wpointer-arith -Wimplicit -Wformat -Werror -Wno-parentheses -Wuninitialized"

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

jemalloc (давно) и glibc (кажется с 2017) умеют в per-cpu кеши, да. А вот musl, нежно любимый за нормальную статическую линковку, любит хватать лок для сложных аллокаций (ЕМНИП для всего, что пересекает у него два блока аллокации).

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

Перестань нести чушь. -Wall годится только чтобы написать команду компиляции по-быстрому там, где она требуется разово и лень писать сборку по-нормальному. Для регулярного использования оно не подходит.

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

Перестань нести чушь. -Wall годится только чтобы написать команду компиляции по-быстрому там, где она требуется разово и лень писать сборку по-нормальному. Для регулярного использования оно не подходит.

Ну если такую дичь писать, то конечно не подходит.

cumvillain
()