Някои разработчици може да отхвърлят проверките: те умишлено не проверяват дали функцията malloc е разпределила памет или не. Техните разсъждения са прости - те смятат, че ще има достатъчно памет. И ако няма достатъчно памет за извършване на операции, оставете програмата да се срине. Изглежда като лош подход, нали? Поради различни причини.

Преди няколко години вече публикувах подобна статия, озаглавена „„Защо е важно да проверим какво е върнала функцията malloc““. Статията, която четете в момента, е нейната актуализирана версия. Първо, имам някои нови идеи, които да споделя с вас. Второ, предишната статия беше част от серия, посветена на проекта Chromium, която проверихме — тя съдържа подробности, които отвличат вниманието от основната тема.

Забележка. В статията под функцията mallocще се подразбира, че въпросът не е само за тази конкретна функция, но и за calloc, realloc, _aligned_malloc, _recalloc, strdup и т.н. На. Не искам да претрупвам статията с всички тези имена на функции. Общото между всички тези функции е, че могат да върнат нулев указател.

malloc

Ако функцията mallocне може да разпредели буфера на паметта, тя връща NULL. Всяка нормална програма трябва да проверява указателите, върнати от функцията malloc и да се справи по подходящ начин със ситуацията, когато паметта не може да бъде разпределена.

За съжаление, много програмисти пренебрегват проверката на указателите и понякога умишлено не проверяват дали паметта е разпределена или не. Мотивите им са следните:

Ако функцията malloc не успя да разпредели памет, малко вероятно е програмата да продължи да функционира правилно. Най-вероятно паметта няма да е достатъчна за други операции, така че защо изобщо да се занимавате с грешките при разпределението на паметта. Първото адресиране към паметта чрез нулев указател води до генериране на структурирано изключение в Windows. Когато става въпрос за Unix-подобни системи, процесът получава сигнала SIGSEGV. В резултат на това програмата се срива, което е съвсем приемливо. Без памет, без страдание. Като алтернатива можете да хванете структурирано изключение/сигнал и да се справите с дереферирането на нулев указател по по-централизиран начин. Това е по-удобно, отколкото да пишете хиляди чекове.

Не си измислям това. Говорил съм с хора, които смятат този подход за подходящ и съзнателно никога не проверяват резултата, който връща функцията malloc.

Между другото, има още едно извинение за разработчиците, защо не правят проверки. Функцията malloc само резервира памет, но не гарантира, че ще има достатъчно физическа памет, когато започнем да използваме разпределения буфер на паметта. Следователно, ако все още няма гаранции, защо да правите проверка? Например Карстен Хайцлер, един от разработчиците на EFL Core библиотеки, обясни защо преброих повече от 500 фрагмента без проверки в кода на библиотеката. Ето неговия коментар към статията:

Добре, така че това е общоприето, че поне на Linux, който винаги е бил основният ни фокус и дълго време беше единствената ни цел, не може да се вярва на връщанията от malloc/calloc/realloc, особено за малки суми. Linux прекалява с паметта по подразбиране. Това означава, че получавате нова памет, но ядрото все още не й е присвоило реални страници с физическа памет. Само виртуално пространство. Не и докато не го докоснеш. Ако ядрото не може да обслужи тази заявка, вашата програма така или иначе се срива, опитвайки се да получи достъп до паметта в нещо, което изглежда като валиден указател. Така че като цяло стойността на проверката на връщанията на allocs, които са малки поне на Linux, е ниска. Понякога го правим… понякога не. Но на връщанията не може да се вярва като цяло, ОСВЕН АКО не са за много големи количества памет и вашият alloc никога няма да бъде обслужван - напр. вашият alloc изобщо не може да се побере във виртуалното адресно пространство (случва се понякога на 32 бита). Да, overcommit може да бъде настроен, но това идва на цена, която повечето хора никога не искат да платят или никой дори не знае, че могат да настроят. Второ, ако разпределението се провали за малка част от паметта - напр. възел на свързан списък... реалистично, ако се върне NULL... сривът е толкова добър, колкото и всичко, което можете да направите. Паметта ви е толкова ниска, че можете да се сринете, извикайте abort(), както прави glib с g_malloc, защото ако не можете да разпределите 20–40 байта... системата ви така или иначе ще падне, тъй като така или иначе нямате останала работна памет. Тук не говоря за малки вградени системи, а за големи машини с виртуална памет и няколко мегабайта памет и т.н., което беше нашата цел. Виждам защо PVS-Studio не харесва това. Всъщност това е правилно, но в действителност кодът, изразходван за обработка на тези неща, е нещо като загуба на код предвид реалността на ситуацията. Ще се спра на това по-късно.

