Как стать автором
Обновить
581
35.6
Андрей Карпов @Andrey2008

Директор по маркетингу

Отправить сообщение

Ловите ленивого! :)

P.S. Системы разные бывают. Настройки систем бывают разные. Код может заимствоваться в другие приложения для других систем.

P.P.S. Впрочем, продолжайте. Больше подобного кода, больше спрос на статические анализаторы кода :) Спасибо.

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

«Есть всего два типа языков программирования: те, на которые люди всё время ругаются, и те, которые никто не использует» Bjarne Stroustrup
:)

В любом случае можно испортить свою память. Это может иметь весьма печальные последствия.

Причём здесь это. Записывая по случайному адресу можно испортить данные самого приложения.

Такое может быть в некоторых приложениях.

Это обсуждают. Я видел такие проверки. Значит стоит. :)

У вас эта книга опубликована на сайте.

Нет. Печатная книга сильно переработана :)

Ответ в личке

Какие конструкторы... Это Си :)

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

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

void foo(const char *name)
{
    char *buf = NULL;

    if (name == NULL)
        goto end;
    int64_t size = GetFileSize(name);
    if (size == -1)
        goto end;
    buf = (char *)malloc(size);
    if (buf == NULL)
        goto end;
    if (ReadFile(name, buf, size) == -1)
        goto end;
    Do(buf, size);
end:
    free(buf);
}

Здесь assert писать перед free смысла нет. Вполне естественно, что файл отсутствует и буфер для его чтения не понадобился. Если же это ненормальная ситуация и файл должен всегда быть, то логичнее assert написать выше рядом с кодом работы с файлом.

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

void foo(const char *name)
{
    if (name == NULL)
        return;

    char *copy = strdup(name);
    if (copy == NULL)
        return;

    Do(copy);

    assert(copy);
    free(copy);
}

Здесь assert уместен, так как проверяет договорённость, что буфер был успешно выделен и при освобождении указатель не может быть нулевым. Если это так, то что-то явно пошло не так и надо с этим разобраться.

В приведённом простом примере кода assert конечно выглядит надуманным и лишним. Но на практике в большом приложении это вполне может помогать.

Вот реальный фрагмент кода из одного проекта, в котором я недавно нашёл ошибку с помощью PVS-Studio:

    free(desc->manifest);
    desc->manifest = NULL;
    free(desc->manifest_digest);
    desc->manifest_digest = NULL;
    free(desc->config);
    desc->config = NULL;
    free(desc->config_digest);
    desc->config_digest = NULL;
    free(desc->uncompressed_digest);      // <=
    desc->uncompressed_digest = NULL;     // <=
    free(desc->compressed_digest);
    desc->compressed_digest = NULL;
    free(desc->tag);
    desc->tag = NULL;
    free(desc->uncompressed_digest);      // <=
    desc->uncompressed_digest = NULL;     // <=
    free(desc->layer_file);
    desc->layer_file = NULL;

Рука программиста дрогнула и он два раза пытается освободить один и тот-же буфер (выделил строки комментариями).

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

Обратите внимание, что защитное программирование в виде обнуления указателя защитила от двойного освобождения памяти. Однако, если добавить assert то станет ещё лучше.

    assert(desc->manifest);
    free(desc->manifest);
    desc->manifest = NULL;

    assert(desc->manifest_digest);
    free(desc->manifest_digest);
    desc->manifest_digest = NULL;

    assert(desc->config);
    free(desc->config);
    desc->config = NULL;

    assert(desc->config_digest);
    free(desc->config_digest);
    desc->config_digest = NULL;

    assert(desc->uncompressed_digest);
    free(desc->uncompressed_digest);      // <=
    desc->uncompressed_digest = NULL;     // <=

    assert(desc->compressed_digest);
    free(desc->compressed_digest);
    desc->compressed_digest = NULL;

    assert(desc->tag);
    free(desc->tag);
    desc->tag = NULL;

    assert(desc->uncompressed_digest);    // Сработает этот assert!
    free(desc->uncompressed_digest);      // <=
    desc->uncompressed_digest = NULL;     // <=

    assert(desc->layer_file);
    free(desc->layer_file);
    desc->layer_file = NULL;

Я не знаю, можно ли сделать такую правку. В том смысле, что возможно нулевое значение указателей это нормально. Однако, если это не так, то assert позволил бы выявить опечатку.

P.S. А вообще выглядят вот такие простыни C кода для ручного управления памяти ужасно. Умные указатели C++ это прям отрада души конечно.

Представляю :) У нас как раз деревья. И решение для повышения эффективности - освобождать всё сразу :)

Какая стратегия освобождения памяти используется в C и С++ ядре PVS-Studio?

Если предполагается, что в 99% ptr будет NULL, то тем более, если free медленная, лучше проверить не вызывая ее.

Это какая-то особая программа, где 99% процентов указателей это NULL :)

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

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

Это просто пример. Можно что-то ещё придумать. Смысл в том, что разумно оптимизировать такие ситуации с верхнего уровня, а не снизу по мелочи. Не там ищется решение задачи оптимизации.

Вообще не спорная. В статье это описано, но ещё раз:

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

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

У нас сложная система публикации материала, включающая в себя собственный плагин для Word, конвертирующий специально оформленные документы в страницы сайта. Система предупреждает про неправильные написания терминов, названий проектов (свой словарь), символов (не используем табы в примерах кода), что в англоязычных материалах попали всякие русские (а, о), проверяющая корректность и доступность ссылок, размеры картинок (чтобы кто-то случайно не вставил фотку на 5 мегабайт) и ещё 100500 проверок. Можно сказать, что у нас ещё есть специализированный статический анализатор для документов :)

И мы точно не будем проталкивать во всю эту экосистему этот экзотический Unicode символ, чтобы один пример в одном квизе стал чуть лучше. Лучше наоборот разумно ограничивать возможности, поддерживаемы символы и т.д. В общем, комментарий не принимаю :)

Относитесь проще. Всё это носит развлекательный характер, а не, например, для проведения собеседований :)

Информация

В рейтинге
134-й
Работает в
Дата рождения
Зарегистрирован
Активность

Специализация

Специалист
C++
C
Software development