LINUX.ORG.RU

Почему надо проверять malloc на NULL

 , ,


0

2

Вообще выделение памяти проверять.

А вот потому что:

#include <stdio.h>
int main()
{
  unsigned char *a = NULL;
  int b = 0;
  unsigned long n = (unsigned long)&b;
 
  printf("b = %d, n = %lu\n",b,n);
  a[n]=1;
  printf("b = %d\n",b);
  return 0;
}

Запуск

$ gcc nullptr.c -o nullptr
$ ./nullptr 
b = 0, n = 140731726099148
b = 1

Запускалось в 64-битном (x86_64) Linux. Как видно, никаких сегфолтов и прочих ошибок. Конечно, адрес в районе 127 Тб в примере далеко за пределами доступного почти на всех компьютерах, но нет никаких гарантий, что на какой-то системе с каким-то компилятором и настройками среды значение не окажется более доступным. Могут быть и другие архитектуры (32-битные например), если запускать от root'а, то в начало может быть разрешена запись и там иметься память процесса. Или ещё какие-то варианты.

shdown, monk, liksys, Xenius - я думаю вам понравится. Пример сочинился по ходу чтения обсуждения Вышло издание 2,92 книги «Программирование: введение в профессию» А. В. Столярова (комментарий)

★★★★★

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

Ну они не прямо так сделали:

Перехватываем libc’шный malloc()

Просто обернули вызов glibc-шного аллокатора в свою функцию.

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

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

при определённой изобретательности

Вопрос в том, какова вероятность, что процесс будет прибит OOM killer’ом vs malloc вернёт NULL, если память закончится.

В продакшене никогда не ловили, но вот на десктопе у меня если что-то кривое всю память отжирает, chromium постоянно сообщает, что процесс вкладки прибит 9-ым сигналом (SIGKILL; это значит, что пришёл OOM killer — всякое по типу systemd-oomd у меня выключено).

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

Проверять конечно нужно, но твой пример полностью не в тему. После malloc-а почти нигде не лезут сразу в его дальний конец, обычно инициализируют начиная с начала.

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

Потому что у тебя многозадачная ОС, это как минимум.

Многозадачные ОС были с 1969. А выдача больше памяти, чем есть, появилась где-то с Windows NT. Причём там это было сделано по уму: нехватка памяти не убивала процесс, а увеличивала своп. А Linux выдает всем сколько запросят, а потом прибивает кого-нибудь.

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

Видимо, придётся разжовывать прям до алфавита.

Запрос количества свободной памяти не имеет смысла, потому что здесь by design содержится race condition. Какое число бы тебе ни вернули, на результат полагаться нельзя.

При чём тут Windows NT, оверкоммит и прочая шизофазия, не ясно, потому что вопрос ветки был:

В стандартной библиотеке не предусмотрена кросплатформенная функция, чтобы узнать количество доступной памяти

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

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

Запрос количества свободной памяти не имеет смысла, потому что здесь by design содержится race condition. Какое число бы тебе ни вернули, на результат полагаться нельзя.

Если бы malloc работал правильно, он был бы и не нужен.

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

В Linux есть /proc/meminfo.

monk ★★★★★
()

Тема номера: если дверью прищемить яйца, то яйца окажутся прищемлены.

legolegs ★★★★★
()

Как видно, никаких сегфолтов и прочих ошибок.

Потому что в программе нет ошибок.

Конечно, адрес в районе 127 Тб в примере далеко за пределами доступного почти на всех компьютерах,

Где вы увидели адрес в районе 127 тб? Нет там такого. Похоже что автор не понимает строку a[n]=1

Разберём: здесь a равно 0, n равен адресу b. Это эквивалентно [&b]=1, то есть в b записали 1. Никакой ошибки.

anonymous
()
Ответ на: комментарий от Werenter

Именно так почти всегда и нормально. g_malloc примерно так и реализован.

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

Где вы увидели адрес в районе 127 тб? Нет там такого.

Числовое значение &b.

