Co dzieje się z zadeklarowaną, niezainicjalizowaną zmienną w C? Czy to ma jakąś wartość?

Jeśli w C napiszę:

int num;

Zanim przypisam cokolwiek do num, czy wartość num jest nieokreślona?

10 answers

Zmienne statyczne (zakres pliku i funkcja statyczna) są inicjalizowane na zero:

int x; // zero
int y = 0; // also zero

void foo() {
    static int x; // also zero
}

Zmienne Niestatyczne (zmienne lokalne) są nieokreślone . Odczytanie ich przed przypisaniem wartości skutkuje niezdefiniowanym zachowaniem.

void foo() {
    int x;
    printf("%d", x); // the compiler is free to crash here
}

W praktyce mają tendencję do po prostu mieć jakąś nonsensowną wartość w tam początkowo - niektóre Kompilatory mogą nawet umieścić w określonych, stałych wartości, aby to oczywiste, gdy patrząc w debuggerze - ale ściśle mówiąc, kompilator jest wolny, aby zrobić wszystko od rozbijania się do przywoływania demonów przez wasze nosy.

Jeśli chodzi o to, dlaczego jest to nieokreślone zachowanie zamiast po prostu "niezdefiniowana / dowolna wartość", istnieje wiele architektur CPU, które mają dodatkowe bity znaczników w swojej reprezentacji dla różnych typów. Współczesnym przykładem może być Itanium, który ma bit " nic " w swoich rejestrach ; oczywiście, Projektory w standardzie C rozważały niektóre starsze architektury.

[[2]}Próba pracy z wartość z ustawionymi bitami znaczników może spowodować wyjątek procesora w operacji, która naprawdę nie powinna zawieść (np. dodanie liczby całkowitej lub przypisanie do innej zmiennej). A jeśli odejdziesz i zostawisz zmienną niezinicjalizowaną, kompilator może wychwycić jakieś losowe śmieci z ustawionymi bitami flagi - co oznacza, że dotknięcie tej niezinicjalizowanej zmiennej może być śmiertelne.
 166
Author: bdonlan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-21 05:40:28

0 jeśli statyczny lub globalny, nieokreślony, jeśli klasa pamięci jest auto

C zawsze bardzo dokładnie określał początkowe wartości obiektów. Jeśli globalny lub static, zostaną wyzerowane. Jeśli auto, wartość jest nieokreślona .

Tak było w kompilatorach pre-C89 i tak zostało określone przez K & R oraz w oryginalnym raporcie DMR C.

Tak było w C89, patrz sekcja6.5.7 Inicjalizacja .

Jeśli obiekt, który ma automatyczne przechowywanie czas trwania nie jest inicjowany explicitely, its value is nieokreślony. Jeśli obiekt, który ma statyczny czas przechowywania nie jest zainicjowany, jest inicjowane implicially jak gdyby każdy członkiem, który ma typ arytmetyczny były przypisane 0 i każdy członek, który ma typowi wskaźnika przypisano null stała wskaźnika.

Tak było w C99, patrz sekcja 6.7.8 Inicjalizacja .

Jeśli obiekt, który ma automatyczne czas przechowywania wynosi nie zainicjowany jawnie, jego wartość jest nieokreślony. Jeśli obiekt, który ma statyczny czas przechowywania nie jest zainicjalizowana jawnie, wtedy:
- jeśli posiada typ wskaźnika, jest inicjowany na wskaźnik null;
- jeśli ma arytmetykę type, it is initialized to (positive or unsigned) zero;
- jeśli jest zbiorcze, każdy członek jest inicjowany (rekurencyjnie) zgodnie z tymi rules;
- jeśli jest to związek, pierwszy nazwa członka jest inicjalizowana (rekurencyjnie) zgodnie z tymi Zasady.

Co dokładnie nieokreślony oznacza, nie jestem pewien Dla C89, C99 mówi:

3.17.2
wartość nieokreślona

albo nieokreślona wartość albo pułapka reprezentacja

Ale niezależnie od tego, co mówią standardy, w prawdziwym życiu, każda strona stosu faktycznie zaczyna się od zera, ale kiedy twój program patrzy na dowolną wartość klasy pamięci auto, widzi to, co zostało pozostawione przez twój własny program ostatnio używałem tych adresów. Jeśli przydzielisz wiele tablic auto, zobaczysz, że w końcu zaczną się porządnie od zer.

