lifetime of a variable when goto jumps back past its declaration

I have a couple of questions regarding lifetime of objects in c and c++.

When I compile the following code and execute it, I get different results depending on the optimization level I specify on the command line.

$ cat t1.c
#include <stdio.h>
int main(int ac, char** av) {
int *p = 0;
label1:
if (p) {
printf(“%d\n”, *p);
return 0;
}

int i = 999;
if (ac != 2) {
p = &i;
goto label1;
}
return -1;
}

$ clang t1.c -O0 && ./a.out

999

$ clang t1.c -O1 && ./a.out
1

It looks like the difference is caused by the lifetime start/end marker inserted at -O1. clang inserts lifetime.start at the variable initialization and lifetime.end before the jump to label1. Because the goto’s destination label1 is not in the lifetime range of variable “i”, the content of “i” gets altered when printf is executed.

This seems like a mis-compile according to the C standard:

“For such an object that does not have a variable length array type, its lifetime extends from entry into the block with which it is associated until execution of that block ends in any way.”

The block with which “i” is associated is the whole function, so “i” shouldn’t be destroyed when the goto jumps back past its declaration.

Is my understanding correct? I discussed this with a couple of people and we all agreed that this looks like an UB as a C code.

What if the code is compiled as a c++ code? Is it still UB?

If the variable in question had a non-trivial destructor, it would probably be UB. In n3337 6.7, there is a c++ code snippet that shows a goto jumping back past the declaration of a variable of type class “X”, and it states that the variable’s destructor gets called when it goes out of scope. However, I’m not sure what the rules are for variables that are POD types. The standard says in 6.6 Jump Statements that the object gets destructed in such cases:

Transfer out of a loop, out of a block, or back past an initialized variable with automatic storage duration involves the destruction of objects with automatic storage duration that are in scope at the point transferred from but not at the point transferred to.

Does “destructed” simply mean a destructor is called? Or it means the content of the object is destroyed regardless of whether or nor it’s a POD?

I have a couple of questions regarding lifetime of objects in c and c++.

When I compile the following code and execute it, I get different results
depending on the optimization level I specify on the command line.

$ cat t1.c
#include <stdio.h>
int main(int ac, char** av) {
  int *p = 0;
label1:
  if (p) {
    printf("%d\n", *p);
    return 0;
  }

  int i = 999;
  if (ac != 2) {
    p = &i;
    goto label1;
  }
  return -1;
}

$ clang t1.c -O0 && ./a.out
999

$ clang t1.c -O1 && ./a.out
1

It looks like the difference is caused by the lifetime start/end marker
inserted at -O1. clang inserts lifetime.start at the variable
initialization and lifetime.end before the jump to label1. Because the
goto's destination label1 is not in the lifetime range of variable "i", the
content of "i" gets altered when printf is executed.

This seems like a mis-compile according to the C standard:

"For such an object that does not have a variable length array type, its
lifetime extends from entry into the block with which it is associated
until execution of that block ends in any way."

The block with which "i" is associated is the whole function, so "i"
shouldn't be destroyed when the goto jumps back past its declaration.

Is my understanding correct? I discussed this with a couple of people and
we all agreed that this looks like an UB as a C code.

This seems to contradict what you just said. In C, this example appears to
have defined behavior, and we're miscompiling it.

What if the code is compiled as a c++ code? Is it still UB?

In C++, I'm confident that this is intended to be undefined behavior, but
the standard does not appear to clearly specify that. I've filed a core
issue.

If the variable in question had a non-trivial destructor, it would probably

I have a couple of questions regarding lifetime of objects in c and c++.

When I compile the following code and execute it, I get different results
depending on the optimization level I specify on the command line.

$ cat t1.c
#include <stdio.h>
int main(int ac, char** av) {
  int *p = 0;
label1:
  if (p) {
    printf("%d\n", *p);
    return 0;
  }

  int i = 999;
  if (ac != 2) {
    p = &i;
    goto label1;
  }
  return -1;
}

$ clang t1.c -O0 && ./a.out
999

$ clang t1.c -O1 && ./a.out
1

It looks like the difference is caused by the lifetime start/end marker
inserted at -O1. clang inserts lifetime.start at the variable
initialization and lifetime.end before the jump to label1. Because the
goto's destination label1 is not in the lifetime range of variable "i", the
content of "i" gets altered when printf is executed.

This seems like a mis-compile according to the C standard:

"For such an object that does not have a variable length array type, its
lifetime extends from entry into the block with which it is associated
until execution of that block ends in any way."

The block with which "i" is associated is the whole function, so "i"
shouldn't be destroyed when the goto jumps back past its declaration.

Is my understanding correct? I discussed this with a couple of people and
we all agreed that this looks like an UB as a C code.

This seems to contradict what you just said. In C, this example appears to
have defined behavior, and we're miscompiling it.

Ah right. It's not UB in C but I wasn't sure whether it's UB in C++.

What if the code is compiled as a c++ code? Is it still UB?

In C++, I'm confident that this is intended to be undefined behavior, but
the standard does not appear to clearly specify that. I've filed a core
issue.

OK, this is a mis-compile only when it's compiled as a C code then.

I agree with this analysis.

John.