Warum benötigt ein Long Int auf einigen Rechnern 12 Bytes?

26

Nach dem Kompilieren dieses Codes auf meinem Computer ist mir etwas Merkwürdiges aufgefallen:

#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;
}

Das Ergebnis ist das Folgende. Beachten Sie, dass zwischen jeder int-Adresse ein Unterschied von 4 Byte besteht. Zwischen dem letzten int und dem langen int besteht jedoch ein Unterschied von 12 Byte:

 Hello, World!
 The addresses are:

 da54dcac 
 da54dca8 
 da54dca4 
 da54dca0 
 da54dc9c 
 da54dc98 
 da54dc94 
 da54dc88
yoyo_fun
quelle
3
Fügen Sie ein weiteres intafter hin den Quellcode ein. Der Compiler kann es vorher in die Lücke setzen h.
Strg-Alt-Delor
32
Verwenden Sie den Unterschied zwischen den Speicheradressen nicht, um die Größe zu bestimmen. Dafür gibt es eine sizeofFunktion. printf("size: %d ", sizeof(long));
Chris Schneider
10
Sie drucken nur die niedrigen 4 Bytes Ihrer Adressen mit %x. Glücklicherweise funktioniert es auf Ihrer Plattform ordnungsgemäß, Zeigerargumente mit einer erwarteten unsigned intFormatzeichenfolge zu übergeben, aber Zeiger und Ints sind in vielen ABIs unterschiedlich groß. Verwenden Sie %pZeiger in portablen Code zu drucken. (Es ist leicht vorstellbar, dass Ihr Code die oberen / unteren Hälften der ersten 4 Zeiger anstelle der unteren Hälfte aller 8 druckt.)
Peter Cordes
5
@ChrisSchneider zum Drucken size_t verwenden%zu . @yoyo_fun zum Drucken von Adressen verwenden%p . Die Verwendung des falschen
Formatspezifizierers
2
@luu verbreiten keine Fehlinformationen. Kein anständiger Compiler kümmert sich um die Reihenfolge, in der Variablen in C deklariert werden. Wenn es ihn interessiert, gibt es keinen Grund, warum er es so machen würde, wie Sie es beschreiben.
gnasher729

Antworten:

81

Es dauerte nicht 12 Bytes nehmen, es dauerte nur 8 jedoch die Standard - Ausrichtung für ein 8 Byte langen int auf dieser Plattform ist 8 Byte. Aus diesem Grund musste der Compiler das lange int an eine durch 8 teilbare Adresse verschieben. Die "offensichtliche" Adresse da54dc8c ist nicht durch 8 teilbar, daher die Lücke von 12 Byte.

Sie sollten dies testen können. Wenn Sie ein weiteres int vor dem long hinzufügen, so dass es 8 davon gibt, sollten Sie feststellen, dass das long int ohne Verschiebung in Ordnung ist. Jetzt sind es nur noch 8 Bytes von der vorherigen Adresse.

Es ist wahrscheinlich erwähnenswert, dass Sie sich, obwohl dieser Test funktionieren sollte, nicht darauf verlassen sollten, dass die Variablen auf diese Weise organisiert werden. AC Compiler darf alle möglichen irren Dinge tun, um zu versuchen, dass Ihr Programm schnell ausgeführt wird, einschließlich der Neuordnung von Variablen (mit einigen Einschränkungen).

Alex
quelle
3
Unterschied, keine Lücke.
Deduplicator
10
msgstr "Variablen neu anordnen". Wenn der Compiler feststellt, dass Sie nicht zwei Variablen gleichzeitig verwenden, können Sie diese auch teilweise überlappen oder vollständig überlagern ...
Roger Lipscombe
8
Oder halten Sie sie in Registern statt auf dem Stapel.
Hören Sie auf, Monica
11
@OrangeDog Ich glaube nicht, dass das passieren würde, wenn die Adresse wie in diesem Fall genommen wird, aber im Allgemeinen sind Sie natürlich richtig.
Alex
5
@Alex: Wenn du die Adresse aufnimmst, kannst du mit dem Speicher und den Registern lustige Sachen machen. Die Adresse zu nehmen bedeutet, dass sie einen Speicherort haben muss, aber nicht, dass sie tatsächlich verwendet werden muss. Wenn Sie die Adresse nehmen, ihr 3 zuweisen und sie an eine andere Funktion übergeben, schreibt sie möglicherweise nur 3 in die RDI und ruft sie auf, ohne sie in den Speicher zu schreiben. Manchmal überraschend in einem Debugger.
Zan Lynx
9

