2009-11-04 16 views
14

Während ein Code-Review Ich habe über einige Codes kommen, die eine einfache Struktur wie folgt definiert:C++ Daten Mitglied Alignment und Array-Verpackung

class foo { 
    unsigned char a; 
    unsigned char b; 
    unsigned char c; 
} 

An anderer Stelle ein Array dieser Objekte definiert:

foo listOfFoos[SOME_NUM]; 

Später werden die Strukturen in einen Puffer-raw kopiert:

memcpy(pBuff,listOfFoos,3*SOME_NUM); 

Dieser Code stützt sich auf die assumpti ons, die: a.) Die Größe von foo ist 3, und keine Auffüllung wird angewendet, und b.) Ein Array dieser Objekte wird ohne Polsterung zwischen ihnen gepackt.

Ich habe es mit GNU auf zwei Plattformen (RedHat 64b, Solaris 9) versucht, und es funktionierte auf beiden.

Sind die obigen Annahmen gültig? Wenn nicht, unter welchen Bedingungen (z. B. Änderung in OS/Compiler) können sie fehlschlagen?

+1

Und jemand erfunden Std: Vektor ... –

+0

@Matthieu: Danke für die Erinnerung an uns. Ich bin sicher, dass das OP das übersehen hatte. – nus

Antwort

16

Ein Array von Objekten muss zusammenhängend sein, so dass zwischen den Objekten niemals ein Abstand hergestellt wird, obwohl am Ende eines Objekts ein Padding eingefügt werden kann (wodurch fast der gleiche Effekt erzeugt wird).

Angesichts der Tatsache, dass Sie mit Char's arbeiten, sind die Annahmen wahrscheinlich meistens richtig, aber der C++ - Standard garantiert dies sicherlich nicht. Ein anderer Compiler oder auch nur eine Änderung der Flags, die an Ihren aktuellen Compiler übergeben werden, könnte dazu führen, dass Padding zwischen den Elementen der Struktur eingefügt wird oder dem letzten Element der Struktur folgt oder beides.

+1

Es würde mich sicherlich nicht überraschen, wenn ein Compiler entschieden hätte, dass es Dinge an Vier-Byte-Grenzen mochte, und legte ein Byte Padding an das Ende. –

+0

Leider die meisten nicht. – Crashworks

20

Es wäre auf jeden Fall sicherer zu machen:

sizeof(foo) * SOME_NUM 
+2

nicht nur sicherer, sondern klarer und los geht eine magische Zahl. +1 – rmeador

+0

Ja, dem stimme ich zu. Ich glaube, ich habe mehr versucht, die Padding- und Array-Organisation zu erreichen. Vielen Dank. –

+1

dies berücksichtigt jedoch nicht das Auffüllen zwischen Array-Elementen. – nschmidt

2

ich sicher haben würde gewesen und ersetzt die magische Zahl 3 mit einem sizeof(foo) ich rechnen.

Meine Vermutung ist, dass Code, der für zukünftige Prozessorarchitekturen optimiert ist, wahrscheinlich eine Art von Padding einführt.

Und versuchen, diese Art von Fehler zu finden, ist ein echter Schmerz!

1

Wie andere gesagt haben, ist die Verwendung von sizeof (foo) eine sicherere Wette. Einige Compiler (insbesondere esoterische in der eingebetteten Welt) fügen Klassen einen 4-Byte-Header hinzu. Andere können, je nach Compiler-Einstellungen, funky Memory-Alignment-Tricks machen.

Für eine Mainstream-Plattform sind Sie wahrscheinlich in Ordnung, aber es ist keine Garantie.

5

Wenn Sie Ihr Array wie folgt kopieren sollten Sie

memcpy(pBuff,listOfFoos,sizeof(listOfFoos)); 

verwenden Dies wird immer so lange arbeiten, wie Sie pBuff auf die gleiche Größe zugeordnet. Auf diese Weise machen Sie überhaupt keine Annahmen über Auffüllen und Ausrichtung.

Die meisten Compiler richten eine Struktur oder Klasse an die erforderliche Ausrichtung des größten eingeschlossenen Typs aus. In Ihrem Fall von Zeichen bedeutet das keine Ausrichtung und Auffüllung, aber wenn Sie beispielsweise eine Abkürzung hinzufügen, wäre Ihre Klasse 6 Byte groß, wobei zwischen dem letzten Zeichen und Ihrer Abkürzung ein Byte Auffüllung hinzugefügt wird.

2

Es kommt auf die Speicherausrichtung an.Typische 32-Bit-Maschinen lesen oder schreiben 4 Byte Speicher pro Versuch. Diese Struktur ist vor Problemen geschützt, da sie problemlos unter diese 4 Byte fällt, ohne verwirrende Auffüllprobleme.

