Умные указатели / безопасное управление памятью для C? - PullRequest
40 голосов
/ 29 апреля 2009

Я, как и многие другие, добился больших успехов, используя умные указатели для обертывания небезопасных операций с памятью в C ++, используя такие вещи, как RAII и так далее. Однако управление обертыванием памяти проще реализовать, когда у вас есть деструкторы, классы, перегрузка операторов и т. Д.

Для кого-то, пишущего на чистом C99, где вы могли бы указать (без каламбура), чтобы помочь с безопасным управлением памятью?

Спасибо.

Ответы [ 8 ]

18 голосов
/ 10 января 2015

Вопрос немного устарел, но я решил, что потрачу время на ссылку на мою библиотеку интеллектуальных указателей для компиляторов GNU (GCC, Clang, ICC, MinGW, ...). *

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

Пример:

simple1.c:

#include <stdio.h>
#include <csptr/smart_ptr.h>

int main(void) {
    smart int *some_int = unique_ptr(int, 1);

    printf("%p = %d\n", some_int, *some_int);

    // some_int is destroyed here
    return 0;
}

Сессия компиляции и Valgrind:

$ gcc -std=gnu99 -o simple1 simple1.c -lcsptr
$ valgrind ./simple1
==3407== Memcheck, a memory error detector
==3407== Copyright (C) 2002-2013, and GNU GPL\'d, by Julian Seward et al.
==3407== Using Valgrind-3.10.0 and LibVEX; rerun with -h for copyright info
==3407== Command: ./simple1 
==3407==
0x53db068 = 1
==3407==
==3407== HEAP SUMMARY:
==3407==     in use at exit: 0 bytes in 0 blocks
==3407==   total heap usage: 1 allocs, 1 frees, 48 bytes allocated
==3407==
==3407== All heap blocks were freed -- no leaks are possible
==3407==
==3407== For counts of detected and suppressed errors, rerun with: -v
==3407== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
13 голосов
/ 29 апреля 2009

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

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

8 голосов
/ 29 апреля 2009

Другим подходом, который вы можете рассмотреть, является подход с объединенной памятью, который Apache использует . Это работает исключительно хорошо, если у вас есть динамическое использование памяти, которое связано с запросом или другим недолговечным объектом. Вы можете создать пул в структуре запроса и убедиться, что вы всегда выделяете память из пула, а затем освобождаете пул, когда закончите обработку запроса. Это звучит не так сильно, как если бы вы немного его использовали. Это почти так же хорошо, как RAII.

3 голосов
/ 11 августа 2012

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

В C я часто вижу шаблон 'GOTO cleanup':

int foo()
{
    int *resource = malloc(1000);
    int retVal = 0;
    //...
    if (time_to_exit())
    {
        retVal = 123;
        goto cleanup;
    }
cleanup:
    free(resource);
    return retVal;
}

В C мы также используем много контекстов, которые выделяют вещи, для этого тоже можно применить то же правило:

int initializeStuff(Stuff *stuff)
{
    stuff->resource = malloc(sizeof(Resource));
    if (!stuff->resource) 
    {
        return -1; ///< Fail.
    }
    return 0; ///< Success.
}

void cleanupStuff(Stuff *stuff)
{
    free(stuff->resource);
}

Это аналог конструкторов и деструкторов объектов. Пока вы не отдадите выделенные ресурсы другим объектам, они не будут вытекать и указатели не будут болтаться.

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

Если вам нужно выдать указатели на выделенные ресурсы, вы можете создать для него контексты обертки, и каждому объекту принадлежит контекст обертки вместо ресурса. Эти обертки совместно используют ресурс и объект-счетчик, который отслеживает использование и освобождает объекты, когда его никто не использует. Вот как в C ++ 11 shared_ptr и weak_ptr работают. Более подробно здесь написано: Как работает weak_ptr?

3 голосов
/ 29 апреля 2009

Инструменты статического анализа кода, такие как splint или Gimpel PC-Lint, могут помочь в этом - вы даже можете сделать их в меру «профилактическими», подключив их к вашей автоматической «непрерывной интеграции» msgstr "стиль сборки сервера. (У вас есть один из них, верно?: Grin:)

Есть и другие (более дорогие) варианты на эту тему ...

2 голосов
/ 08 апреля 2014

Вы можете определить макросы, например, BEGIN и END, которые будут использоваться вместо фигурных скобок и запускать автоматическое уничтожение ресурсов, выходящих из области их действия. Это требует, чтобы на все такие ресурсы указывали умные указатели, которые также содержат указатель на деструктор объекта. В моей реализации я сохраняю стек интеллектуальных указателей в памяти кучи, запоминаю указатель стека при входе в область и вызываю деструкторы всех ресурсов над запомненным указателем стека при выходе из области (завершение или замена макроса для возврата) Это прекрасно работает, даже если используется механизм исключений setjmp / longjmp, и очищает все промежуточные области между блоком catch и областью, в которой было сгенерировано исключение. См. https://github.com/psevon/exceptions-and-raii-in-c.git для реализации.

1 голос
/ 29 апреля 2009

Если вы кодируете в Win32, вы можете использовать структурированную обработку исключений для выполнения чего-то подобного. Вы могли бы сделать что-то вроде этого:

foo() {
    myType pFoo = 0;
    __try
    {
        pFoo = malloc(sizeof myType);
        // do some stuff
    }
    __finally
    {
        free pFoo;
    }
}

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

0 голосов
/ 26 августа 2015
Sometimes i use this approach and it seems good :)

Object *construct(type arg, ...){

    Object *__local = malloc(sizeof(Object));
    if(!__local)
        return NULL;
    __local->prop_a = arg;
    /* blah blah */


} // constructor

void destruct(Object *__this){

   if(__this->prop_a)free(this->prop_a);
   if(__this->prop_b)free(this->prop_b);

} // destructor

Object *o = __construct(200);
if(o != NULL)
   ;;

// use

destruct(o);

/*
  done !
*/
...