2017-02-03 11 views
4

Вопрос: Не могли бы вы помочь мне лучше понять макрос RAII на языке C (а не C++), используя только ресурсы, которые я поставлю в нижней части этого вопроса? Я пытаюсь проанализировать это в своем уме, чтобы понять, что он говорит и как это имеет смысл (это не имеет смысла в моем сознании). Синтаксис сложный. В центре внимания вопроса: у меня проблемы с чтением и пониманием странного синтаксиса и его реализации на языке C. Например я могу легко читать, понимать и анализировать (это имеет смысл для меня) следующий своп макрос:Приобретение ресурсов Инициализация в C lang

#define myswap(type,A,B) {type _z; _z = (A); (A) = (B); (B) = _z;} 

(следующий отрывок поднимается из книги: Понимание указателей C)

В языке C компилятор GNU обеспечивает нестандартное расширение до поддержка RAII.

Расширение GNU использует макрос, называемый RAII_VARIABLE. Он объявляет переменную и связывает с переменной:

  • Тип
  • Функция для выполнения, когда создается переменная
  • Функция для выполнения, когда переменная выходит из области видимости

    макросов приведен ниже:

    #define RAII_VARIABLE(vartype,varname,initval,dtor) \ 
    void _dtor_ ## varname (vartype * v) { dtor(*v); } \ 
    vartype varname __attribute__((cleanup(_dtor_ ## varname))) = (initval) 
    

    Пример:

    void raiiExample() { 
    RAII_VARIABLE(char*, name, (char*)malloc(32), free); 
    strcpy(name,"RAII Example"); 
    printf("%s\n",name); 
    } 
    
    int main(void){ 
        raiiExample(); 
    } 
    

Когда эта функция выполняется, строка «RAII_Example» будет отображаться. Аналогичные результаты могут быть достигнуты без использования расширения GNU.

+0

Мне не нравится этот метод, это не похоже на C. – Stargateur

+2

Зачем использовать этот макрос вместо ... с помощью C++? –

+1

В центре внимания вопроса: не могли бы вы помочь мне лучше понять макрос RAII на языке C? Я борюсь с синтаксисом и тем, как он реализован в Clang. Я знаю, что он делает (выделяет и освобождает память .....). :) – Mynicks

ответ

2

Конечно, вы можете добиться чего-либо без использования RAII. В случае использования RAII ему не нужно думать о том, чтобы освободить ресурсы повторно. Образец, подобный:

void f() { 
    char *v = malloc(...); 
    // use v 
    free v; 
} 

Необходимо, чтобы вы заботились о выпуске памяти, если бы у вас не было утечки памяти. Как это не всегда легко правильно освободить RESSOURCES, RAII предоставляет вам способ автоматизировать освобождение:

void f() { 
    RAII_VARIABLE(char*, v, malloc(...), free); 
    // use v 
} 

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

2

Синтаксис немного сложный, потому что __attribute__ ((cleanup)) ожидает передать функцию, которая принимает указатель на переменную. Из GCC documentation (курсив мой):

Функция должна принимать один параметр указатель на тип, совместимый с переменной. Возвращаемое значение функции (если оно есть) равно .

Рассмотрим следующие некорректной Например:

char *name __attribute__((cleanup(free))) = malloc(32); 

Было бы гораздо проще осуществить это так, однако в этом случае free функция неявным принимает указатель к name, где его тип char ** , Вам нужно каким-то образом принудительно передать соответствующий объект, что является самой идеей функционального макроса RAII_VARIABLE.

Упрощенная и необщего воплощение RAII_VARIABLE было бы определить функцию, скажем, raii_free:

#include <stdlib.h> 

void raii_free(char **var) { free(*var); } 

int main(void) 
{ 
    char *name __attribute__((cleanup(raii_free))) = malloc(32); 
    return 0; 
} 
3

Хорошо, давайте посмотрим на части макро построчно

#define RAII_VARIABLE(vartype,varname,initval,dtor) \ 

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

void _dtor_ ## varname (vartype * v) { dtor(*v); } \ 

Вторая строка объявляет функцию. Он принимает предоставленный токен varname и добавляет его с префиксом _dtor_ (оператор ## инструктирует препроцессор объединить два токена вместе в один токен). Эта функция принимает указатель на vartype в качестве аргумента и вызывает предоставленный деструктор с этим аргументом.

Этот синтаксис может быть неожиданным здесь (например, использование оператора ## или тот факт, что он полагается на способность объявлять вложенные функции), но пока это не настоящая магия. на третьей строке появляется магия:

vartype varname __attribute__((cleanup(_dtor_ ## varname))) = (initval) 

Здесь переменная объявлена, без __attribute__() это выглядит довольно просто: vartype varname = (initvar). Магия - это директива __attribute__((cleanup(_dtor_ ## varname))). Он инструктирует компилятор обеспечить, чтобы вызываемая функция вызывалась, когда переменная выпадает из области видимости.


Синтаксис __attribute__() это является расширением языка при условии, компилятором, так что вы глубоко в реализации определяется поведение здесь. Вы не можете полагаться на другие компиляторы, предоставляя одинаковые __attribute__((cleanup())). Многие могут это предоставить, но никто не должен. Некоторые старшие компиляторы могут даже не знать синтаксис __attribute__() вообще, и в этом случае стандартная процедура должна быть #define __attribute__() пустым, зачищая все __attribute__() деклараций из кода. Вы не хотите, чтобы это произошло с переменными RAII. Итак, если вы полагаетесь на __attribute__(), знайте, что вы потеряли возможность компиляции с любым стандартным соответствующим компилятором.