Możesz się zastanawiać, dlaczego tak jest? Inna więc odpowiedź dotyczy tego pytania, patrz: https://stackoverflow.com/a/2091505/140740
 53
Author: DigitalRoss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:19

Zależy to od czasu przechowywania zmiennej. Zmienna o statycznym czasie przechowywania jest zawsze domyślnie inicjalizowana przez zero.

Jeśli chodzi o zmienne automatyczne (lokalne), zmienna niezainicjalizowana ma wartość nieokreśloną . Nieokreślona wartość, między innymi, oznacza, że niezależnie od " wartości "można" zobaczyć " w tej zmiennej jest nie tylko nieprzewidywalne, to nawet nie jest gwarantowana stabilny . Przykładowo, w praktyce (tj. ignorując UB przez chwilę) to kod

int num;
int a = num;
int b = num;

Nie gwarantuje, że zmienne a i b otrzymają identyczne wartości. Co ciekawe, nie jest to jakaś pedantyczna koncepcja teoretyczna, łatwo dzieje się to w praktyce jako konsekwencja optymalizacji.

Ogólnie więc, popularna odpowiedź, że "jest zainicjowana tym, co było w pamięci", nie jest nawet w najmniejszym stopniu poprawna. Uninitialized zachowanie zmiennej różni się od zachowania zmiennej zainicjalizowanej ze śmieciami.

 10
Author: AnT,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-22 18:27:38

Ubuntu 15.10, Kernel 4.2.0, x86-64, GCC 5.2.1 przykład

Dość standardów, spójrzmy na implementację: -)

Zmienna lokalna

Standardy: nieokreślone zachowanie.

Implementacja: program przydziela przestrzeń stosu i nigdy nie przenosi niczego na ten adres, więc to, co było wcześniej, jest używane.

#include <stdio.h>
int main() {
    int i;
    printf("%d\n", i);
}

Kompiluj z:

gcc -O0 -std=c99 a.c

Wyjścia:

0

I dekompile z:

objdump -dr a.out

Do:

0000000000400536 <main>:
  400536:       55                      push   %rbp
  400537:       48 89 e5                mov    %rsp,%rbp
  40053a:       48 83 ec 10             sub    $0x10,%rsp
  40053e:       8b 45 fc                mov    -0x4(%rbp),%eax
  400541:       89 c6                   mov    %eax,%esi
  400543:       bf e4 05 40 00          mov    $0x4005e4,%edi
  400548:       b8 00 00 00 00          mov    $0x0,%eax
  40054d:       e8 be fe ff ff          callq  400410 <printf@plt>
  400552:       b8 00 00 00 00          mov    $0x0,%eax
  400557:       c9                      leaveq
  400558:       c3                      retq

Z naszej wiedzy o x86-64 wywołujących konwencje:

  • %rdi jest pierwszym argumentem printf, a więc łańcuch "%d\n" pod adresem 0x4005e4

  • %rsi jest drugim argumentem printf, zatem i.

    Pochodzi od -0x4(%rbp), która jest pierwszą 4-bajtową zmienną lokalną.

    W tym momencie, rbp jest na pierwszej stronie stosu został przydzielony przez jądro, więc aby zrozumieć, że wartość chcielibyśmy zajrzeć do kodu jądra i dowiedzieć się na co to ustawia.

    TODO czy jądro ustawia tę pamięć na coś przed ponownym użyciem jej dla innych procesów, gdy proces umiera? Jeśli nie, nowy proces byłby w stanie odczytać pamięć innych gotowych programów, wyciekając dane. Zobacz: czy niezinicjalizowane wartości są zagrożeniem bezpieczeństwa?

Możemy wtedy również grać z własnymi modyfikacjami stosu i pisać zabawne rzeczy, takie jak:]}

#include <assert.h>

int f() {
    int i = 13;
    return i;
}

int g() {
    int i;
    return i;
}

int main() {
    f();
    assert(g() == 13);
}

Global zmienne

Standardy: 0

Realizacja: .bss sekcja.

#include <stdio.h>
int i;
int main() {
    printf("%d\n", i);
}

