-1

I tried the two following pieces of code:

void swap_woPointer()
{ 
    int a=5;
    static int b=5;
    printf("value of a : %d , value of b: %d \n", a, b);
    printf("address of a: %p , address of b %p \n", &a, &b);

    a++;
    b++;
}



void main(int argc, char *argv[]) 
{
    int ii;
    for (ii=0; ii<10; ii++){
        swap_woPointer();
    }

}

and

void swap_woPointer()
{ 
    int a;
    static int b;
    printf("value of a : %d , value of b: %d \n", a, b);
    printf("address of a: %p , address of b %p \n", &a, &b);

    a++;
    b++;
}



void main(int argc, char *argv[]) 
{
    int ii;
    for (ii=0; ii<10; ii++){
        swap_woPointer();
    }

}

The only difference between the two pieces of code is that once I only declared the variables a and b

int a;
static int b;

and in the other case I defined them

int a=5;
static int b=5;

The output that I obtain is different for the two cases. In the case in which I only declared the variables, I obtain

value of a : 0 , value of b: 0 
address of a: 0xffffcbbc , address of b 0x100407000 
value of a : 1 , value of b: 1 
address of a: 0xffffcbbc , address of b 0x100407000 
value of a : 2 , value of b: 2 
address of a: 0xffffcbbc , address of b 0x100407000 
value of a : 3 , value of b: 3 
address of a: 0xffffcbbc , address of b 0x100407000 
value of a : 4 , value of b: 4 
address of a: 0xffffcbbc , address of b 0x100407000 
value of a : 5 , value of b: 5 
address of a: 0xffffcbbc , address of b 0x100407000 
value of a : 6 , value of b: 6 
address of a: 0xffffcbbc , address of b 0x100407000 
value of a : 7 , value of b: 7 
address of a: 0xffffcbbc , address of b 0x100407000 
value of a : 8 , value of b: 8 
address of a: 0xffffcbbc , address of b 0x100407000 
value of a : 9 , value of b: 9 
address of a: 0xffffcbbc , address of b 0x100407000

whereas if I define the variables rightaway, I obtain

value of a : 5 , value of b: 5 
address of a: 0xffffcbbc , address of b 0x100402010 
value of a : 5 , value of b: 6 
address of a: 0xffffcbbc , address of b 0x100402010 
value of a : 5 , value of b: 7 
address of a: 0xffffcbbc , address of b 0x100402010 
value of a : 5 , value of b: 8 
address of a: 0xffffcbbc , address of b 0x100402010 
value of a : 5 , value of b: 9 
address of a: 0xffffcbbc , address of b 0x100402010 
value of a : 5 , value of b: 10 
address of a: 0xffffcbbc , address of b 0x100402010 
value of a : 5 , value of b: 11 
address of a: 0xffffcbbc , address of b 0x100402010 
value of a : 5 , value of b: 12 
address of a: 0xffffcbbc , address of b 0x100402010 
value of a : 5 , value of b: 13 
address of a: 0xffffcbbc , address of b 0x100402010 
value of a : 5 , value of b: 14 
address of a: 0xffffcbbc , address of b 0x100402010

I do not understand where the difference comes from. It somehow has to be related to the memory allocation. I thought that in both cases I should obtain the same result, e.g. the variable a, which is declared not to be static, should be allocated once every time the function is called. Apparently this is only the case when the variable is directly defined and not merely declared.

anonymous
  • 409
  • 1
  • 4
  • 14

2 Answers2

3

int a; gives you a variable with indeterminate value. What will happen if you print an indeterminate value is unspecified behavior, as explained here. The value 0 is by no means guaranteed. You can get any value and the value may differ from time to time, even if the program has not been re-compiled.

Often some compilers, when set for debug build, zero-out all memory even if it is uninitialized. This could explain why the value seems deterministic. When you later switch to release build, you could get garbage instead. This phenomenon is good to know, as it is a common explanation why code breaks in release build: there's some variable you forgot to initialize and switching to release build exposed the bug.

In both cases a is allocated on the local stack.

As for b, there is a rule stating that all static storage duration variables that aren't initialized by the programmer, must be initialized to zero. Therefore in case of static int b; you get the value 0, which is actually guaranteed, unlike in the case of int a;.

Furthermore, compilers allocate static storage duration variables in different segments depending on if they are initialized to 0 or to some other value. Those initialized to zero end up in a segment called .bss, those initialized to a value end up in .data. This is why you get a different address for b in the two different cases: static int b; gets allocated in .bss and static int b=5; gets allocated in .data.