Dies liegt daran, dass Ihr Compiler einen zusätzlichen Abstand zwischen den Variablen generiert, um sicherzustellen, dass sie im Speicher korrekt ausgerichtet sind.

Bei den meisten modernen Prozessoren ist der Zugriff auf einen Wert, dessen Adresse ein Vielfaches seiner Größe hat, effizienter. Wenn es han der ersten verfügbaren Stelle platziert worden wäre, wäre seine Adresse 0xda54dc8c, was kein Vielfaches von 8 ist, und daher weniger effizient zu verwenden gewesen. Der Compiler weiß darüber Bescheid und fügt zwischen den letzten beiden Variablen ein wenig ungenutzten Speicherplatz hinzu, um sicherzustellen, dass dies geschieht.

Jules
quelle
Danke für die Erklärung. Können Sie mich auf einige Materialien verweisen, aus denen sich der Zugriff auf Variablen mit mehreren Größen als effizienter erweist? Ich würde gerne wissen, warum das so ist.
yoyo_fun
4
@yoyo_fun und wenn Sie das Gedächtnis wirklich verstehen wollen, dann gibt es eine berühmte Zeitung zum Thema futuretech.blinkenlights.nl/misc/cpumemory.pdf
Alex
1
@yoyo_fun Es ist ganz einfach. Einige Speichercontroller können nur auf ein Vielfaches der Bitbreite des Prozessors zugreifen (z. B. kann ein 32-Bit-Prozessor nur die Adressen 0-3, 4-7, 8-11 usw. direkt anfordern). Wenn Sie nach einer nicht ausgerichteten Adresse fragen, muss der Prozessor zwei Speicheranforderungen stellen und dann die Daten in das Register aufnehmen. Wenn Sie also auf 32-Bit zurückkehren und einen Wert an Adresse 1 speichern möchten, muss der Prozessor nach den Adressen 0-3, 4-7 fragen und dann die Bytes von 1, 2, 3 und 4 abrufen. Vier Bytes von Speicher ausgelesen verschwendet.
Phyrfox
2
Ein kleiner Punkt, aber ein falsch ausgerichteter Speicherzugriff kann ein nicht behebbarer Fehler anstelle eines Leistungseinbruchs sein. Architektur abhängig.
Jon Chesterfield
1
@ JonChesterfield - Ja. Deshalb habe ich bemerkt, dass die Beschreibung, die ich gegeben habe, für die meisten modernen Architekturen gilt (womit ich hauptsächlich x86 und ARM gemeint habe). Es gibt andere, die sich unterschiedlich verhalten, aber sie sind wesentlich seltener. (Interessanter: ARM verwendet eine der Architekturen zu sein, die den Zugang ausgerichtet ist erforderlich, aber sie hinzugefügt automatische Handhabung von nicht ausgerichteten Zugriff in späteren Versionen)
Jules
2

Ihr Test testet nicht unbedingt, was Sie denken, da die Sprache nicht verpflichtet ist, die Adresse einer dieser lokalen Variablen miteinander in Beziehung zu setzen.

Sie müssten diese als Felder in eine Struktur einfügen, um auf die Speicherzuordnung schließen zu können.

Lokale Variablen müssen den Speicher nicht auf bestimmte Weise nebeneinander teilen. Der Compiler kann beispielsweise eine temporäre Variable an einer beliebigen Stelle im Stapel einfügen, die sich zwischen zwei dieser lokalen Variablen befinden kann.

Im Gegensatz dazu ist es nicht zulässig, eine temporäre Variable in eine Struktur einzufügen. Wenn Sie also stattdessen die Adressen von Strukturfeldern drucken, vergleichen Sie die beabsichtigten Elemente, die aus demselben logischen Speicherbereich (der Struktur) zugewiesen wurden.

Erik Eidt
quelle