Даденото разсъждение на разработчиците е неправилно. По-долу ще обясня подробно защо.

Трябва да извършите проверки

Има четири причини наведнъж, всяка от които е достатъчна, за да докаже да напишете чек, след като извикате функцията malloc. Ако някой от вашия екип не пише чекове, накарайте го да прочете тази статия.

Преди да започна, ето малка теоретична справка защо се случват структурни изключения или сигнали, ако възникне дерефериране на нулев указател. Важно е за по-нататъшното разказване.

В началото на адресното пространство една или повече страници от паметта са защитени от операционната система. Това позволява да се открият грешки при адресиране към паметта чрез нулев указател или към указател със стойност, близка до 0.

В различните операционни системи за тези цели са запазени различни количества памет. Освен това в някои операционни системи тази стойност може да се конфигурира. Следователно няма смисъл да наричаме определен брой байтове памет запазени. Нека ви напомня, че в Linux системите стандартната стойност е 64Kb.

Важно е, когато добавяте достатъчно голямо число към нулев указател, можете да „зачертаете“ страниците с контролна памет и случайно да попаднете в страници, които не са защитени от запис. По този начин може да се повредят някои данни. Операционната система няма да забележи това и няма да генерира сигнал/изключение.

Забележка. Ако говорим за вградени системи, може да няма защита на паметта от запис чрез нулев адрес. Някои системи имат малко памет и цялата памет съхранява данни. Системите с малко RAM обаче най-вероятно няма да имат динамично управление на паметта и съответно функцията malloc.

Направете си кафе, да започваме!

Дереференцирането на нулев указател е недефинирано поведение

По отношение на езиците C и C++, „дереферирането на нулев указател причинява недефинирано поведение“. Когато се извика недефинирано поведение, всичко може да се случи. Не предполагайте, че знаете как ще се държи програмата, ако възникне деименуване на nullptr. Съвременните компилатори използват сериозни оптимизации. В резултат на това понякога е невъзможно да се предвиди как ще се прояви дадена грешка в кода.

Недефинираното поведение на програмата е много неприятно. Трябва да избягвате недефинирано поведение във вашия код.

Не мислете, че ще можете да се справите с дереференциране на нулев указател, като използвате структурирани манипулатори на изключения (SEH в Windows) или сигнали (в UNIX-подобни системи). Ако се извърши деименуване на нулев указател, работата на програмата вече е прекъсната и всичко може да се случи. Нека да разгледаме един абстрактен пример, защо не можем да разчитаме на SEH-обработчици и т.н.

size_t *ptr = (size_t *)malloc(sizeof(size_t) * N * 2);
for (size_t i = 0; i != N; ++i)
{
  ptr[i] = i;
  ptr[N * 2 - i - 1] = i;
}

Този код запълва масив от краищата към центъра. Стойностите на елемента нарастват към центъра. Измислих този пример за 1 минута, така че не предполагайте защо някой ще има нужда от такъв масив. Дори не знам себе си. За мен беше важно записът в съседните редове да е в началото на масива и някъде в края му. Понякога имате нужда от нещо подобно в практически задачи и ще разгледаме действителния код, когато стигнем до 4-та причина.

Нека отново разгледаме по-отблизо тези два реда:

ptr[i] = i;
ptr[N * 2 - i - 1] = i;

От гледна точка на програмист, в началото на цикъла се появява запис в елемента ptr[0] — ще се появи структурирано изключение/сигнал. Ще се справим и всичко ще бъде наред.

Компилаторът обаче може да размени присвояванията за някои оптимизационни цели. Има всички права за това. Според компилатора, ако указателят е дерефериран, той не може да бъде равен на nullptr. Ако указателят е нула, това е недефинирано поведение и от компилатора не се изисква да мисли за последствията от оптимизацията.

Така че компилаторът може да реши, че за целите на оптимизацията е по-изгодно да изпълнява присвоявания, както следва:

ptr[N * 2 - i - 1] = i;
ptr[i] = i;