Разберём: здесь a равно 0, n равен адресу b. Это эквивалентно [&b]=1, то есть в b записали 1. Никакой ошибки.

Вообще-то UB. Потому что доступ к b через адрес a стандартом запрещён. А смысл статьи в том и есть, что когда пишешь

a = malloc(...);
a[x] = y;

в общем случае нет никакой гарантии, что если a = NULL, то a[x] = y вызовет завершение программы.

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

нет никакой гарантии, что если a = NULL, то a[x] = y вызовет завершение программы.

Ну так если мы сошли с нулевой страницы, это должно быть очевидно?

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

Кроме просадки производительности в РБВ?

Там почти вся просадка за счёт длины указателей (и скорости памяти). Проверка безопасности идёт асинхронно с самим доступом к памяти.

Вот считал такты: Энтузиасты дизассемблировали микрокод i386 и создали открытый CPU z386 (комментарий)

13 тактов в обычном против 16 в РБВ. Но это в предположении, что L1 хватает. Удвоение длины указателей заставляет чаще обращаться к медленной памяти.

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

Я имел в виду, что если есть какой-то алгоритм, активно работающий с указателями и в нём в обычном режиме 64КБ указателей, то при переводе на РБВ будет заметное падение, так как рабочий набор станет 128КБ.

monk ★★★★★
()

Почему надо проверять malloc на NULL

NULL - это просто число (а адрес, это число), обозначающее отсутствие адреса, т.е. по данному адресу любая работа не имеет смысла. Не всегда для этой цели можно использовать NULL, например, mmap() возвращает число MAP_FAILED. Вообще, кроме NULL используется очень много разных чисел - https://ru.wikipedia.org/wiki/Hexspeak

ИМХО, поэтому, на NULL надо проверять тогда, когда это имеет смысл - в интерфейсных функциях. Т.е. в тех функциях, которыми будут пользоваться другие.

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


typedef struct
{
    int Var;
} MyObject_t;

void MyObjectInit(MyObject_t *this)
{
    if (this == NULL) // Такое условие сохранит работоспособность при любом значении NULL.
        return;
    this->Var = 0;
}

MyObject_t *MyObjectCreate()
{
    MyObject_t *this = malloc(sizeof(*this));
    MyObjectInit(this);

    return this;
}

void MyObjectFree(MyObject_t **this_ptr)
{
    MyObject_t *this;

    if (this_ptr == NULL)
        return;
    this = (*this_ptr);
    (*this_ptr) = NULL;
    if (this == NULL)
        return;

    free(this);
}

void MyObjectVarShow(MyObject_t *this)
{
    if (this == NULL)
        return;

    printf("0x%p.MyObject.Var = %i\n", this, this->Var);
}

int main()
{
    MyObject_t *MyObject;

    MyObject = MyObjectCreate(); // MyObject = NULL;
    MyObjectVarShow(MyObject);
    MyObjectFree(&MyObject);
}

Vic
()
Ответ на: комментарий от wandrien

Если к адресу прибавить 0, то будет тот же адрес. Вот это открытие.

Смысл не в этом, а в наглядной иллюстрации, что результат выделения памяти надо проверять. Не уповая на то, что нуля не будет или OOM Killer прибьет или еще на что-то. Даже при том, что на x86_64 приняты определенные меры (хотя бы очень большой адрес по умолчанию, чтобы случайных индексов было поменьше) , чтобы защититься от сбоев в этом случае.

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

Ну так если мы сошли с нулевой страницы, это должно быть очевидно?

Я специально написал пример так, чтобы это было очевидно. Потому что столкнулся с тем, что даже некоторые авторы учебников программирования, похоже, что не понимают, чем может быть чревато отсутствие проверки выделения памяти. В их учебнике в примерах результат malloc нигде не проверяется, а сами они сначала вообще были уверены, что на совремённых системах NULL получить невозможно, а когда их убедили, что таки возможно, неудобство видят только в необходимости проверки SIGSEGV и обещают подумать над отношением к результату malloc. С 2017-го года думают http://stolyarov.info/guestbook/archive/2#cmt127

