Dlaczego długi int zajmuje 12 bajtów na niektórych komputerach?

26

Zauważyłem coś dziwnego po skompilowaniu tego kodu na moim komputerze:

#include <stdio.h>

int main()
{
    printf("Hello, World!\n");

    int a,b,c,d;

    int e,f,g;

    long int h;

    printf("The addresses are:\n %0x \n %0x \n %0x \n %0x \n %0x \n %0x \n %0x \n %0x",
        &a,&b,&c,&d,&e,&f,&g,&h);

    return 0;
}

Wynik jest następujący. Zauważ, że między każdym adresem int występuje 4-bajtowa różnica. Jednak między ostatnim intem a długim intem istnieje 12-bajtowa różnica:

 Hello, World!
 The addresses are:

 da54dcac 
 da54dca8 
 da54dca4 
 da54dca0 
 da54dc9c 
 da54dc98 
 da54dc94 
 da54dc88
yoyo_fun
źródło
3
Wstaw kolejny intpo hw kodzie źródłowym. Kompilator może wcześniej wstawić go do luki h.
ctrl-alt-delor
32
Nie używaj różnicy między adresami pamięci, aby określić rozmiar. Jest do tego sizeoffunkcja. printf("size: %d ", sizeof(long));
Chris Schneider
10
Drukujesz tylko 4 bajty swoich adresów za pomocą %x. Na szczęście dla ciebie, zdarza się, że działa poprawnie na twojej platformie, aby przekazywać argumenty wskaźnikowe z oczekiwanym ciągiem formatu unsigned int, ale wskaźniki i liczby całkowite mają różne rozmiary w wielu ABI. Służy %pdo drukowania wskaźników w przenośnym kodzie. (Łatwo jest wyobrazić sobie system, w którym kod drukowałby górną / dolną połowę pierwszych 4 wskaźników zamiast dolnej połowy wszystkich 8.)
Peter Cordes,
5
@ChrisSchneider do wydrukowania użyj size_t%zu . @yoyo_fun do drukowania adresów użyj%p . Użycie niewłaściwego specyfikatora formatu wywołuje niezdefiniowane zachowanie
phuclv,
2
@luu nie rozpowszechnia dezinformacji. Żaden porządny kompilator nie dba o porządek, w którym zmienne są deklarowane w C. Jeśli to obchodzi, nie ma powodu, dla którego miałby to robić tak, jak to opisujesz.
gnasher729

Odpowiedzi:

81

Nie zajęło 12 bajtów, zajęło tylko 8. Jednak domyślne wyrównanie 8-bajtowego int na tej platformie to 8 bajtów. W związku z tym kompilator musiał przenieść long int na adres, który jest podzielny przez 8. Adres „oczywisty”, da54dc8c, nie jest podzielny przez 8, stąd 12-bajtowa przerwa.

Powinieneś być w stanie to przetestować. Jeśli dodasz kolejny int przed długim, więc jest ich 8, powinieneś zauważyć, że long int zostanie wyrównany bez ruchu. Teraz będzie tylko 8 bajtów z poprzedniego adresu.

Prawdopodobnie warto zauważyć, że chociaż ten test powinien działać, nie należy polegać na tak zorganizowanych zmiennych. Kompilator prądu przemiennego może wykonywać różne czynności, aby przyspieszyć działanie programu, w tym zmieniać kolejność zmiennych (z pewnymi zastrzeżeniami).

Alex
źródło
3
różnica, a nie przerwa.
Deduplicator
10
„w tym zmienne ponownego zamawiania”. Jeśli kompilator zdecyduje, że nie używasz dwóch zmiennych jednocześnie, można je częściowo nałożyć lub całkowicie nałożyć na siebie ...
Roger Lipscombe
8
A może trzymaj je w rejestrach zamiast na stosie.
Stop Harming Monica,
11
@OrangeDog Nie sądzę, aby tak się stało, gdyby adres został podany tak jak w tym przypadku, ale ogólnie rzecz biorąc, masz oczywiście rację.
Alex
5
@Alex: Możesz uzyskać zabawne rzeczy z pamięcią i rejestrami podczas przyjmowania adresu. Biorąc adres oznacza, że ​​musi nadać mu miejsce w pamięci, ale nie oznacza, że ​​musi go faktycznie użyć. Jeśli weźmiesz adres, przypiszesz mu 3 i przekażesz go innej funkcji, może po prostu zapisać 3 w RDI i zadzwonić, nigdy nie zapisując go w pamięci. Czasami zaskakujące w debuggerze.
Zan Lynx,
9

