1

I'm new to C. I cannot understand the result in following code. I use goto and jump the declaration of int a[N] array and int x. Although x is not initilized to 10, I can still get access to these variables.

#include <stdlib.h>
#include <stdio.h>
#define N 4

void printArray(int a[], int length) {
    for (int i = 0; i < length; i++) {
        printf("%d, ", a[i]);
    }
    printf("\n");
}


int main(void) {

    goto done;
    
    int a[N];
    int x=10;
    printf("x=%d\n", x);

    done:
    for (int i = 0; i < N; i++) {
        a[i] = i;
    }
    printArray(a, N);
    printf("x=%d\n", x);

    return EXIT_SUCCESS;
}

result

0, 1, 2, 3
x=0

My question:

Why I can get access to these variables whose declarations have been jumped? How are variables declared in C? It seems that variable declarations are not run line by line.

2
  • Do you just want to know how it is possible that a C program could behave this way? Or do you want us to look up this behavior in the C standard and see if it is required to behave this way? Commented Dec 23, 2021 at 5:14
  • @DavidGrayson I'm most curious about the first question. It will also be great if you could explain both. Thank you so much.
    – maplemaple
    Commented Dec 23, 2021 at 5:23

2 Answers 2

7

Aside from "Variable Length Arrays (VLAs)", an automatic variable's "lifetime extends from entry into the block with which it is associated until execution of that block ends in any way." (§6.2.4) The initialization (if any) occurs a bit later, when the program passes through the declaration. It's legal to jump over the declaration if the program does not depend on the initialization of the variable. But regardless of whether the initialization will eventually happen, the variable exists from the moment your program enters the block, however that is done. (Jumping into a block from outside is also legal, and may also prevent initialization. But as soon as you're in the block, the variable exists.)

If the program attempts to read the value of an uninitialised variable, it receives an indeterminate value. (Most compilers attempt to detect the possibility that this might happen, but you'll need to enable warnings in order to see the report.)

The consequence of "hoisting" the lifetime of a variable to its enclosing block is that there is a portion of the program in which the variable exists but is not visible (since its scope starts where it is defined.) If you save the address of the variable and then jump back into this region of the program, you will be able to access the value of the variable through the pointer, which shows that scope and lifetime are distinct.

If the variable is a VLA, then it's lifetime starts at the declaration and the compiler will not allow you to jump over the declaration. VLAs cannot be initialised, so you must assign a value to every element in a VLA which you intend to access. Not all compilers implement VLAs. Your example does not show a VLA, since N is a macro which expands to an integer constant.

2

For objective info about the C standard, see rici's answer.

But I see you are asking how this behavior is possible from a C program, and remarking that:

It seems that variable declarifications are not run line by line.

In fact, most computer languages are not run line by line. There is almost always some kind of multi-line parsing step that happens beforehand. For example, even the Bash shell language is processed multiple lines at a time. When Bash finds a while loop, it seems to do extra parsing to make sure the done command is found before it runs any of the code in the while loop. If you don't believe me, try running this Bash script:

while [ 1 ]
do
  echo hi
  sleep 1
# file terminates before the expected "done" command that ends the loop

Similarly, a C compiler is capable of parsing your code, checking its syntax, and checking that your program is well-formed before it even executes a single line of your code inside any of your functions. When the compiler sees that you are using a or x, it knows what those things are because you declared them above, even if the execution of the program never passed through those lines.

One way that you could imagine that a compiler might work is that (after checking the validity of your code) it moves all the local variable declarations to the top of the function. So your main function might look like this in the internal representation in the compiler:

int main(void) {
    int a[N];
    int x;
    int i;

    goto done;

    x = 10;
    printf("x=%d\n", x);

    done:
    for (i = 0; i < N; i++) {
        a[i] = i;
    }
    printArray(a, N);
    printf("x=%d\n", x);

    return EXIT_SUCCESS;
}

This could actually be a useful transformation that would help the compiler generate code for your function because it would make it easy for the compiler to allocate memory on the stack for all your local variables when the function starts.

Not the answer you're looking for? Browse other questions tagged or ask your own question.