Подтвердить что ты не робот

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

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

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

Спасибо.

4b9b3361

Ответ 1

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

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

Ответ 2

Вопрос немного устарел, но я решил, что я потрачу время на ссылку на интеллектуальную библиотеку указателей для компиляторов 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)

Ответ 3

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

Ответ 4

Вы не можете делать интеллектуальные указатели в 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.

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

Ответ 5

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

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

Ответ 6

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

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

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

Ответ 7

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

Ответ 8

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 !
*/