-1

Edit: Apparently accessing variables inside braced groups after they end is undefined behaviour. Since I don't want to use dynamic allocation for nodes (as suggested by @dbush, @ikegami) I assume the next best way to keep hidden variables (within a function) is generating unique variable names for the nodes (with __LINE__) and 'declaring' without the use of a braced group. The code now reads something like

#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

I'm trying to keep records for memory allocated within a function's scope. Records are kept by a LifeTime structure, which form a linked list. This list is later traversed when returning from said function, in order to automatically free the memory. The lt_head variable is used to keep track of the current head of the list.

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);

    ...
}

Using this code, the temporary variables (named _) within the braced groups created by the LT_mark macro, are created with the same memory address.

I assume the reason for this is, as stated in the answer to this question: In C, do braces act as a stack frame? that variables with non-overlapping usage lifetimes may be merged if the compiler deems it appropriate.

Is there any way to override this behaviour? I acknowledge it may be impossible (I am using GCC without any optimization flags, so I can't simply remove them), but the actual code I am working with requires that the variables inside these groups are kept afterwards, though hidden from visibility (as braced groups do usually). I considered using __attribute__((used)) but apparently this is only valid for functions and global variables.

myc3lium
  • 31
  • 5
  • 1
    They are in different scopes, so may well have the same address. They do not co-exist. "the variables inside these groups are kept afterwards" - no although some compilers will allocate a separate address for each at the start of the function, they can be anywhere. – Weather Vane Apr 02 '20 at 19:46
  • 2
    "the actual code I am working with requires that the variables inside these groups are kept afterwards". So your question is an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). Describe more what you are trying to achieve as there may be better solutions. – kaylum Apr 02 '20 at 19:48
  • @WeatherVane so I take it there is no way to prevent this? – myc3lium Apr 02 '20 at 19:49
  • It would be useful if C had a syntax to specify that an object created within a statement should be exported to the surrounding context, but no such feature exists. If your function won't be used recursively or by multiple threads, you could declare your objects `static`. – supercat Apr 02 '20 at 19:49
  • 1
    If they are needed later, define them at the start of the function. As it stands you *can't* use them later. – Weather Vane Apr 02 '20 at 19:49
  • 2
    Re "*the actual code I am working with requires that the variables inside these groups are kept afterwards*", Accessing variables after they cease to exists is undefined behaviour. Use dynamic memory allocation (e.g. `malloc`) if you want them to persist. – ikegami Apr 02 '20 at 19:51
  • 1
    Every brace group creates a new variable scope and lifetime. You can't access a variable after you've exited its lifetime. – Barmar Apr 02 '20 at 19:53
  • @Barmar ah, I see. I assumed (wrongly it seems) since the question I quoted says they don't behave like stack frames, the memory such variables occupy would still be accessible by pointer. – myc3lium Apr 02 '20 at 20:17
  • @myc3lium The compiler is allowed to optimize. Even if the variables are in the same scope, if it notices that they're never used at the same time, it's could use the same memory for them. – Barmar Apr 02 '20 at 20:22
  • The correct solution to the problem that you've described is an `lt_malloc` function that allocates memory and adds the returned pointer to a list. An `lt_free` function would then `free` all of the memory. – user3386109 Apr 02 '20 at 21:43

2 Answers2

1

The lifetime of a variable is that of its enclosing scope, so when that scope ends the variable no longer exits. Saving the address of that variable and attempting to use it when its lifetime has ended causes undefined behavior.

For example:

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

Inside of the braces, p points to valid memory and can be dereferenced. Outside of the braces p cannot be safely defererenced.

You'll need to do some dynamic allocation to create these structures. Also, this isn't a place where you should be using a macro instead of a function:

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;
}

And similarly the cleanup function:

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);
    }
}

Also, the prev field should be renamed to next, as the existing name is misleading.

dbush
  • 205,898
  • 23
  • 218
  • 273
  • just a nitpick, `prev` is the name I chose because it points to the most recently created record. I'd disagree the name is misleading. – myc3lium Apr 02 '20 at 20:10
0

Under most circumstances, you'll want to use @dbush's dynamic allocation solution. Since you're presumably using this with dynamic memory allocations of some kind anyway, dynamically allocating the descriptor blocks shouldn't be a huge overhead.

However, under some really restricted circumstances which you will have to police yourself, and assuming that you're not using an antediluvian version of the C compiler, it is possible to do this fairly simply with compound literals. Aside from the C compiler version limitation (C99 or better, which shouldn't be a huge burden), this will work in exactly the same circumstances as your edit#1 using token concatenation to generate a unique name: that is, if no use of the LT_mark macro is inside a braced-block subordinate to the function.

The reason for this restriction -- which, as I said, applies also to your solution with token concatenation -- is that the lifetime of automatic allocations terminates when control exits from the block in which they were declared. This is an essential aspect of C (and many other programming languages), so it's important to be clear about how it works.

Here's a simple example:

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);
}

Note that the problem is not that the inner block is executed more than once (although that will probably guarantee that you notice the problem). The same thing would happen had I written it as a conditional:

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

The above cannot work with automatic allocation of descriptor blocks (but it will work fine with dynamic allocation).

OK, so what happens if you absolutely promise to only use LT_mark in the outermost block of your function, as with your original example:

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);
}

That will work. Your only problem is how to enforce the restriction, including on all the maintenance programmers who will modify the code after you leave the project, and may not have the foggiest idea of why they're not allowed to nest LT_mark inside a block (or even know that they're not allowed to do that).

But if you like playing with fire, you can do it like this:

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

This will work, in the limited set of cases in which it does work, because the the compound literal created by the macro "has automatic storage duration associated with the enclosing block." (§6.5.2.5/5).

Honestly, I sincerely hope you don't use the above code. I contribute this answer mostly in the hopes that it provides some kind of explanation of the importance of understanding lifetimes.

rici
  • 234,347
  • 28
  • 237
  • 341