2010-02-01:

Częściowa inicjacja tablic w C/C++

c:c++:easy:programming:asm:x86
Otrzymałem mejlowo (od luqa który zgodził się na publikacje korespondencji ;>) pytanie dotyczące wartości niezainicjowanych elementów częściowo zainicjowanej lokalnej tablicy:

Kod:
int tab[6] = {0}

Czy ZAWSZE jest pewność, że cała tablica będzie wyzerowana, czy nie jest to czasem kwestia kompilatora, dialektu języka czy czegokolwiek innego. Czy istnieje chodź najmniejsza szansa, że od [1..5] dostaniemy jakieś losowe dane (takie co leżały tam wcześniej w pamięci)?

Pytanie uznałem za ciekawe, szczególnie, że niby wiedziałem jak to działa, ale nigdy nie uznałem za stosowne się upewnić (tj. zajrzeć do standardu czy też sprawdzić na różnych wersjach GCC lub innych kompilatorów). Poniżej publikuje moją odpowiedź:

--quote--
Ad meritum, domyślam się, że chodzi o tablicę lokalną (gdyż w innych przypadkach tablica ta będzie wyzerowana niezależnie od jej inicjacji... hmm, ale nie jestem tu pewien co do tablic globalnych/lokalnych względem threadu (TLS)).

Kilka testów empirycznych:

gcc w wersji 4.4.1 x86-64 zeruje całą tablicę:
gcc w wersji 4.3.4 x86-64 zeruje całą tablicę:
      mov     QWORD PTR [rbp-48], 0
      mov     QWORD PTR [rbp-40], 0
      mov     QWORD PTR [rbp-32], 0

gcc w wersji 2.95.4 x86 zeruje całą tablicę:
      leal -24(%ebp),%edi
      xorl %eax,%eax
      cld
      movl $6,%ecx
      rep
      stosl

gcc w wersji 3.4.6 x86 zeruje całą tablicę:
      lea     %edi, [%ebp-40]
      cld
      mov     %edx, 0
      mov     %eax, 6
      mov     %ecx, %eax
      mov     %eax, %edx
      rep stosd

gcc w wersji 4.1.2 x86 zeruje całą tablicę:
      lea     %eax, [%ebp-28]
      mov     %edx, 0
      mov     DWORD PTR [%eax], %edx
      add     %eax, 4
      mov     DWORD PTR [%eax], %edx
      add     %eax, 4
      mov     DWORD PTR [%eax], %edx
      add     %eax, 4
      mov     DWORD PTR [%eax], %edx
      add     %eax, 4
      mov     DWORD PTR [%eax], %edx
      add     %eax, 4
      mov     DWORD PTR [%eax], %edx

Microsoft 32-bit C/C++ Optimizing Compiler w wersji 15.00.30729.01 zeruje tablice:
; Line 14
      mov     DWORD PTR _tab$[ebp], 0
      xor     eax, eax
      mov     DWORD PTR _tab$[ebp+4], eax
      mov     DWORD PTR _tab$[ebp+8], eax
      mov     DWORD PTR _tab$[ebp+12], eax
      mov     DWORD PTR _tab$[ebp+16], eax
      mov     DWORD PTR _tab$[ebp+20], eax

OK, to teraz trzeba rzucić okiem w standardy C, co one tam piszą:

ISO/IEC 9899:1999 (Second edition, 1999-12-01) paragraf 6.7.8, punkt 21:
If there are fewer initializers in a brace-enclosed list than there are elements or members of an aggregate, or fewer characters in a string literal used to initialize an array of known size than there are elements in the array, the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration.
Warto tu zacytować punkt 10 z tego samego paragrafu:
[...] If an object that has static storage duration is not initialized explicitly, then:
[...]
- if it has arithmetic type, it is initialized to (positive or unsigned) zero;

Czyli, wynika z tego, że standard nakazuje kompilatorowi wyzerowanie pozostałych elementów tablicy.