В резултат на това в началото ще се извърши запис от адреса ((size_t *)nullptr)[N * 2–0–1]. Ако стойността Nе достатъчно голяма, защитената страница в началото на паметта ще бъде „прескочена“ и стойността на променливата i може да бъде записана във всяка клетка който е достъпен за писане. Като цяло някои данни ще бъдат повредени.

И едва след това ще се извърши присвояването на адреса ((size_t *)nullptr)[0]. Операционната система ще забележи опит за запис в областта, която контролира, и ще генерира сигнал/изключение.

Програмата може да се справи с това структурно изключение/сигнал. Но вече е твърде късно. Някъде в паметта има повредени данни. Освен това не е ясно какви данни са повредени и какви последствия може да има!

Компилаторът ли е виновен за размяната на операциите за присвояване? Не. Програмистът позволи дереферирането на нулев указател да се случи и по този начин доведе програмата до състояние на недефинирано поведение. В този конкретен случай недефинираното поведение на програмата ще бъде, че данните са повредени някъде в паметта.

Заключение

Придържайте се към аксиомата: всяко деименуване на нулев указател е недефинирано поведение на програма. Няма такова нещо като „безобидно“ недефинирано поведение. Всяко недефинирано поведение е неприемливо.

Не позволявайте дерефериране на указатели, върнати от функцията mallocи нейните аналози, без тяхната предварителна проверка. Не разчитайте на други начини за прихващане на дереферирането на нулев указател. Използвайте само добрия стар оператор if.

Дереференцирането на нулев указател е уязвимост

Това, което някои разработчици изобщо не смятат за грешка, други възприемат като уязвимост. Това е точната ситуация, която се случва в случай на дереференция на нулев указател.

В редица проекти е приемливо, ако програмата се срине поради дерефериране на нулевия указател или ако грешката се обработва по някакъв общ начин с помощта на прихващане на сигнал/структурно изключение.

В други приложения дереференцирането на нулев указател представлява един вид потенциална уязвимост, която може да се използва за „DoS атака“ на приложния слой. Вместо нормално да се справи с липсата на памет, програмата или една от нишките за изпълнение прекратява работата си. Това може да доведе до загуба на данни, целостта на данните и т.н.

Ето един пример. Има такава програма като Ytnef, направена за декодиране на TNEF нишки, например създадени в Outlook. Липсата на проверка след извикване на calloc се счита за уязвимост CVE-2017–6298.

Всички фиксирани фрагменти, които можеха да съдържат дереференция на нулев указател, бяха приблизително еднакви:

vl->data = calloc(vl->size, sizeof(WORD));
temp_word = SwapWord((BYTE*)d, sizeof(WORD));
memcpy(vl->data, &temp_word, vl->size);

Изводи

Ако разработвате не много значимо приложение, за което сривът по време на работата му не е проблем, тогава да - не пишете чекове.

Но ако разработвате реален софтуерен проект или библиотека, липсата на проверки е недопустима!

Следователно аз идеологически не съм съгласен с аргумента на Карстен Хайцлер, че липсата на проверки в библиотеката EFL Core е приемлива (повече подробности — в статията). Този подход не позволява на разработчиците да създават надеждни приложения, базирани на такива библиотеки. Ако създавате библиотека, имайте предвид, че в някои приложения дереферирането на нулев указател е уязвимост. Трябва да се справите с грешките при разпределяне на паметта и правилно да върнете информацията за грешката.

Къде са гаранции, че ще се случи дерефериране на точно нулев указател?

Тези, които се чувстват мързеливи да пишат проверки, по някаква причина смятат, че дереферирането засяга точно нулевите указатели. Да, често се случва по този начин. Но може ли програмист да гарантира за кода на цялото приложение? Сигурен съм, че не.

Ще покажа какво имам предвид с практически примери. Например, нека разгледаме кодовия фрагмент на библиотеката LLVM-subzero, която се използва в Chromium.

void StringMapImpl::init(unsigned InitSize) {
  assert((InitSize & (InitSize-1)) == 0 &&
         "Init Size must be a power of 2 or zero!");
  NumBuckets = InitSize ? InitSize : 16;
  NumItems = 0;
  NumTombstones = 0;
  
  TheTable = (StringMapEntryBase **)
             calloc(NumBuckets+1,
                    sizeof(StringMapEntryBase **) + 
                    sizeof(unsigned));
  // Allocate one extra bucket, set it to look filled
  // so the iterators stop at end.
  TheTable[NumBuckets] = (StringMapEntryBase*)2;
}