Wenn nun die Struktur war als solche:

class foo { 
    unsigned char a; 
    unsigned char b; 
    unsigned char c; 
    unsigned int i; 
    unsigned int j; 
} 

Ihre Mitarbeiter Logik wahrscheinlich

memcpy(pBuff,listOfFoos,11*SOME_NUM); 

(3 Char = 3 Bytes, 2 ints = 2 * 4 Bytes, also 3 führen würde + 8)

Leider nimmt die Struktur aufgrund von Padding tatsächlich 12 Bytes auf. Dies liegt daran, dass Sie drei Zeichen und ein int in dieses 4-Byte-Wort nicht einfügen können, und so gibt es dort ein Byte gepolsterten Speicherplatz, der den Int in sein eigenes Wort drückt. Dies wird immer mehr zu einem Problem, je vielfältiger die Datentypen werden.

4

Ich denke, der Grund, dass dies funktioniert, weil alle Felder in der Struktur char sind, die eine ausrichten. Wenn es mindestens ein Feld gibt, das nicht 1 ausrichtet, ist die Ausrichtung der Struktur/Klasse nicht 1 (die Ausrichtung hängt von der Feldreihenfolge und -ausrichtung ab).

Lassen Sie einige Beispiele sehen:

#include <stdio.h> 
#include <stddef.h> 

typedef struct { 
    unsigned char a; 
    unsigned char b; 
    unsigned char c; 
} Foo; 
typedef struct { 
    unsigned short i; 
    unsigned char a; 
    unsigned char b; 
    unsigned char c; 
} Bar; 
typedef struct { Foo F[5]; } F_B; 
typedef struct { Bar B[5]; } B_F; 


#define ALIGNMENT_OF(t) offsetof(struct { char x; t test; }, test) 

int main(void) { 
    printf("Foo:: Size: %d; Alignment: %d\n", sizeof(Foo), ALIGNMENT_OF(Foo)); 
    printf("Bar:: Size: %d; Alignment: %d\n", sizeof(Bar), ALIGNMENT_OF(Bar)); 
    printf("F_B:: Size: %d; Alignment: %d\n", sizeof(F_B), ALIGNMENT_OF(F_B)); 
    printf("B_F:: Size: %d; Alignment: %d\n", sizeof(B_F), ALIGNMENT_OF(B_F)); 
} 

Wenn er ausgeführt wird, ist das Ergebnis:

Foo:: Size: 3; Alignment: 1 
Bar:: Size: 6; Alignment: 2 
F_B:: Size: 15; Alignment: 1 
B_F:: Size: 30; Alignment: 2 

Sie können sehen, dass Bar und F_B Ausrichtung hat 2, so dass sein Feld i richtig ausgerichtet werden. Sie können auch sehen, dass die Größe der Bar 6 und nicht 5 ist. Ähnlich ist die Größe von B_F (5 von Bar) 30 und nicht 25.

Also, wenn Sie ein fester Code anstelle von sizeof(...) ist, werden Sie hier ein Problem bekommen.

Hoffe, das hilft.

+0

sieht gut aus, leider die anonyme Struktur innerhalb der Offset-Aufruf in msvc 2010 nicht kompiliert – nus

2

Für Situationen, in denen solche Dinge verwendet werden, und ich kann es nicht vermeiden, versuche ich, die Kompilation zu unterbrechen, wenn die Vermutungen nicht mehr gelten. Ich benutze so etwas wie die folgende (oder Boost.StaticAssert, wenn es die Situation erlaubt):

static_assert(sizeof(foo) <= 3); 

// Macro for "static-assert" (only usefull on compile-time constant expressions) 
#define static_assert(exp)   static_assert_II(exp, __LINE__) 
// Macro used by static_assert macro (don't use directly) 
#define static_assert_II(exp, line) static_assert_III(exp, line) 
// Macro used by static_assert macro (don't use directly) 
#define static_assert_III(exp, line) enum static_assertion##line{static_assert_line_##line = 1/(exp)} 
0

Es könnte immer noch ein Problem mit sizeof() sein, wenn Sie die Daten zwischen zwei Computern sind vorbei. Auf einem von ihnen könnte der Code mit Padding kompilieren und in dem anderen ohne, in welchem ​​Fall sizeof() unterschiedliche Ergebnisse liefern würde. Wenn die Array-Daten von einem Computer zum anderen übertragen werden, wird dies falsch interpretiert, da die Array-Elemente nicht an der erwarteten Stelle gefunden werden. Eine Lösung besteht darin, sicherzustellen, dass #pragma pack (1) wann immer möglich verwendet wird, aber das ist möglicherweise nicht genug für die Arrays. Am besten ist es, das Problem vorherzusehen und Padding für ein Vielfaches von 8 Bytes pro Array-Element zu verwenden.