Wynika to z faktu, że kompilator generuje dodatkowe wypełnienie między zmiennymi, aby zapewnić ich prawidłowe wyrównanie w pamięci.

W większości nowoczesnych procesorów, jeśli wartość ma adres wielokrotności jej wielkości, bardziej efektywny jest dostęp do niej. Gdyby umieścił hw pierwszym dostępnym miejscu, jego adres to 0xda54dc8c, który nie jest wielokrotnością liczby 8, więc użycie go byłoby mniej wydajne. Kompilator wie o tym i dodaje trochę nieużywanego miejsca między dwiema ostatnimi zmiennymi, aby się upewnić.

Jules
źródło
Dziękuję za wyjaśnienie. Czy możesz wskazać mi materiały dotyczące powodów, dla których dostęp do zmiennych o wielu rozmiarach jest bardziej wydajny? chciałbym wiedzieć, dlaczego tak się dzieje?
yoyo_fun
4
@yoyo_fun, a jeśli naprawdę chcesz zrozumieć pamięć, to jest słynny artykuł na ten temat futuretech.blinkenlights.nl/misc/cpumemory.pdf
Alex
1
@yoyo_fun To całkiem proste. Niektóre kontrolery pamięci mają dostęp do wielokrotności szerokości bitów procesora (np. Procesor 32-bitowy może bezpośrednio żądać tylko adresów 0-3, 4-7, 8-11 itd.). Jeśli poprosisz o niewyrównany adres, procesor musi wysłać dwa żądania pamięci, a następnie pobrać dane do rejestru. Wracając do wersji 32-bitowej, jeśli chcesz mieć wartość przechowywaną pod adresem 1, procesor musi poprosić o adresy 0-3, 4-7, a następnie pobrać bajty z 1, 2, 3 i 4. Cztery bajty zmarnowana pamięć odczytu.
phyrfox
2
Drobny punkt, ale nierównomierny dostęp do pamięci może być nieodwracalnym błędem zamiast pogorszenia wydajności. Zależna od architektury.
Jon Chesterfield
1
@JonChesterfield - Tak. Dlatego skomentowałem, że opis, który podałem, dotyczy większości współczesnych architektur (przez co głównie miałem na myśli x86 i ARM). Są inne, które zachowują się na różne sposoby, ale są znacznie mniej powszechne. (Interesujące ARM stosuje się jedną z architektur wymagane wyrównane dostępu, ale dodaje się automatyczne manipulowanie niewyrównany dostępu w dalszych zmian)
Jules
2

Twój test niekoniecznie testuje to, co myślisz, ponieważ nie ma wymogu, aby język odnosił między sobą adres którejkolwiek z tych zmiennych lokalnych.

Musisz umieścić je jako pola w strukturze, aby móc wywnioskować coś o alokacji pamięci.

Zmienne lokalne nie są wymagane do dzielenia pamięci obok siebie w żaden szczególny sposób. Kompilator może wstawić zmienną tymczasową w dowolnym miejscu stosu, na przykład, która może znajdować się pomiędzy dowolnymi dwoma z tych zmiennych lokalnych.

W przeciwieństwie do tego, nie byłoby dozwolone wstawianie zmiennej tymczasowej do struktury, więc jeśli zamiast tego wydrukowałbyś adresy pól struct, porównywałbyś zamierzone elementy przydzielone z tego samego logicznego uchwytu pamięci (struktury).

Erik Eidt
źródło