1

According to C99 standard 6.5.2.5 .9 the code:

int *p = (int []){2, 4};

initializes p to point to the first element of an array of two ints, the first having the value two and the second, four. The expressions in this compound literal are required to be constant. The unnamed object has static storage duration.

But what happens when we do something like this:

int* arr[100];
for (int a=0; a<100; a++) {
  arr[a] = (int []){2, 4};
}

is a new unnamed object create with each iteration of the loop or is the same oject used for each iteration?

Would the result be different if we did something like this:

int* ptr = NULL;
for (int a=0; a<100; a++) {
  ptr = (int []){2, 4};
}

The two possible options are: a new object is created each time the loop iterates or the same object is used for each loop iteration.

I am interested whether the behavior in this situation can somehow be deducted from what is written in the standard, or is it up to compiler to decide.

I have tested it under gcc 4.1.2 with this code:

int main(void) {
  int* arr[100];
  for (int a=0; a<10; a++) {
      arr[a] = (int []){2, 4};
      printf("%p ", arr[a]);
  }
  printf("\n");
}

And the result is:

0x7fff4c0010a0 0x7fff4c0010a0 0x7fff4c0010a0 0x7fff4c0010a0 0x7fff4c0010a0 0x7fff4c0010a0 0x7fff4c0010a0 0x7fff4c0010a0 0x7fff4c0010a0 0x7fff4c0010a0

I wrote some code to check caf's answer:

void fillArr(int* arr[]) {
  for (int a=0; a<4; a++) {
    arr[a] = (int []){a, a};
    printf("%p %d |  ", arr[a], arr[a][0]);
  }
}

void fillArr2(int* arr[]) {
  for (int a=0; a<4; a++) {
    int temp[] = { a, a };
    arr[a] = temp;
    printf("%p %d |  ", arr[a], arr[a][0]);
  }
}


int main(void) {
  int* arr[4];
  printf("\nfillarr1 function scope\n");
  fillArr(arr);


  printf("\nfillArr main scope\n");
  for (int a=0; a<4; a++) {
    printf("%p %d | ", arr[a], arr[a][0]);
  }

  printf("\nfillArr2 function scope\n");
  fillArr2(arr);

  printf("\nfillArr2 main scope\n");
  for (int a=0; a<4; a++) {
    printf("%p %d | ", arr[a], arr[a][0]);
  }

  printf("\n");
}

And the result is (called it with valgrind to detect memory errors):

==19110== Memcheck, a memory error detector
==19110== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==19110== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==19110== Command: ./a.out
==19110==

fillarr1 function scope
0x7ff000830 0 |  0x7ff000830 1 |  0x7ff000830 2 |  0x7ff000830 3 |
fillArr main scope
==19110== Use of uninitialised value of size 8
==19110==    at 0x3E33A41B1D: _itoa_word (in /lib64/libc-2.5.so)
==19110==    by 0x3E33A44F44: vfprintf (in /lib64/libc-2.5.so)
==19110==    by 0x3E33A4CAF9: printf (in /lib64/libc-2.5.so)
==19110==    by 0x400664: main (literalstest.c:26)
==19110==
==19110== Conditional jump or move depends on uninitialised value(s)
==19110==    at 0x3E33A41B27: _itoa_word (in /lib64/libc-2.5.so)
==19110==    by 0x3E33A44F44: vfprintf (in /lib64/libc-2.5.so)
==19110==    by 0x3E33A4CAF9: printf (in /lib64/libc-2.5.so)
==19110==    by 0x400664: main (literalstest.c:26)
==19110==
==19110== Conditional jump or move depends on uninitialised value(s)
==19110==    at 0x3E33A44FBE: vfprintf (in /lib64/libc-2.5.so)
==19110==    by 0x3E33A4CAF9: printf (in /lib64/libc-2.5.so)
==19110==    by 0x400664: main (literalstest.c:26)
==19110==
==19110== Conditional jump or move depends on uninitialised value(s)
==19110==    at 0x3E33A4574A: vfprintf (in /lib64/libc-2.5.so)
==19110==    by 0x3E33A4CAF9: printf (in /lib64/libc-2.5.so)
==19110==    by 0x400664: main (literalstest.c:26)
==19110==
==19110== Conditional jump or move depends on uninitialised value(s)
==19110==    at 0x3E33A43C49: vfprintf (in /lib64/libc-2.5.so)
==19110==    by 0x3E33A4CAF9: printf (in /lib64/libc-2.5.so)
==19110==    by 0x400664: main (literalstest.c:26)
==19110==
0x7ff000830 864144320 | 0x7ff000830 864144320 | 0x7ff000830 864144320 | 0x7ff000830 864144320 |
fillArr2 function scope
0x7ff000830 0 |  0x7ff000830 1 |  0x7ff000830 2 |  0x7ff000830 3 |
fillArr2 main scope
==19110== Use of uninitialised value of size 8
==19110==    at 0x3E33A41B1D: _itoa_word (in /lib64/libc-2.5.so)
==19110==    by 0x3E33A44F44: vfprintf (in /lib64/libc-2.5.so)
==19110==    by 0x3E33A4CAF9: printf (in /lib64/libc-2.5.so)
==19110==    by 0x4006B9: main (literalstest.c:34)
==19110==
==19110== Conditional jump or move depends on uninitialised value(s)
==19110==    at 0x3E33A41B27: _itoa_word (in /lib64/libc-2.5.so)
==19110==    by 0x3E33A44F44: vfprintf (in /lib64/libc-2.5.so)
==19110==    by 0x3E33A4CAF9: printf (in /lib64/libc-2.5.so)
==19110==    by 0x4006B9: main (literalstest.c:34)
==19110==
==19110== Conditional jump or move depends on uninitialised value(s)
==19110==    at 0x3E33A44FBE: vfprintf (in /lib64/libc-2.5.so)
==19110==    by 0x3E33A4CAF9: printf (in /lib64/libc-2.5.so)
==19110==    by 0x4006B9: main (literalstest.c:34)
==19110==
==19110== Conditional jump or move depends on uninitialised value(s)
==19110==    at 0x3E33A4574A: vfprintf (in /lib64/libc-2.5.so)
==19110==    by 0x3E33A4CAF9: printf (in /lib64/libc-2.5.so)
==19110==    by 0x4006B9: main (literalstest.c:34)
==19110==
==19110== Conditional jump or move depends on uninitialised value(s)
==19110==    at 0x3E33A43C49: vfprintf (in /lib64/libc-2.5.so)
==19110==    by 0x3E33A4CAF9: printf (in /lib64/libc-2.5.so)
==19110==    by 0x4006B9: main (literalstest.c:34)
==19110==
0x7ff000830 864144320 | 0x7ff000830 864144320 | 0x7ff000830 864144320 | 0x7ff000830 864144320 |
==19110==
==19110== HEAP SUMMARY:
==19110==     in use at exit: 0 bytes in 0 blocks
==19110==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==19110==

