2013-04-20 13 views
6

Używam gcc w wersji 4.7.2 na Ubuntu 12.10 x86_64.Zarządzanie pamięcią C w gcc

Przede wszystkim są to rozmiary typów danych na moim terminalu:

sizeof(char) = 1  

sizeof(short) = 2   sizeof(int) = 4 
sizeof(long) = 8   sizeof(long long) = 8 

sizeof(float) = 4   sizeof(double) = 8 
sizeof(long double) = 16 

Teraz proszę spojrzeć na ten fragment kodu:

int main(void) 
{ 
    char c = 'a'; 
    printf("&c = %p\n", &c); 
    return 0; 
} 

Jeśli się nie mylę to możliwe” t przewidzieć cokolwiek na temat adresu c. Ale za każdym razem ten program daje losowy adres heksadecymalny kończący się na f. Tak więc następna dostępna lokalizacja będzie miała wartość szesnastkową kończącą się na 0. Ten wzór zaobserwowałem również w przypadku innych typów danych. Dla wartości int adres był pewną wartością szesnastkową kończącą się na c. Dla double była to losowa wartość heksadecymalna kończąca się na 8 i tak dalej.

Mam więc 2 pytania tutaj.

1) Kto zarządza tego rodzaju przydzielaniem pamięci? Czy jest to standard gcc lub C?

2) Ktokolwiek to jest, dlaczego tak jest? Dlaczego zmienna jest przechowywana w taki sposób, że następna dostępna lokalizacja pamięci zaczyna się od wartości szesnastkowej kończącej się na 0? Jakieś szczególne korzyści?

Teraz proszę spojrzeć na ten fragment kodu:

int main(void) 
{ 
    double a = 10.2; 
    int b = 20; 
    char c = 30; 
    short d = 40; 

    printf("&a = %p\n", &a); 
    printf("&b = %p\n", &b); 
    printf("&c = %p\n", &c); 
    printf("&d = %p\n", &d); 

    return 0; 
} 

Teraz oto co zauważyłem jest zupełnie nowy dla mnie. Myślałem, że zmienna zostanie zapisana w tej samej kolejności, w jakiej zostały zadeklarowane. Ale nie! Nie o to chodzi. Oto przykładowe dane wyjściowe jednego z losowym Run:

&a = 0x7fff8686a698 
&b = 0x7fff8686a694 
&c = 0x7fff8686a691 
&d = 0x7fff8686a692 

Wydaje się, że zmienne get sortowane w kolejności rosnącej ich rozmiarów, a następnie są one przechowywane w tej samej kolejności posortowanej ale z utrzymaniem obserwację 1. czyli ostatnia zmienna (największa) zostaje zapisana w taki sposób, że następna dostępna lokalizacja pamięci jest wartością szesnastkową kończącą się na 0.

Oto moje pytania:

3) Kto za tym stoi? Czy jest to standard gcc lub C?

4) Po co tracić czas na sortowanie zmiennych, a następnie przydzielanie pamięci zamiast bezpośredniego przydzielania pamięci na zasadzie "kto pierwszy ten lepszy"? Jakieś szczególne korzyści z tego rodzaju sortowania, a następnie przydzielania pamięci?

Teraz proszę spojrzeć na ten fragment kodu:

int main(void) 
{ 
    char array1[] = {1, 2}; 
    int array2[] = {1, 2, 3}; 

    printf("&array1[0] = %p\n", &array1[0]); 
    printf("&array1[1] = %p\n\n", &array1[1]); 

    printf("&array2[0] = %p\n", &array2[0]); 
    printf("&array2[1] = %p\n", &array2[1]); 
    printf("&array2[2] = %p\n", &array2[2]); 

    return 0; 
} 

Teraz jest to również dla mnie szokujące. Co zauważyłem jest to, że tablica jest zawsze przechowywany w jakiś przypadkowy wartość hex kończące się na „0”, jeśli obserwacja elements of an array >= 2 a jeśli elements < 2 wtedy robi się miejsce w pamięci po 1.

Więc oto moje pytania:

5) Kto stoi za tym przechowywaniem tablicy przy pewnej losowej wartości heksadecymalnej kończącej się na 0 rzeczy? Czy jest to standard gcc lub C?

6) Dlaczego warto marnować pamięć? Chodzi mi o to, że array2 mógł być przechowywany natychmiast po array1 (a tym samym array2 miałby miejsce w pamięci kończące się na 2). Ale zamiast tego array2 jest przechowywany przy następnej wartości heksadecjalnej kończącej się na 0, pozostawiając 14 miejsc pamięci pomiędzy nimi. Jakieś konkretne korzyści?