Забележка. Тук и по-нататък използвам стари кодови фрагменти, които са ми останали от писането на различни статии. Следователно кодът или номерата на редовете може вече да не съвпадат с това, което са сега. Това обаче не е толкова важно за разказа.

Предупреждение на PVS-Studio: V522 CWE-690 Възможно е да има дерефериране на потенциален нулев указател „TheTable“. Проверете редове: 65, 59. stringmap.cpp 65

Веднага след разпределянето на буфера на паметта се извършва запис в клетката TheTable[NumBuckets]. Ако стойността на променливата NumBuckets е достатъчно голяма, ще опетним някои данни с непредвидими последици. След такава повреда няма смисъл да се спекулира как ще работи програмата. Може да има най-неочаквани последствия.

Ще продължа непряката дискусия с Карстен Хайцлер. Той казва, че разработчиците на библиотека разбират какво правят, когато не проверяват резултата от извикването на функцията malloc. Страхувам се, че подценяват опасността от този подход. Нека да разгледаме например следния кодов фрагмент от EFL библиотеката:

static void
st_collections_group_parts_part_description_filter_data(void)
{
  ....
  filter->data_count++;
  array = realloc(filter->data,
    sizeof(Edje_Part_Description_Spec_Filter_Data) *
    filter->data_count);
  array[filter->data_count - 1].name = name;
  array[filter->data_count - 1].value = value;
  filter->data = array;
}

Предупреждение на PVS-Studio: V522 [CWE-690] Възможно е да има дерефериране на потенциален „масив“ с нулев указател. edje_cc_handlers.c 14249

Тук имаме типична ситуация: няма достатъчно място за съхранение на данни в буфер, трябва да се увеличи. За увеличаване на размера на буфера се използва функциятаrealloc, която може да върне NULL.

Ако това се случи, не е задължително да възникне структурирано изключение/сигнал поради дерефериране на нулев указател. Нека да разгледаме тези редове:

array[filter->data_count - 1].name = name;
array[filter->data_count - 1].value = value;

Ако стойността на променливата filter-›data_count е достатъчно голяма, стойностите ще бъдат записани на странен адрес.

В паметта някои данни ще бъдат повредени, но програмата ще работи така или иначе. Последствията са непредвидими и със сигурност няма да има добро.

Заключение

Отново задавам въпроса: „Къде е гаранцията, че ще се случи дерефериране на точно нулев указател?“. Няма такива гаранции. Невъзможно е, докато разработвате или модифицирате код, да си спомните за нюанс, разгледан напоследък. Лесно можете да объркате нещо в паметта, докато програмата ще продължи да се изпълнява, тъй като нищо не се е случило.

Единственият начин да напишете надежден и правилен код е винаги да проверявате резултата, върнат от функцията malloc. Направете проверка и живейте спокоен живот.

Къде са гаранциите, че memset запълва паметта в директен ред?

Ще се намери някой, който ще каже нещо подобно:

Знам идеално за realloc и всичко останало, което пише в статията. Но аз съм професионалист и, когато разпределям памет, просто я запълвам с нули, използвайки memset наведнъж. Където е необходимо използвам чекове. Но няма да пиша допълнителните проверки след всеки malloc.

Като цяло запълването на паметта веднага след разпределението на буфера е доста странна идея. Странно е, защото има функция calloc. Хората обаче много често действат така. Не е нужно да търсите много надалеч, за да намерите примери, ето кода от библиотеката WebRTC:

int Resampler::Reset(int inFreq, int outFreq, size_t num_channels) {
  ....
  state1_ = malloc(8 * sizeof(int32_t));
  memset(state1_, 0, 8 * sizeof(int32_t));
  ....
}

Паметта се разпределя, след което буферът се запълва с нули. Това е много често срещана практика, въпреки че всъщност два реда могат да бъдат намалени до един с помощта на calloc. Това обаче няма особено значение.

Основното е, че дори такъв код не е безопасен! Функцията memset не е необходима, за да започне да запълва паметта от самото начало и по този начин да причини дерефериране на нулев указател.

Функцията memset има право да започне да запълва буфера от края. И ако беше разпределен голям буфер, някои полезни данни могат да бъдат изчистени. Да, докато запълва паметта, функцията memset в крайна сметка ще достигне страницата, защитена от запис, и операционната система ще генерира структурно изключение/сигнал. Вече обаче няма смисъл да се борави с тях. До този момент голям фрагмент от паметта ще бъде повреден - и последващата работа на програмата ще бъде непредсказуема.