So the literals are available only inside the function they were declared in, go out of scope after leaving the function and after that accessing them is undefined behavior.

Dariusz
  • 21,561
  • 9
  • 74
  • 114
  • 1
    Well, did you try it? –  Jun 11 '13 at 05:45
  • But since the object "has static storage duration", I'd say that it will always be the same. –  Jun 11 '13 at 05:48
  • Yes I did, under gcc 4.1.2; but I'd like to know the general answer. I'll edit the question, thx. – Dariusz Jun 11 '13 at 05:48
  • Isn't it practically the same as with strings? Consider, in function scope `char a[] = "hello";`, where the array's life time is the function, and `const char *a = "hello";`, where the pointer's lifetime is the function, but the string literal is static. – ugoren Jun 11 '13 at 15:16
  • @ugoren The same as in which case? The first case seems to apply here, not the second. – Dariusz Jun 11 '13 at 16:38
  • 1
    Note that *scope* and *storage duration* are different concepts. Scope is associated with names, and does not apply to compound literals, because they are unnamed. *Storage duration* does apply to them, and in this case the storage duration ends at the end of the block in which they were declared (which may be before the end of the function, and in this case, it is). – caf Jun 11 '13 at 21:37
  • If you inlined the contents of fillArr and fillArr2, you'd still get the same results...it's not because they are in different functions, but because the compound literals are used outside of the block they were created in that they're invalid. In this code, each compound literal becomes invalid when you reach the closing brace of the loop body and proceed to the next iteration of the loop. – Theodore Murdock Jan 12 '17 at 23:30

1 Answers1

2

You are misreading the standard. The example you gave starts "The file scope definition...", but your code cannot appear at file scope.

§6.5.2.5 p6 says that if a compound literal occurs within a function body,

...it has automatic storage duration associated with the enclosing block.

So, there is no ambiguity. The compound literal in this case has automatic storage duration that lasts until the end of the loop block in which it is contained - conceptually a new compound literal is created and destroyed for each iteration of the loop, but since the lifetimes of those literals do not overlap, the implementation may re-use the same space. What you've written is no different to this:

int *arr[100];
for (int a=0; a<100; a++) {
  int temp[] = { 2, 4 };
  arr[a] = temp;
}

...it's just that in the compound literal case, the array is unnamed. The lifetime is the same.

caf
  • 233,326
  • 40
  • 323
  • 462
  • I extended my question with tests of your suggestion; I seems to not work this way, though my reasoning may be wrong. – Dariusz Jun 11 '13 at 13:30
  • @Dariusz: It is not legal to access the compound literal after the iteration of the loop in which it was declared, let alone after the function has exited. The fact that in your example you can still print an unchanged pointer value is neither here nor there. The implementation may freely re-use that space for any other variable. – caf Jun 11 '13 at 14:10
  • I rewrote the test, added real access to the memory from the literals and I have. Printing pointers was no proof at all, as you pointed out. – Dariusz Jun 11 '13 at 14:30
  • Given the code: `void test(void) { int i=0; LOOP: THING *p = &(THING){...}; use_thing(p); if (++i < 10) goto LOOP;} `, I see nothing in the Standard that would end the lifetime of the compound literal between invocations of `use_thing`; if one acknowledges that re-execution of the literal within the same function context should end the lifetime of the previous object created for that literal in that context, that would eliminate any real "problems" with having the lifetime extended to that of the outer function context. – supercat Dec 15 '17 at 22:06