Rzucę jeszcze okiem na najnowszą wersję C99 i na C89 aka ANSI C...

Najpierw C89 (kurcze, nie wiem którą wersję mam), paragraf 3.5.7
If an object that has static storage duration is not initialized explicitly, it is initialized implicitly as if every member that has arithmetic type were  assigned 0 and every member that has pointer type were assigned a null pointer constant.
[...]
If there are fewer initializers in a list than there are members of an aggregate, the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration.

Czyli tak samo jak z C99.
Z tego co widzę, to w najnowszym dostępnym standardzie C99 (http://open-std.org/JTC1/SC22/WG14/www/docs/n1336.pdf) nic nie zmienili w tym punkcie w stosunku do wersji cytowanej przeze mnie na początku :)

Podsumowując: jeżeli kompilator trzyma się standardu w tym punkcie, to jest pewność, że tablica będzie wyzerowana.
Zarówno GCC we wszystkich wersjach testowanych przeze mnie jak i MS C/C++ compiler (aka Visual C++) trzymają się w tym punkcie standardu :)
--end of quote--

A nuż się komuś przyda :)

Comments:

2010-02-02 09:23:26 = luq
{
Jeszcze raz dzięki wielkie za szybką i rzeczową odpowiedź :)
}
2010-02-02 14:50:40 = Pafi
{
Ciekawa sprawa - dobrze to widać w takim zastosowaniu:

#include <stdio.h>

int main() {
int tab[6] = {1};
printf("tab[0] = %d
", tab[0]);
printf("tab[1] = %d
", tab[1]);
printf("tab[5] = %d
", tab[5]);
return 0;
}

Na wartość 1 inicjalizowany jest tylko pierwszy element. Program wypisze co następuje:

tab[0] = 1
tab[1] = 0
tab[5] = 0

(Sprawdzone na gcc 4.1.1)
}
2010-02-02 15:20:03 = dd3s
{
Ogólnie post ciekawy, but::
"Za równo GCC" ma być - zarówno.
A obiecywałeś poprawę::
2010-01-28 02:23:41 = Gynvael Coldwind
{
@rinz
Thx, postaram się zapamiętać :)
(lub dodać "disclaimer" a propos drobnych błędów w pisowni ;p)
}Sry
}
2010-02-02 16:13:50 = Gynvael Coldwind
{
@luq
Haha np i thx za ciekawe pytanie :)

@Pafi
Thx, masz rację, podany przez Ciebie przykład nawet lepiej ilustruje co się dzieje :)

@dd3s
Mea culpa! W ramach zadośćuczynienia znalazłem 3 "za równo" na blogu i poprawiłem :)
}
2010-02-05 17:00:30 = Fanael
{
W tytule jest C/C++, a sama notka mówi tylko o C, coś tu nie pasuje ;)

W każdym bądź razie, w C++ reguła brzmi nieco inaczej (8.5.1):
"If there are fewer initializers in the list than there are members in the aggregate, then each member not
explicitly initialized shall be value-initialized (8.5)."
Znaczenie "value-initialization" zaś jest takie:
"To value-initialize an object of type T means:
— if T is a class type (clause 9) with a user-declared constructor (12.1), then the default constructor for T is
called (and the initialization is ill-formed if T has no accessible default constructor);
— if T is a non-union class type without a user-declared constructor, then every non-static data member
and base-class component of T is value-initialized;
— if T is an array type, then each element is value-initialized;
— otherwise, the object is zero-initialized
A program that calls for default-initialization or value-initialization of an entity of reference type is ill-
formed. If T is a cv-qualified type, the cv-unqualified version of T is used for these definitions of zero-
initialization, default-initialization, and value-initialization."

Czyli generalnie wychodzi na to samo - PODy inicjalizowane zerami, pozostałe rzeczy konstruktorem domyślnym.
}

Add a comment:

Nick:
URL (optional):
Math captcha: 4 ∗ 2 + 1 =