Читателят може да възрази, че всичко това е чисто теоретично. Да, функцията memset теоретично може да запълни буфера, започвайки от края на буфера, но на практика никой няма да приложи тази функция по този начин.

Бих се съгласил, че тази реализация на memset е наистина екзотична и дори зададох въпрос относно Stack Overflow по тази тема. Това е отговорът:

Мемсетът на ядрото на Linux за SuperH архитектурата има това свойство: връзка.

За съжаление това е код в непознат за мен асемблер, така че не се наемам да говоря за него. Но все пак има такава интересна реализация в езика C. Ето началото на функцията:

void *memset(void *dest, int c, size_t n)
{
  unsigned char *s = dest;
  size_t k;
  if (!n) return dest;
  s[0] = c;
  s[n-1] = c;
  ....
}

Обърнете внимание на тези редове:

s[0] = c;
s[n-1] = c;

Тук стигаме до причина N1 „Дереференцирането на нулев указател е недефинирано поведение“. Няма гаранция, че компилаторът няма да размени присвояванията. Ако вашият компилатор го направи и аргументът n е от голяма стойност, един байт памет ще бъде повреден в началото. Дереференцирането на нулев указател ще се случи само след това.

Не сте убедени отново? Е, какво ще кажете за това „внедряване“?

void *memset(void *dest, int c, size_t n)
{
  size_t k;
  if (!n) return dest;
  s[0] = s[n-1] = c;
  if (n <= 2) return dest;
  ....
}

Заключение

Не можете дори да се доверите на функцията memset. Да, това може да е изкуствен и пресилен проблем. Просто исках да покажа колко много нюанси се появяват, ако човек не провери стойността на показалеца. Просто е невъзможно да се вземе предвид всичко това. Следователно трябва внимателно да проверявате всеки указател, върнат от функцията malloc и подобни. Това е моментът, когато ще станете професионалист и ще пишете надежден код.

Бележки въз основа на публикацията на предишната статия

Предишната статия породи няколко дебата: 1, 2, 3. Позволете ми да отговоря на някои коментари.

1. Ако malloc върна NULL, по-добре е да прекратите програмата веднага, отколкото да напишете куп if-s и да се опитате по някакъв начин да се справите с липсата на памет, което така или иначе прави изпълнението на програмата невъзможно.

Не призовавах да се борим с последствията от липсата на памет до последно, като хвърляме грешката все по-високо и по-високо. Ако е приемливо вашето приложение да прекрати работата си без предупреждение, така да бъде. За тази цел е достатъчна дори една проверка веднага след malloc или използване на xmalloc (вижте следващата точка).

Възразявах и предупреждавах за липсата на проверки, когато програмата продължава да работи „сякаш нищо не се е случило“. Това е съвсем друг случай. Не е безопасно, тъй като води до недефинирано поведение, повреда на данните и т.н.

2. Няма описание на решение, което се състои в писане на обвиващи функции за разпределяне на памет с проверка, следваща го или използване на вече съществуващи функции, като xmalloc.

Съгласен съм, тази точка ми се изплъзна от ума. За мен беше по-важно да предам на читателя опасността от липсата на чека. Как да коригирате кода е въпрос на вкус и подробности за изпълнението.

Функцията xmalloc не е част от стандартната библиотека на C (вижте „Каква е разликата между xmalloc и malloc?“). Въпреки това, тази функция може да бъде декларирана в други библиотеки, например в GNU utils библиотека (GNU libiberty).

Основната точка на функцията е, че програмата се срива, когато не успее да задели памет. Изпълнението на тази функция може да изглежда по следния начин:

void* xmalloc(size_t s)
{
  void* p = malloc(s);
  if (!p) {
    fprintf (stderr, "fatal: out of memory (xmalloc(%zu)).\n", s);
    exit(EXIT_FAILURE);
  }
  return p;
}

Съответно, като извиквате функцията xmallocвместо malloc всеки път, можете да сте сигурни, че няма да има недефинирано поведение в програмата поради използването на нулев указател.

За съжаление, xmalloc също не е панацея. Трябва да запомните, че използването на xmalloc е неприемливо, когато става въпрос за писане на библиотечен код. Ще говоря за това по-късно.

