Предотвращение G CC от объединения переменных в фигурных группах - PullRequest
0 голосов
/ 02 апреля 2020

Редактировать: видимо, доступ к переменным внутри фигурных групп после их завершения является неопределенным поведением. Так как я не хочу использовать динамическое распределение c для узлов (как предложено @ dbu sh, @ikegami), я предполагаю, что следующий лучший способ сохранить скрытые переменные (внутри функции) - генерировать уникальные имена переменных для узлы (с __LINE__) и «объявление» без использования группы в скобках. Код теперь читает что-то вроде

#define PASTE_(x, y) x ## y
#define PASTE(x, y) PASTE_(x, y)
#define LT_mark_(LABEL, NAME, DELETE)\
        struct LifeTime LABEL  ={\
            .delete=DELETE,\
            .prev=lt_head,\
            .ref=NAME\
        };\
        \
        lt_head = &LABEL;\

#define LT_mark(NAME, DELETE) LT_mark_(PASTE(lt_, __LINE__), NAME, DELETE)

/ Edit

Я пытаюсь сохранить записи для памяти, выделенной в области действия функции. Записи хранятся в структуре LifeTime, которая образует связанный список. Этот список позже просматривается при возврате из указанной функции, чтобы автоматически освободить память. Переменная lt_head используется для отслеживания текущего заголовка списка.

struct LifeTime {
    void (*delete)(void*);
    struct LifeTime *prev;
    void *ref;
};

#define LT_mark(NAME, DELETE)\
    {\
        struct LifeTime _  ={\
            .delete=DELETE,\
            .prev=lt_head,\
            .ref=NAME\
        };\
        \
        lt_head = &_;\
    }

int example (){
    struct LifeTime *lt_head = NULL;

    char *s  = malloc(64); LT_mark(s,  free);
    char *s2 = malloc(64); LT_mark(s2, free);

    ...
}

Используя этот код, временные переменные (с именем _) внутри групп в скобках, созданные LT_mark макрос, создаются с тем же адресом памяти.

Я предполагаю, что причина этого заключается в том, как указано в ответе на этот вопрос: В C действуют ли фигурные скобки как кадр стека? что переменные с непересекающимися временами использования могут быть объединены, если компилятор сочтет это целесообразным.

Есть ли способ переопределить это поведение? Я признаю, что это может быть невозможно (я использую G CC без каких-либо флагов оптимизации, поэтому я не могу просто удалить их), но реальный код, с которым я работаю, требует, чтобы переменные внутри этих групп впоследствии сохранялись, хотя и были скрыты от видимости (как обычно это делают группы в скобках). Я рассмотрел использование __attribute__((used)), но, видимо, это справедливо только для функций и глобальных переменных.

Ответы [ 2 ]

1 голос
/ 02 апреля 2020

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

Например:

int *p;
{
    int i=4;
    p=&i;
    printf("*p=%d\n", *p);   // prints *p=4
}
printf("*p=%d\n", *p);   // undefined behavior, p points to invalid memory

Внутри фигурных скобок p указывает на действительную память и может быть разыменована. За пределами скобок p нельзя безопасно отразить.

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

void LT_mark(void *p, void (*cleanup)(void *))
{
    struct LifeTime *l = malloc(sizeof *l);
    l->delete = cleanup;
    l->prev = lt_head;
    l->ref = p;
    lt_head = l;
}

И, аналогично, функция очистки:

void LT_clean()
{
    struct LiftTime *p;
    while (lt_head) {
        lt_head->delete(lt_head->ref);
        p = lt_head->prev;
        lt_head = lt_head->prev;
        free(p);
    }
}

Кроме того, prev поле должно быть переименовано в next, так как существующее имя вводит в заблуждение.

0 голосов
/ 03 апреля 2020

В большинстве случаев вы захотите использовать @ dbu sh динамический c распределение решение . Поскольку вы, вероятно, в любом случае используете это с динамическим распределением памяти c в некотором роде, динамическое выделение блоков дескрипторов не должно быть огромными накладными расходами.

Однако, при некоторых действительно ограниченных обстоятельствах, которые вам придется Полицейский сам, и, предполагая, что вы не используете допотопную версию компилятора C, это можно сделать довольно просто с помощью составных литералов . Помимо ограничения версии C компилятора (C99 или выше, что не должно быть огромным бременем), это будет работать точно в тех же условиях, что и ваша правка # 1, использующая конкатенацию токенов для генерации уникального имени: то есть, если не используется макрос LT_mark внутри ограниченного блока, подчиненного функции.

Причина этого ограничения, которое, как я уже говорил, относится и к вашему решению с конкатенацией токенов, заключается в том, что время жизни автоматических c распределений заканчивается, когда управление выходит из блока, в котором они были объявлены. Это важный аспект C (и многих других языков программирования), поэтому важно четко понимать, как он работает.

Вот простой пример:

int example (){
    struct LifeTime *lt_head = NULL;
    char *s  = malloc(64); LT_mark(s,  free);
    for (int i = 0; i < 4; ++i) {
        /* InnerBlock */
        char *s2 = malloc(64); LT_mark(s2, free);
        ....
    }
    /* Lifetime of all variables declared in InnerBlock expires */
    ....
    /* If lt_head points to a struct automatically allocated inside
     * InnerBlock, it is now a dangling pointer and cannot be used.
     * The next statement is Undefined Behaviour.
     */
    freeTheMallocs(lt_head);
}

Обратите внимание, что проблема не в том, что внутренний блок выполняется более одного раза (хотя это, вероятно, гарантирует, что вы заметите проблему). То же самое произошло бы, если бы я написал это как условное выражение:

int example (int flag){
    struct LifeTime *lt_head = NULL;
    char *s  = malloc(64); LT_mark(s,  free);
    if (flag) {
        /* InnerBlock */
        char *s2 = malloc(64); LT_mark(s2, free);
        ....
    }
    /* Lifetime of all variables declared in InnerBlock expires */
    ....
    freeTheMallocs(lt_head); /* Dangling pointer */
}

Вышеуказанное не может работать с автоматическим c распределением блоков дескрипторов (но оно будет работать нормально с Dynami c выделение).

ОК, что произойдет, если вы абсолютно пообещаете использовать LT_mark только во внешнем блоке своей функции, как в исходном примере:

int example (){
    struct LifeTime *lt_head = NULL;
    char *s  = malloc(64); LT_mark(s,  free);
    char *s2 = malloc(64); LT_mark(s2, free);
    freeTheMallocs(lt_head);
}

Это сработает. Ваша единственная проблема заключается в том, как обеспечить соблюдение ограничения, в том числе для всех программистов, которые будут изменять код после того, как вы покидаете проект, и, возможно, не будете иметь ни малейшего представления о том, почему им не разрешено вкладывать LT_mark в блок ( или даже знаете, что им нельзя это делать).

Но если вам нравится играть с огнем, вы можете сделать это так:

#define LT_mark(NAME, DELETE)      \
    lt_head = &(struct LifeTime){  \
        .delete=DELETE,            \
        .prev=lt_head,             \
        .ref=NAME                  \
    }

Это будет работать, в ограниченный набор случаев, в которых он работает, потому что составной литерал, созданный макросом, «имеет автоматическую c продолжительность хранения, связанную с включающим блоком». (§6.5.2.5 / 5).

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

...