+2

Nie ma "standardu". Może zależeć od wersji używanego kompilatora, ABI, jądra, środowiska, w którym uruchamiasz ten program, itp. ABI prawdopodobnie nakazuje, aby wskaźnik stosu był wyrównany do 16 bajtów, a kompilator umieścił zmienne w "jak chce ". A tablica 'array2' musi być wyrównana do słowa (wielokrotność 4 bajtów), podczas gdy' tablica1' nie musi być ... –

+1

Dlaczego numery pokoi po tej stronie korytarza są wszystkie * nawet *? Dlaczego numery pokoi po przeciwnej stronie korytarza są wszystkie * nieparzyste *? – wildplasser

+0

Dlaczego dokładnie pytasz? Jaki jest powód twojego pytania i dlaczego naprawdę cię to obchodzi? (Nie powinieneś kodować programów, które zależą tak ściśle od dokładnej przyczyny tego). –

Odpowiedz

5

Część przyczyn została określona w specyfikacjach application binary interface (ABI) dla procesora systemu .

Zobacz x86 calling conventions i SVR4 x86-64 ABI supplement (daję URL ostatniej kopii; ostatni oryginał jest zaskakująco trudne do znalezienia w sieci).

W ramach danej ramki połączenia kompilator może umieszczać zmienne w dowolnych gniazdach stosu. Może spróbować (podczas optymalizacji), aby zreorganizować stos do woli, np. przez zmniejszenie ograniczeń wyrównania. Nie powinieneś się o to martwić.

Kompilator próbuje umieścić lokalne zmienne w lokalizacji stosu z odpowiednim wyrównaniem. Zobacz rozszerzenie GCC z alignof. Gdzie dokładnie kompilator umieścił te zmienne nie jest ważne, zobacz my answer here. (Jeśli jest to ważne dla twojego kodu, naprawdę powinieneś spakować zmienne w jednym wspólnym lokalnym struct, ponieważ każda flaga kompilatora, wersja i flagi optymalizacyjne mogą robić różne rzeczy, więc nie zależą od tego konkretnego zachowania twojego konkretnego kompilatora).

7

Adres, pod którym stos i początek sterty jest przydzielany procesowi przez system operacyjny. O wszystkim innym decyduje kompilator, używając przesunięć, które są znane podczas kompilacji. Niektóre z tych rzeczy mogą być zgodne z istniejącą konwencją zastosowaną w architekturze docelowej, a niektóre z nich nie.

standardowy C niczego nie upoważnia dotyczące kolejności zmiennych lokalnych wewnątrz ramki stosu (jak wskazano w komentarzu, że nie ma nawet mandatu stosowanie stosie w ogóle). Standard tylko przeszkadza zdefiniować porządek, jeśli chodzi o struktury, a nawet wtedy nie definiuje konkretnych przesunięć, tylko fakt, że te przesunięcia muszą być w porządku rosnącym. Zwykle kompilatory próbują wyrównać zmienne w taki sposób, aby dostęp do nich wymagał jak najmniejszej ilości instrukcji procesora - i standard dopuszcza to, bez konieczności zlecania.

+0

Więc gcc jest za wszystkimi powyższymi 3 obserwacjami. Ok. Odpowiada to na pytanie 1), 3) i 5). Kompilator robi tego rodzaju przydział, aby skrócić czas pobierania. Ale to jest spokojne ogólne stwierdzenie. 1) Mam na myśli, jak dokładnie pomaga kompilator, jeśli zmienne są sortowane w rosnącej kolejności ich wielkości, a następnie przydziału pamięci? 2) W jaki sposób skraca czas pobierania lub jakąkolwiek korzyść, jaką ma zrobić, jeśli zmienne są przechowywane w taki sposób, że następna dostępna lokalizacja pamięci jest losową wartością szesnastkową kończącą się na "0"? – rootkea

+2

@rootkea - Zdarza się, że na twoim procesorze (x86) pobieranie danych jest najszybsze, jeśli adres jest parzystą wielokrotnością rozmiaru danych. W ten sposób informacje mogą przemieszczać się prosto w sprzęcie bez żadnych opóźnień potrzebnych do posortowania bitów. Na przykład, jeśli twoje "podwójne" znajduje się na końcu kończącym się na 4, procesor może odczytać dwa słowa 64-bitowe i wybrać cztery bajty z każdego odczytu. To może zająć trochę więcej czasu. –

+0

Norma C nie dyktuje, że zmienne automatyczne są przydzielane na stosie. W większości przypadków są, ale nie muszą. – wildplasser