3. Повечето коментари бяха следните: „на практика malloc никога не връща NULL.“

Това обикновено се твърди от разработчиците на Linux. „Те не са прави“. За щастие не съм единственият, който разбира, че това е грешен подход. Много ми хареса този коментар:

От моя опит в обсъждането на тази тема, имам чувството, че има две секти в интернет. Членовете на първата секта са хора, които са твърдо убедени, че в Linux malloc никога не връща NULL. Поддръжниците на второто са твърдо убедени, че ако паметта в програмата не може да бъде разпределена, нищо не може да се направи по принцип, просто оставяте приложението да се срине. Няма как да ги преубедиш. Особено когато тези две секти се пресекат. Можете да го приемете само като даденост. И дори не е важно на кой специализиран ресурс се провежда дискусия.

Помислих малко и реших да последвам съвета, така че няма да се опитвам да убеждавам никого :). Да се ​​надяваме, че тези екипи за разработка пишат само некритичен софтуер. Ако, например, някои данни се повредят в играта или играта се срине, това не е голяма работа.

Единственото важно нещо е разработчиците на библиотеки, бази данни и т.н. да не мислят същото.

Призив към разработчиците на много надежден код и библиотеки

Ако разработвате библиотека или друг силно надежден код, винаги проверявайте стойността на указателя, върнат от функцията malloc/realloc, и връщайте навън код за грешка, ако паметта не може да бъде разпределена.

В библиотеките не можете да извикате функцията изход, ако разпределението на паметта е неуспешно. По същата причина не можете да използвате xmalloc. За много приложения е неприемливо просто да ги прекъснете. Поради това, например, база данни или проект, върху който човек е работил много часове, може да бъде повреден. Човек може да загуби данни, които са били оценявани в продължение на много часове. Поради това програмата може да бъде подложена на уязвимости „отказ на услуга“, когато вместо правилното обработване на нарастващото работно натоварване, едно многонишково приложение просто се прекратява.

Не можете да предположите в какви проекти ще се използва библиотеката. Следователно трябва да се приеме, че приложението може да решава много критични задачи. Ето защо простото убиване чрез извикване на exit не е добро. Най-вероятно такава програма е написана, като се вземе предвид възможността за липса на памет и може да направи нещо в този случай. Например, CAD система не може да разпредели подходящ буфер на паметта, който да е достатъчен за редовна работа, поради силната фрагментация на паметта. В случая не е причината да забива в авариен режим със загуба на данни. Програмата може да предостави възможност за запазване на проекта и нормално рестартиране.

Никога не разчитайте на факта, че malloc винаги може да разпредели памет. Не знаете на коя платформа и как ще се използва библиотеката. Ако ситуацията с недостатъчна памет на една платформа е екзотична, това може да бъде доста често срещана ситуация на друга.

Не можете да очаквате, че ако malloc върне NULL, програмата ще се срине. Всичко може да се случи. Програмата може да записва данни изобщо не по нулевия адрес. В резултат на това някои данни могат да бъдат повредени, което води до непредвидими последици. Дори memset не е безопасен. Ако подпълването с данни върви в обратен ред, първо някои данни се повреждат и след това програмата ще се срине. Но сривът може да настъпи твърде късно. Ако се използват повредени данни в паралелни нишки, докато функцията memset работи, последствията могат да бъдат фатални. Можете да получите повредена транзакция в база данни или да изпратите команди за премахване на „ненужни“ файлове. Всичко има шанс да се случи. Предлагам на читателя да си помисли какво може да се случи поради използването на боклук в паметта.

По този начин библиотеката има само един правилен начин за работа с функциите malloc. Трябва ВЕДНАГА да проверите какво е върнала функцията и ако е NULL, да върнете статус на грешка.

Заключение

Винаги проверявайте наведнъж указателя, върнат от функцията malloc или нейните аналози.

Както можете да видите, анализаторът на PVS-Studio е прав, предупреждавайки, че няма проверка на указателя след извикване на malloc. Невъзможно е да напишете надежден код без да правите проверки. Това е особено важно и подходящо за разработчиците на библиотеки.

Надявам се, че сега имате нов поглед върху функцията malloc, указателите за проверка и предупрежденията на кодовия анализатор на PVS-Studio. Не забравяйте да покажете тази статия на вашите съотборници и започнете да използвате PVS-Studio. Благодаря за вниманието. Пожелавам ви по-малко грешки!