gcc -00 -std=c99 a.c

Kompiluje do:

0000000000400536 <main>:
  400536:       55                      push   %rbp
  400537:       48 89 e5                mov    %rsp,%rbp
  40053a:       8b 05 04 0b 20 00       mov    0x200b04(%rip),%eax        # 601044 <i>
  400540:       89 c6                   mov    %eax,%esi
  400542:       bf e4 05 40 00          mov    $0x4005e4,%edi
  400547:       b8 00 00 00 00          mov    $0x0,%eax
  40054c:       e8 bf fe ff ff          callq  400410 <printf@plt>
  400551:       b8 00 00 00 00          mov    $0x0,%eax
  400556:       5d                      pop    %rbp
  400557:       c3                      retq
  400558:       0f 1f 84 00 00 00 00    nopl   0x0(%rax,%rax,1)
  40055f:       00

# 601044 <i> mówi, że i jest pod adresem 0x601044 i:

readelf -SW a.out

Zawiera:

[25] .bss              NOBITS          0000000000601040 001040 000008 00  WA  0   0  4

, który mówi 0x601044 znajduje się w środku .bss sekcji, która zaczyna się od 0x601040 i ma długość 8 bajtów.

Standard ELF gwarantuje, że sekcja o nazwie .bss jest całkowicie wypełniona zerami:

.bss Ta sekcja zawiera niezainicjalizowane dane, które przyczyniają się do obraz pamięci programu. Z definicji system inicjuje dane z zerami, gdy program zaczyna działać. Sekcja occu- Pie nie ma miejsca na pliki, jak wskazuje typ sekcji, SHT_NOBITS.

Ponadto Typ SHT_NOBITS jest wydajny i nie zajmuje miejsca na pliku wykonywalnym:

sh_size ten element podaje Rozmiar sekcji w bajtach. Chyba że sec- Typ tion to SHT_NOBITS , sekcja zajmuje sh_size bajtów w pliku. Sekcja typu SHT_NOBITS może mieć niezerową rozmiar, ale nie zajmuje miejsca w pliku.

Wtedy to do jądra Linuksa należy zerowanie tego regionu pamięci podczas ładowania programu do pamięci po uruchomieniu.

 5
Author: Ciro Santilli 新疆改造中心 六四事件 法轮功,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:16

To zależy. Jeśli ta definicja jest globalna (poza jakąkolwiek Funkcją), to num zostanie zainicjalizowana na zero. Jeśli jest lokalna (wewnątrz funkcji), to jej wartość jest nieokreślona. Teoretycznie, nawet próba odczytania wartości Ma nieokreślone zachowanie -- C pozwala na możliwość bitów, które nie przyczyniają się do wartości, ale muszą być ustawione w określony sposób, aby uzyskać zdefiniowane wyniki z odczytu zmiennej.

 3
Author: Jerry Coffin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-20 21:28:39

Podstawowa odpowiedź brzmi: tak, jest niezdefiniowana.

Jeśli widzisz dziwne zachowanie z tego powodu, może zależeć od tego, gdzie jest zadeklarowane. Jeśli wewnątrz funkcji na stosie, zawartość będzie bardziej niż prawdopodobne, że będzie inna za każdym razem, gdy funkcja zostanie wywołana. Jeśli jest to zakres statyczny lub modułowy, jest niezdefiniowany, ale nie ulegnie zmianie.

 1
Author: simon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-20 21:30:14

Jeśli Klasa storage jest statyczna lub globalna, to podczas ładowania BSS inicjalizuje zmienną lub lokalizację pamięci(ML) na 0, chyba że zmiennej wstępnie przypisano jakąś wartość. W przypadku zmiennych lokalnych niezainicjalizowanych reprezentacja pułapki jest przypisana do lokalizacji pamięci. Więc jeśli któryś z Twoich rejestrów zawierających ważne informacje zostanie nadpisany przez kompilator, program może się zawiesić.

Ale niektóre Kompilatory mogą mieć mechanizm pozwalający uniknąć takiego problemu.

