-1
typedef struct test{
   char c_arr[1];
} test;

test array[1] = {{1}};

test get(int index){
 return array[index];
}

int main(){
  char* a =  get(0).c_arr;
  return *a;
}

In this post the behaviour is explained in C++: Accessing an array within a struct causes warnings with clang

Above code does not cause any warnings or errors when compiling with gcc or clang.

Is get(0).c_arr returning a pointer to a temporary variable which gets destroyed at the end of the expression? and if so, is dereferencing and returning its value UB? if it is then what would be a good way to fix it, maybe this?

test* get(int index){
 return &array[index];
}
Dan
  • 2,694
  • 1
  • 6
  • 19
  • 2
    It does not use a temporary (rightly automatic) variable; `array` has a static storage allocation; and the fact that `index` calculates from that does not change its allocation. That it copies from this calculation to a local (anonymous) location to reference c_arr, does not change from that. However, you are on a path to something dangerous; if your function returned `a` rather than `*a`, it would be doing something bad. – mevets Jul 07 '21 at 03:34
  • 1
    @mevets Why does `array` having a static storage allocation matter, when `get` returns a `struct test` by **value**? – Joseph Sible-Reinstate Monica Jul 07 '21 at 03:35
  • 1
    i'm confused here, in C++ this is a temp variable, but not in C? what rules makes this difference happen? – Dan Jul 07 '21 at 03:36
  • It's all a matter of **scope** when you boil it all down. Suggest a read of [C11 Standard - 6.2.4 Storage durations of objects](http://port70.net/~nsz/c/c11/n1570.html#6.2.4) Variables have a "lifetime" (the storage duration). It seems you are having trouble identifying the lifetime for the different storage types. There are no "temporary" objects in C. In C++ object construction creates true temporary objects in certain circumstances -- not so in C. – David C. Rankin Jul 07 '21 at 03:45
  • @JosephSible-ReinstateMonica it doesn't matter on its own. I was just trying to break down each step to show that at no point did it rely on improperly allocated memory, then point out how the next step could. – mevets Jul 07 '21 at 03:49
  • @mevets but it **does** use a temporary, that's what we are arguing on. – Dan Jul 07 '21 at 03:50
  • @Dan oops, just saw that the `*a` is out of scope. Ok, I read @Joshua 's answer then saw what I didn't see.... – mevets Jul 07 '21 at 03:51
  • @DavidC.Rankin Paragraph 8 of the very section you linked to talks about temporary lifetime, which seems to apply to exactly this case – Joseph Sible-Reinstate Monica Jul 07 '21 at 03:56
  • 2
    @DavidC.Rankin The problem with performing an experiment to prove or disprove UB is that one of the manifestations of UB is to work in exactly the way you might expect if there was no UB. So the experiment won't prove anything. – user3386109 Jul 07 '21 at 04:10
  • @JosephSible-ReinstateMonica - I think where everyone (maybe me) is getting wrapped around the axle is `array[]` is global, nothing temporary about it. It has member `c_arr` again, global and nothing temporary about it. The fact that `get()` returns the address of an element within the global is valid and non-temporary. It's the address of an element of the global. – David C. Rankin Jul 07 '21 at 04:10
  • @DavidC.Rankin `a` does hold the address of the `c_arr` member right, however, as soon as the expression is done, ie after `;`, `c_arr` is a pointer to garbage memory. what it was pointing to was temporary. array being global doesn't make a difference here. – Dan Jul 07 '21 at 04:11
  • @DavidC.Rankin `get(0).c_arr == array[0].c_arr` is false. – Joseph Sible-Reinstate Monica Jul 07 '21 at 04:13
  • 1
    Uugh, hold on, It may well be me. The function return is to an element of `array` (not an address in `array`). Now I'm seeing where the problem comes in (any why no sane person would code it this way) So the problem is you are assigning the address of `c_arr` (pointer to the first element) predicated on the function return itself. (which does use a true temporary to provide the address -- shit it was me `:)` – David C. Rankin Jul 07 '21 at 04:14
  • @user3386109 Well, an experiment can prove that there is UB, if something obviously wrong happens. It just can't ever prove that there isn't. – Joseph Sible-Reinstate Monica Jul 07 '21 at 04:28

2 Answers2

3
  char* a =  get(0).c_arr;
  return *a;

Is clearly UB; the memory referred to by a has been released from the stack by the time the return runs, and I would be really annoyed by the contrary result. Imagine if somebody wrote:

  while (true) {
  char a =  get(0).c_arr[0];
  //...
  a =  get(0).c_arr[0];
  //...
  a =  get(0).c_arr[0];
  //...
  a =  get(0).c_arr[0];
  //...
  a =  get(0).c_arr[0];
  //...
  }

The stack waste stinks and embedded programmers and the kernel programmers would be up in arms about it.

However, the following is valid in C: char a = get(0).c_arr[0]; This is because the temporary persists long enough to use in the expression.

Joshua
  • 40,822
  • 8
  • 72
  • 132
3

Yes, it's undefined behavior. The relevant section of the C17 standard is "6.2.4 Storage durations of objects":

The lifetime of an object is the portion of program execution during which storage is guaranteed to be reserved for it. An object exists, has a constant address,33) and retains its last-stored value throughout its lifetime.34) If an object is referred to outside of its lifetime, the behavior is undefined.

A non-lvalue expression with structure or union type, where the structure or union contains a member with array type (including, recursively, members of all contained structures and unions) refers to an object with automatic storage duration and temporary lifetime.36) Its lifetime begins when the expression is evaluated and its initial value is the value of the expression. Its lifetime ends when the evaluation of the containing full expression ends.

The expression get(0) is not an lvalue, and struct test contains c_arr, a member with array type, so it has temporary lifetime. This means that return *a; is UB because it accesses it outside of its lifetime.

Also, a big hint that this isn't allowed is that had you used char c; instead of char c_arr[1];, then char* a = &get(0).c; would have been a compile-time error, since you can't take the address of an rvalue, and what you wrote is basically morally equivalent to trying to do that.

I filed GCC bug 101358 and LLVM bug 51002 about not receiving warnings for this.