2016-11-11 5 views
2

Ich bin neugierig, warum mein Code auf x86- und armeabi-Plattformen ein unterschiedliches Verhalten aufweist. Das Konzept des Code (es ist nicht wirklich Code, aber es ist genug, um das Problem zu verstehen):Speicherausrichtung beim Umwandeln verschiedener Datentypen

struct data 
{ 
    int x; 
} 

void method(unsigned char* buff) 
{ 
    data D; 

    memcpy(&D.x, buff, sizeof(int)); //good approach 
    D.x = *(int*)buff; //bad approach 
} 

Also, wenn dieser Code unter dem Arm Architektur von GCC kompiliert wird - es führt zu SIGFAULT (unaligned Speichern) an der Linie mit Casting von Datentypen, obwohl msvc-kompilierter Code in Ordnung ist. Soweit ich weiß, ist die einzige richtige Lösung in diesem Fall die Verwendung von memcpy. Kann jemand erklären, was zur Laufzeit wirklich passiert?

Antwort

3

Dies ist eine grundlegende Hardwarebeschränkung.

Einige CPUs können nur Hardwarebefehle ausführen, die nur auf entsprechend ausgerichtete Adressen 2, 4 (oder 8) Byte-Werte zugreifen. Das heißt, sie können keinen 4-Byte-Wert (zum Beispiel) von einer ungeraden Speicheradresse lesen. Diese Operation erzeugt eine Hardware-Ausnahme, die in ein Signal übersetzt wird. Alle Zugriffe auf 4-Byte-Werte müssen von physikalischen Adressen sein, die gleichmäßig durch 4 geteilt sind (zum Beispiel).

Die genauen Ausrichtungseinschränkungen variieren je nach CPU. So ist die CPU konzipiert. Hier ist ein theoretisches Beispiel. Nehmen wir an, dass auf einer bestimmten Hardwareplattform auf alle RAM als 32-Bit-Wörter zugegriffen wird. Aus Sicht der Programmierung sind es immer noch 8-Bit-Bytes, aber jedes RAM-Wort enthält vier Bytes. CPU-Anweisungen, die ein einzelnes Byte betreffen, werden von der CPU ausgeführt, indem das gesamte Speicherwort, das dieses Byte enthält, abgerufen, die Operation ausgeführt und dann zurückgespeichert wird. Es wird jedoch erwartet, dass eine Operation, die eine 4-Byte-Ganzzahl betrifft, zum Beispiel die logische Adresse für das erste Byte in diesem Speicherwort referenziert. Die CPU ruft das gesamte 4-Byte-Wort ab, führt die Operation aus und speichert sie dann zurück.

Das Endergebnis ist also, dass die CPU 4-Byte-Werte, die nicht an einer geraden 4-Byte-Grenze beginnen, nicht adressieren kann. Theoretisch wäre dies möglich, indem zwei benachbarte Speicherwörter abgerufen werden, die Operation ausgeführt wird, die den 4-Byte-Wert beeinflusst, der beide Wörter überlappt, und dann beide zurück im RAM gespeichert werden. Dies führt natürlich zu erheblichen Komplikationen, und einige CPUs sind dafür einfach nicht ausgelegt.

In Ihrem Beispiel wird die Zeigerdereferenz in eine direkte CPU-Anweisung übersetzt, die fehlschlägt, wenn die tatsächliche Speicheradresse ungerade ist (z. B.). memcpy() kopiert Byte für Byte und funktioniert.

+0

Sie sind cool. Thaks viel. –

1

C++ 11 hat die alignof Operator, die Sie (weil, wie Sam Varshavchik answered, die Hardware & instruction set und ABI haben Ausrichtung Einschränkungen) verwenden möchten. Und es hat auch die alignas Spezifizierer.

BTW, mit einem memcpy wie Sie tun, ist möglich, aber möglicherweise ineffizient (weil falsch ausgerichtete Datenbewegung langsamer als ausgerichtet ist). Sogar auf Hardware (insbesondere x86), bei der fehlausgerichtete Zugriffe (à la D.x = *(int*)buff;) möglich sind, ist es ineffizient (und könnte 10x langsamer sein).

In der Tat möchten Sie sicher sein, dass jeder Anruf zu Ihrem method einen entsprechend ausgerichteten Puffer bietet. Sie könnten alignas & alignof verwenden, verwenden Sie einige etc ... dafür.

Lesen Sie mehr über CPU cache. Sehen Sie sich auch die Antworten Abschnitt http://norvig.com/21-days.html (was eine sehr interessante Lektüre ist).

Verwandte Themen