Witamy w świecie optymalizacji kompilatorów!
Z powodu reguły "jak gdyby" kompilator musi tylko zbudować coś, co dałoby identyczne wyniki, co oryginalny kod. więc kompilator jeśli swobodę:
- usunąć nieużywane tablice
- usunąć pustej pętli
- przechowywać dynamiczne tablice z głównego zewnątrz stosu - bo główny jest specjalna funkcja, która zostanie wywołana tylko raz przez środowisko
Jeśli chcesz obserwować przepełnienie stosu (złe jednym, nie nasza nice site :-)), należy:
- użyć kodu, aby wypełnić tablice
- skompilować z wszystkimi optymalizacji usunięte i preferently w trybie debugowania, aby poinformować kompilator robić to, co napisałem tak dokładnie, jak to tylko możliwe
Poniższy kod robi SIGSEGV z dzyń 3.4.1 gdy kompilowany jako cc -g foo.c -o foo
#include <stdio.h>
#define SIZE 88388608
void fill(int *arr, size_t size, int val) {
for (size_t i=0; i<size; i++) {
arr[i] = val;
}
}
int main() {
int arr[SIZE];
int arr1[SIZE];
int arr2[SIZE];
fill(arr, SIZE, 0);
fill(arr1, SIZE, 0);
fill(arr2, SIZE, 0);
printf("%d %d %d\n", arr[12], arr1[15], arr2[18]);
return 0;
}
i nawet ten kod działa poprawnie, gdy skompilowany jako -O2
poziomu optymalizacji ... Kompilatory są teraz o sprytny dla mnie, a ja nie jestem na tyle odważny, aby dokładnie przyjrzeć się kodowi montażowemu, który byłby jedynym prawdziwym sposobem zrozumienia tego, co faktycznie zostało wykonane!
Po przepełnieniu stosu uzyskujesz * niezdefiniowane zachowanie *, które czasami może działać zgodnie z oczekiwaniami. Również kompilatory w tych dniach są dość inteligentne. Ponieważ te tablice są nieużywane, dlaczego kompilator rzeczywiście powinien dla nich utworzyć przestrzeń? –
Co się stanie, gdy zmienisz typ arr z 'int' na' volatile int'? –
Ponadto, AFAIK, rozmiar stosu może się różnić w zależności od systemu. –