Notes to pedantic readers:

  • The code does actually not contain undefined behavior, as explained in the linked post. The address of the local variable is taken. This assuming a mainstream system without any trap representation of two's complement integers.

  • The C standard does not guarantee where variables are allocated. .stack, .bss and .data are industry de facto standard names, but not enforced by the C language standard.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • “The code does actually not contain undefined behavior” is an over-simplification. It is explicitly undefined in C89, and C99 is ambiguous (hence the changes between https://port70.net/~nsz/c/c11/n1570.html#6.3.2.1p2 and https://port70.net/~nsz/c/c99/n1256.html#6.3.2.1p2 ). – Pascal Cuoq Sep 26 '18 at 09:33
  • @PascalCuoq That part has only to do with lvalues and the special case of a variable which does not have it's address taken. It does not specify what happens otherwise, because that part is covered 6.2.4 §6 "The initial value of the object is indeterminate." This is an improvement from C90 6.1.2.4 which was completely ambiguous. The most important part being that nowhere in the C standard will you find normative text claiming that accessing an indeterminate value invokes undefined behavior, apart from the uninteresting special case of trap representations. – Lundin Sep 26 '18 at 12:55
  • In C90, the fact that accessing indeterminate memory was UB was in the very definition of UB, 1.6: “Undefined behavior — behavior, upon use of a nonportable or erroneous program construct, of erroneous data, or of indeterminately-valued objects […]”: https://port70.net/~nsz/c/c89/c89-draft.html#1.6 – Pascal Cuoq Sep 27 '18 at 07:19
0

Uninitialized local non-static variables (also called "automatic" variables) are truly uninitialized. They will have an indeterminate (and seemingly random) value. Don't use uninitialized variables.

On the other hand, local static variables will be automatically zero-initialized.


What happens with the case of the uninitialized a variable, it simply happens to be the value of whatever is in the memory where the variable is located.

If you call other functions using local (and initialized) variables between, then you could get other (and unpredictable) results.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • But somehow the behaviour seems to be deterministic since the program reproduces the same results – anonymous Sep 26 '18 at 08:01
  • @anonymous no need to spam the same comment everywhere. – Swordfish Sep 26 '18 at 08:01
  • @swordfish no need to complain if I deleted the other comments ;) – anonymous Sep 26 '18 at 08:02
  • what do you mean by "value of whatever is in the memory" - who knows if it is in memory... – Antti Haapala -- Слава Україні Sep 26 '18 at 08:03
  • 1
    @anonymous Assuming the value \*is\* in memory the automatic variable will on every call of the function be stored at the same memory address so its value is preserved. But that is pure luck and implementation defined and not covered by the standard. Reading an uninitialized variable is undefined behaviour. – Swordfish Sep 26 '18 at 08:05
  • @AnttiHaapala That's the point. The contents of the memory is whatever is in the memory, but no-one can say for certain what it might be. – Some programmer dude Sep 26 '18 at 08:06
  • @Swordfish IIRC just *reading* the value might not be UB, unless the value happens to be a trap-representation. This is different from C++. – Some programmer dude Sep 26 '18 at 08:07
  • 1
    ISO/IEC9899:2017 - N2176, 6.3.2.1/2: "If the lvalue designates an object of automatic storage duration that could have been declared with the register storage class (never had its address taken), and that object is uninitialized (not declared with an initializer and no assignment to it has been performed prior to use), the behavior is undefined." – Swordfish Sep 26 '18 at 08:15
  • 1
    More clearly in J.2 Undefined behavior: "An lvalue designating an object of automatic storage duration that could have been declared with the register storage class is used in a context that requires the value of the designated object, but the object is uninitialized." – Swordfish Sep 26 '18 at 08:18
  • 3
    @Swordfish Indeed, and this variable's address is taken. So it is not UB but merely unspecified behavior. Unless there's trap representations, which isn't the case in the real world of 2's complement integer computers. – Lundin Sep 26 '18 at 08:18
  • 2
    @Lundin "and this variable's address is taken" Damned. Didn't look at the code. Was under the impression just the value was printed. Well ... sometimes you lose, sometimes the other wins. – Swordfish Sep 26 '18 at 08:20
  • 1
    Ah but the behaviour *is* undefined... [DR 451](http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_451.htm) – Antti Haapala -- Слава Україні Sep 26 '18 at 08:53