Zgodnie z tym filmem (około 38 minuty), jeśli mam dwie funkcje z tymi samymi lokalnymi zmiennymi, będą one używać tej samej przestrzeni. Powinien więc wydrukować poniższy program 5
. Kompilowanie go z gcc
wynikami -1218960859
. czemu?
Program:
#include <stdio.h>
void A()
{
int a;
printf("%i",a);
}
void B()
{
int a;
a = 5;
}
int main()
{
B();
A();
return 0;
}
zgodnie z żądaniem, oto dane wyjściowe z deasemblera:
0804840c <A>:
804840c: 55 push ebp
804840d: 89 e5 mov ebp,esp
804840f: 83 ec 28 sub esp,0x28
8048412: 8b 45 f4 mov eax,DWORD PTR [ebp-0xc]
8048415: 89 44 24 04 mov DWORD PTR [esp+0x4],eax
8048419: c7 04 24 e8 84 04 08 mov DWORD PTR [esp],0x80484e8
8048420: e8 cb fe ff ff call 80482f0 <printf@plt>
8048425: c9 leave
8048426: c3 ret
08048427 <B>:
8048427: 55 push ebp
8048428: 89 e5 mov ebp,esp
804842a: 83 ec 10 sub esp,0x10
804842d: c7 45 fc 05 00 00 00 mov DWORD PTR [ebp-0x4],0x5
8048434: c9 leave
8048435: c3 ret
08048436 <main>:
8048436: 55 push ebp
8048437: 89 e5 mov ebp,esp
8048439: 83 e4 f0 and esp,0xfffffff0
804843c: e8 e6 ff ff ff call 8048427 <B>
8048441: e8 c6 ff ff ff call 804840c <A>
8048446: b8 00 00 00 00 mov eax,0x0
804844b: c9 leave
804844c: c3 ret
804844d: 66 90 xchg ax,ax
804844f: 90 nop
c
gcc
memory-management
elyashiv
źródło
źródło
Odpowiedzi:
Tak, tak, jest to niezdefiniowane zachowanie , ponieważ używasz zmiennej uninitialized 1 .
Jednak na architekturze x86, 2 , eksperyment ten powinien działać . Wartość nie jest „usuwana” ze stosu, a ponieważ nie jest zainicjowana w
B()
, ta sama wartość powinna nadal występować, pod warunkiem, że ramki stosu są identyczne.Zaryzykowałbym zgadywanie, że ponieważ
int a
nie jest używany w programievoid B()
, kompilator zoptymalizował ten kod, a 5 nigdy nie zostało zapisane w tym miejscu na stosie. Spróbuj również dodaćprintf
inB()
- po prostu może działać.Ponadto flagi kompilatora - a mianowicie poziom optymalizacji - prawdopodobnie również wpłyną na ten eksperyment. Spróbuj wyłączyć optymalizacje, przekazując
-O0
do gcc.Edycja: właśnie skompilowałem twój kod z
gcc -O0
(64-bitowym) i rzeczywiście, program wypisuje 5, jak oczekiwałby ktoś zaznajomiony ze stosem wywołań. W rzeczywistości działało nawet bez-O0
. Kompilacja 32-bitowa może zachowywać się inaczej.Zastrzeżenie: Nigdy, przenigdy nie używaj czegoś takiego w „prawdziwym” kodzie!
1 - Jest to debata dzieje pod uwagę, czy nie jest to oficjalnie „UB”, lub po prostu nieprzewidywalne.
2 - Również x64 i prawdopodobnie każda inna architektura używająca stosu wywołań (przynajmniej tych z MMU)
Spójrzmy na powód, dla którego to nie zadziałało. Najlepiej widać to w wersji 32-bitowej, więc skompiluję z
-m32
.Skompilowałem z
$ gcc -m32 -O0 test.c
(Optymalizacje wyłączone). Kiedy to uruchamiam, drukuje śmieci.Patrząc na
$ objdump -Mintel -d ./a.out
:Widzimy, że w
B
programie kompilator zarezerwował 0x10 bajtów miejsca na stosie i zainicjował nasząint a
zmienną na[ebp-0x4]
5.W
A
Jednak kompilator umieszczonyint a
na[ebp-0xc]
. Więc w tym przypadku nasze zmienne lokalne nie kończyły się w tym samym miejscu! Dodanieprintf()
wywołaniaA
również spowoduje, że ramki stosu dlaA
iB
będą identyczne i zostaną wydrukowane55
.źródło
To niezdefiniowane zachowanie . Niezainicjowana zmienna lokalna ma nieokreśloną wartość i użycie jej doprowadzi do niezdefiniowanego zachowania.
źródło
{ int uninit; &uninit; printf("%d\n", uninit); }
nadal ma niezdefiniowane zachowanie. Z drugiej strony, możesz traktować dowolny obiekt jako tablicęunsigned char
; czy to właśnie miałeś na myśli?Jedna ważna rzecz do zapamiętania - nigdy nie polegaj na czymś takim i nigdy nie używaj tego w prawdziwym kodzie! To po prostu interesująca rzecz (co nawet nie zawsze jest prawdą), a nie funkcja ani coś w tym rodzaju. Wyobraź sobie, że próbujesz znaleźć błąd wywołany przez tego rodzaju „funkcję” - koszmar.
Przy okazji. - C i C ++ są pełne tego rodzaju "funkcji", oto ŚWIETNY pokaz slajdów na ten temat: http://www.slideshare.net/olvemaudal/deep-c Jeśli chcesz zobaczyć więcej podobnych "funkcji", zrozum pod maską i jak to działa po prostu obejrzyj ten pokaz slajdów - nie pożałujesz i jestem pewien, że nawet większość doświadczonych programistów c / c ++ może się z tego wiele nauczyć.
źródło
W funkcji
A
zmiennaa
nie jest inicjalizowana, wypisanie jej wartości prowadzi do nieokreślonego zachowania.W niektórych kompilatorach zmienne
a
inA
ia
inB
znajdują się pod tym samym adresem, więc może drukować5
, ale znowu nie można polegać na niezdefiniowanym zachowaniu.źródło
s machine will be the same depends on the assembly generated by the compiler. As @JonathonReinhart pointed out the call to
B () `mogły zostać zoptymalizowane.5
, ale najwyraźniej Jonathon Reinhart ma o wiele lepsze wyjaśnienie.Skompiluj swój kod z
gcc -Wall filename.c
Zobaczysz te ostrzeżenia.In function 'B': 11:9: warning: variable 'a' set but not used [-Wunused-but-set-variable] In function 'A': 6:11: warning: 'a' is used uninitialized in this function [-Wuninitialized]
In c Drukowanie niezainicjowanej zmiennej prowadzi do niezdefiniowanego zachowania.
Sekcja 6.7.8 Inicjalizacja standardu C99 mówi
— if it has pointer type, it is initialized to a null pointer; — if it has arithmetic type, it is initialized to (positive or unsigned) zero; — if it is an aggregate, every member is initialized (recursively) according to these rules; — if it is a union, the first named member is initialized (recursively) according to these rules.
Edycja 1
As @Jonathon Reinhart Jeśli wyłączysz optymalizację za pomocą
-O
flagi,gcc-O0
możesz otrzymać wynik 5.Ale to wcale nie jest dobry pomysł, nigdy, przenigdy nie używaj tego w kodzie produkcyjnym.
-Wuninitialized
jest to jedno z cennych ostrzeżeń. Powinieneś wziąć to pod uwagę. Nie powinieneś ani wyłączać, ani pomijać tego ostrzeżenia, które prowadzi do ogromnych szkód w produkcji, takich jak awarie podczas uruchamiania demonów.Edycja 2
Wyjaśnienie slajdów z głębokiego C Dlaczego wynik to 5 / śmieci. Dodanie tych informacji z tych slajdów z niewielkimi modyfikacjami, aby ta odpowiedź była trochę bardziej skuteczna.
$ gcc -O0 file.c && ./a.out 5
Być może ten kompilator ma pulę nazwanych zmiennych, których używa ponownie. Np. Zmienna a została użyta i wypuszczona
B()
, wtedy gdyA()
potrzebuje nazw całkowitych,a
to otrzyma zmienną, która otrzyma to samo miejsce w pamięci. Jeśli zmienisz nazwę zmiennejB()
na, powiedzmyb
, nie sądzę, aby to dostałeś5
.Wiele rzeczy może się wydarzyć, gdy zadziała optymalizator. W tym przypadku myślę, że wywołanie
B()
można pominąć, ponieważ nie ma żadnych skutków ubocznych. Nie zdziwiłbym się również, gdyby znakA()
był wbudowanymain()
, tj. Nie ma wywołania funkcji. (Ale ponieważA ()
jest widoczny konsolidator, kod obiektowy funkcji musi być nadal utworzony na wypadek, gdyby inny plik obiektowy chciał się połączyć z funkcją). W każdym razie podejrzewam, że wydrukowana wartość będzie inna, jeśli zoptymalizujesz kod.gcc -O file.c && ./a.out 1606415608
Śmieci!
źródło