2

Am I allowed to use a variable being initialized inside a designated initializer?

Consider the following listing:

struct A {
    int a;
    int * const a_ptr;
};

struct A foo(int a) {
    struct A result = {
        .a = a,
        .a_ptr = &result.a
    };

    return result;
}

demo

Am I allowed to use result in this designated initializer expression? Is this behavior defined? Is this code portable?

Update

My bad, the example contains a potential stack corruption. The listing should be:

struct A {
    int a;
    int * const a_ptr;
};

void foo(int a) {
    struct A result = {
        .a = a,
        .a_ptr = &result.a
    };

    bar(&result);
}
ivaigult
  • 6,198
  • 5
  • 38
  • 66
  • 4
    Beware that once *value* of `result` is returned, `a_ptr` will be a pointer to deallocated stack memory – user16217248 Oct 27 '21 at 15:23
  • 2
    Not any different than `void *p = &p;` and I believe there is a duplicate for that somewhere. – StoryTeller - Unslander Monica Oct 27 '21 at 15:25
  • @user16217248, you're right, I shouldn't be returning this struct. It's just for illustration purposes. – ivaigult Oct 27 '21 at 15:26
  • @ivaigult your question remains a bit unclear, there are two very different code snippets, so which one are you actually asking about? – Jabberwocky Oct 27 '21 at 15:48
  • @Jabberwocky the later one. I kept the old version just to be nice with people posted the first comments on this, so I don't invalidate their comments. The question is solely about the syntax construction and the order of initialization, it's not about the UB I introduced in the first version. – ivaigult Oct 27 '21 at 15:51
  • It depends which C version you are using; C90 will not allow it, but also doesn't have designated initializers. – Neil Oct 27 '21 at 17:27

2 Answers2

2

The initialization by itself is fine.

At the time result is declared, its address (as well as the addresses of its fields) is constant. So it is safe to use &result.a in the initializer of result.

What is a problem however is that you're returning a copy of this structure. This copy contains the address of a local variable that no longer exists, so attempting to use the value of the a_ptr member of the returned struct will trigger undefined behavior.

dbush
  • 205,898
  • 23
  • 218
  • 273
  • 1
    But is it OK to use the value of a previously initialized field? `struct {int a, b;} myStruct = {.a - 10, .b = a};` – user16217248 Oct 27 '21 at 15:30
  • 1
    @user16217248 I don't think the order of initialization is well-defined. – Eugene Sh. Oct 27 '21 at 15:32
  • @user16217248 That's a different case. The order of initialization is unspecified, so there's no guarantee that `.a` will be set before `.b`. – dbush Oct 27 '21 at 15:33
  • @dbush I think it's not quite true. The order is [defined](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf) in 6.7.9 paragraph 11. "In contrast, a designation causes the following initializer to begin initialization of the subobject described by the designator. Initialization then continues forward in order, beginning with the next subobject after that described by the designator." – ivaigult Oct 27 '21 at 15:46
  • 1
    @ivaigult That clause is telling you which subobjects an initializer is associated with, not evaluation order. p23 talks about evaluation: *"The evaluations of the initialization list expressions are indeterminately sequenced with respect to one another and thus the order in which any side effects occur is unspecified."* – dbush Oct 27 '21 at 15:52
1

The question has changed completely, here is my new answer:

Your code is fine, you can check this, it won't assert on any platform.

You pass the pointer to the local variable result to bar. In bar that local variable still exists p points to that variable (result). Therefore the a_ptr still points to result.a.

But I'm just wondering what you're trying to achieve here.

#include <assert.h>

struct A {
  int a;
  int* const a_ptr;
};    

void bar(struct A *p)
{
  assert(p->a_ptr == &p->a);
}    

void foo(int a) {
  struct A result = {
      .a = a,
      .a_ptr = &result.a
  };

  bar(&result);
}

int main()
{
  foo(2);
}

BTW:

 struct A result = {
      .a = a,
      .a_ptr = &result.a
  };

is equivalent to this:

  struct A result;  
  result.a = a;
  result.a_ptr = &result.a;

but for latter you'd need to declare int* a_ptr; instead of int* const a_ptr;.

Jabberwocky
  • 48,281
  • 17
  • 65
  • 115