anonymous_incognito ★★★★★
() автор топика
Ответ на: комментарий от anonymous_incognito
#include <stdio.h>
#include <stdint.h>

int main(void)
{
	uint32_t stack_var = 0;
	uint32_t *p = &stack_var;
        *p = 1;
	printf("%u\n", 0[p]);
	printf("%u\n", *(p + (uint32_t)0));
	return 0;
}
IvanRia
()
Ответ на: комментарий от luke

Это вам не буржуйская ересь! Это наш посконно-домотканый Режим Безопасных Вычислений! )
Но в целом похоже, кроме того, что Afaik для тегов используется ещё и половина ECC битов.

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

CHERI?

А для него вообще быстрая реализация существует?

Просто если в РБВ

  • указатель на начало выделенной области памяти/объекта (64 бита);
  • размер выделенной памяти/объекта (32 бита);
  • смещение относительно начала (32 бита).

и все проверки даже для L1 не тратят дополнительных тактов.

То в CHERI границы в формате плавающей точки. По-моему, преобразование https://riscv.github.io/riscv-cheri/#section_cap_bounds_decoding даже теоретически в несколько тактов не утрамбовать.

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

нет никакой гарантии, что если a = NULL, то a[x] = y вызовет завершение программы.

:-) помниться что в dos (real-mode x86) это вызовет установку вектора прерывания номер x

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

Только если int far *a или int huge *a. Если обычный указатель, то запишешь что-то в начале сегмента кода.

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

Тогда при разыменовании NULL на уровне ассемблера не будет сегфолта.

Под фряхой и под рутом:

# ./map-zero-page
mmap: Invalid argument
Ошибка сегментации(core dumped)
iron ★★★★★
()
Ответ на: комментарий от shdown

Попробуйте сделать sysctl security.bsd.map_at_zero=1.

Это читерство)

# ./map-zero-page
0x0
x = 0
iron ★★★★★
()

Почему надо проверять malloc на NULL…

ты не поверишь - все Сишные функции нужно проверять! Это в плюсах православные exception

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

когда их убедили, что таки возможно, неудобство видят только в необходимости проверки SIGSEGV и обещают подумать над отношением к результату malloc. С 2017-го года думают http://stolyarov.info/guestbook/archive/2#cmt127

Мы тут говорим об отборном шизофренике, который не смог нормально свою CMS написать и орал во всё горло, что мерзкие утырки суют ему неправильный HTML, от чего всё едет и падает.

почему я не приемлю технические стандарты как явление и называю их создателей (поголовно всех, во всяком случае, ныне действующих) опасными международными террористами;

Чуваку «террористы» нормально программировать мешают. И, самое чудовищное, он ведь в МГУ преподавал.

yorshka
()

Кому вообще может прийти в голову НЕ проверять результат malloc? Какая-то тема странная. malloc может возвращать NULL во множестве ситуаций.

Если не хочется думать - хотя бы свою обёртку над malloc надо сделать и вызывать abort().

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

malloc может возвращать NULL во множестве ситуаций.

Я знаю две:

  1. когда система отказывает процессу в аллокации новой памяти.
  2. когда мы тестируем ПО на корректность обработки возвращаемых malloc’ом NULLов.

А какие другие?

r--r--r--
()
Ответ на: комментарий от r--r--r--

Я думаю, что это множество ситуаций укладывается в твои два пункта.

Я, конкретно, сталкивался с такими ситуациями:

  1. Когда программа хочет памяти больше, чем есть на компьютере (RAM + swap).

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

  3. Когда программа запущена с ulimit-ограничениями.

vbr ★★★★★
()
  • Markdown
Пустая строка (два раза Enter) начинает новый абзац. Знак '>' в начале абзаца выделяет абзац курсивом цитирования.
Внимание: прочитайте описание разметки Markdown.
Используйте Ctrl-Enter для размещения комментария