Pracowałam z seria nec v850, kiedy zdałem sobie sprawę, że istnieje reprezentacja pułapki, która ma wzorce bitowe, które reprezentują niezdefiniowane wartości dla typów danych z wyjątkiem char. Kiedy wziąłem niezaliczalny znak, otrzymałem zero domyślnej wartości ze względu na reprezentację pułapki. Może to być przydatne dla any1 przy użyciu necv850es

 1
Author: hanish,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-30 10:29:53

Ponieważ komputery mają ograniczoną pojemność pamięci masowej, zmienne automatyczne są zazwyczaj przechowywane w elementach pamięci (rejestrach lub pamięci RAM), które wcześniej były używane do innych dowolnych celów. Jeśli taka zmienna zostanie użyta przed przypisaniem do niej wartości, magazyn może przechowywać to, co przechowywała wcześniej, a więc zawartość zmiennej będzie nieprzewidywalna.

Jako dodatkowy błąd, wiele kompilatorów może przechowywać zmienne w rejestrach, które są większe niż powiązane typy. Chociaż kompilator musiałby zapewnić, że każda wartość, która jest zapisana do zmiennej i odczytana z powrotem, zostanie obcięta i / lub rozszerzona do jej właściwego rozmiaru, Wiele kompilatorów wykona takie obcinanie, gdy zmienne są zapisywane i oczekuje, że zostanie ono wykonane przed odczytaniem zmiennej. Na takich kompilatorach coś w stylu:

uint16_t hey(uint32_t x, uint32_t mode)
{ uint16_t q; 
  if (mode==1) q=2; 
  if (mode==3) q=4; 
  return q; }

 uint32_t wow(uint32_t mode) {
   return hey(1234567, mode);
 }

Może bardzo dobrze spowodować wow() zapisanie wartości 1234567 w rejestrach Odpowiednio 0 i 1 oraz wywołanie foo(). Ponieważ {[4] }nie jest potrzebna wewnątrz "foo", a ponieważ funkcje mają umieszczać wartość zwracaną na rejestr 0, kompilator może przydzielić rejestr 0 do q. Jeżeli mode jest 1 lub 3, rejestr 0 zostanie załadowany odpowiednio 2 lub 4, ale jeśli jest jakiś inną wartość, funkcja może zwrócić to, co było w rejestrze 0 (tzn. wartość 1234567), nawet jeśli wartość ta nie mieści się w zakresie uint16_t.

Aby uniknąć konieczności wykonywania dodatkowej pracy przez kompilatory, aby zapewnić, że niewtajemniczeni zmienne nigdy nie przechowują wartości poza swoją domeną i unikają konieczności aby określić nieokreślone zachowania w nadmiernych szczegółach, Standard mówi użycie niezainicjowanych zmiennych automatycznych jest niezdefiniowanym zachowaniem. W w niektórych przypadkach konsekwencje tego mogą być nawet bardziej zaskakujące niż wartość jest poza zakresem swojego typu. Na przykład:

void moo(int mode)
{
  if (mode < 5)
    launch_nukes();
  hey(0, mode);      
}

Kompilator może to wywnioskować, ponieważ wywołanie moo() z trybem, który jest większa niż 3 nieuchronnie prowadzić do programu wywołującego Undefined Zachowania, kompilator może pominąć dowolny kod, który byłby tylko istotny Jeśli mode jest 4 lub większa, np. kod, który normalnie uniemożliwia uruchomienie Atomówek w takich przypadkach. Zauważ, że ani Standard, ani współczesnej filozofii kompilatora, zależy by na tym, że wartość zwracana z "Hej" jest ignorowany-akt próby zwrócenia daje kompilator nieograniczona licencja na generowanie dowolnego kodu.

 1
Author: supercat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-06 15:28:36

Wartość num będzie jakąś wartością śmieci z pamięci głównej (RAM). lepiej, jeśli zainicjujesz zmienną zaraz po utworzeniu.

 -1
Author: Shrikant Singh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-25 11:57:22

O ile przeszedłem to zależy głównie od kompilatora, ale ogólnie w większości przypadków wartość jest wstępnie przyjmowana jako 0 przez kompilatory.
Mam wartość śmieci w przypadku VC++ podczas gdy TC dał wartość jako 0. Drukuję jak poniżej

int i;
printf('%d',i);
 -3
Author: Rajeev Kumar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-27 19:34:39