Ja, __attribute__((packed))
ist auf einigen Systemen möglicherweise unsicher. Das Symptom wird auf einem x86 wahrscheinlich nicht auftreten, was das Problem nur heimtückischer macht. Tests auf x86-Systemen werden das Problem nicht aufdecken. (Auf dem x86 werden falsch ausgerichtete Zugriffe in der Hardware behandelt. Wenn Sie einen int*
Zeiger dereferenzieren , der auf eine ungerade Adresse zeigt, ist er etwas langsamer als bei richtiger Ausrichtung, aber Sie erhalten das richtige Ergebnis.)
Auf einigen anderen Systemen, z. B. SPARC, wird versucht, auf eine falsch ausgerichtete Datei zuzugreifen int
Objekt , der das Programm zum Absturz bringt.
Es gab auch Systeme, bei denen ein falsch ausgerichteter Zugriff die niederwertigen Bits der Adresse stillschweigend ignoriert und dazu führt, dass sie auf den falschen Speicherblock zugreift.
Betrachten Sie das folgende Programm:
#include <stdio.h>
#include <stddef.h>
int main(void)
{
struct foo {
char c;
int x;
} __attribute__((packed));
struct foo arr[2] = { { 'a', 10 }, {'b', 20 } };
int *p0 = &arr[0].x;
int *p1 = &arr[1].x;
printf("sizeof(struct foo) = %d\n", (int)sizeof(struct foo));
printf("offsetof(struct foo, c) = %d\n", (int)offsetof(struct foo, c));
printf("offsetof(struct foo, x) = %d\n", (int)offsetof(struct foo, x));
printf("arr[0].x = %d\n", arr[0].x);
printf("arr[1].x = %d\n", arr[1].x);
printf("p0 = %p\n", (void*)p0);
printf("p1 = %p\n", (void*)p1);
printf("*p0 = %d\n", *p0);
printf("*p1 = %d\n", *p1);
return 0;
}
Unter x86 Ubuntu mit gcc 4.5.2 wird die folgende Ausgabe erzeugt:
sizeof(struct foo) = 5
offsetof(struct foo, c) = 0
offsetof(struct foo, x) = 1
arr[0].x = 10
arr[1].x = 20
p0 = 0xbffc104f
p1 = 0xbffc1054
*p0 = 10
*p1 = 20
Unter SPARC Solaris 9 mit gcc 4.5.1 wird Folgendes erzeugt:
sizeof(struct foo) = 5
offsetof(struct foo, c) = 0
offsetof(struct foo, x) = 1
arr[0].x = 10
arr[1].x = 20
p0 = ffbff317
p1 = ffbff31c
Bus error
In beiden Fällen wird das Programm nur ohne zusätzliche Optionen kompiliert gcc packed.c -o packed
.
(Ein Programm, das eine einzelne Struktur anstelle eines Arrays verwendet, weist das Problem nicht zuverlässig auf, da der Compiler die Struktur einer ungeraden Adresse zuordnen kann, damit das Element x
richtig ausgerichtet ist. Mit einem Array von zwei struct foo
Objekten, mindestens dem einen oder anderen wird ein falsch ausgerichtetes x
Mitglied haben.)
(In diesem Fall p0
verweist es auf eine falsch ausgerichtete Adresse, da es auf ein gepacktes Element zeigt, das einem Element int
folgt char
. Es ist p1
zufällig korrekt ausgerichtet, da es auf dasselbe Element im zweiten Element des Arrays zeigt, sodass zwei char
Objekte davor stehen - und unter SPARC Solaris arr
scheint das Array an einer Adresse zugewiesen zu sein, die gerade ist, aber kein Vielfaches von 4.)
Wenn der Compiler auf das Mitglied x
eines struct foo
nach Namen verweist, weiß er, dass x
es möglicherweise falsch ausgerichtet ist, und generiert zusätzlichen Code, um korrekt darauf zuzugreifen.
Sobald die Adresse von arr[0].x
oder arr[1].x
in einem Zeigerobjekt gespeichert wurde, wissen weder der Compiler noch das laufende Programm, dass es auf ein falsch ausgerichtetes int
Objekt verweist . Es wird lediglich davon ausgegangen, dass es richtig ausgerichtet ist, was (auf einigen Systemen) zu einem Busfehler oder einem ähnlichen anderen Fehler führt.
Dies in gcc zu beheben, wäre meiner Meinung nach unpraktisch. Eine allgemeine Lösung würde erfordern, für jeden Versuch, einen Zeiger auf einen beliebigen Typ mit nicht trivialen Ausrichtungsanforderungen zu dereferenzieren, entweder (a) zum Zeitpunkt der Kompilierung nachzuweisen, dass der Zeiger nicht auf ein falsch ausgerichtetes Element einer gepackten Struktur zeigt, oder (b) Generieren von sperrigerem und langsamerem Code, der entweder ausgerichtete oder falsch ausgerichtete Objekte verarbeiten kann.
Ich habe einen gcc-Fehlerbericht eingereicht . Wie gesagt, ich glaube nicht, dass es praktisch ist, das Problem zu beheben, aber die Dokumentation sollte es erwähnen (derzeit nicht).
UPDATE : Ab dem 20.12.2018 ist dieser Fehler als BEHOBEN markiert. Der Patch wird in gcc 9 mit einer neuen -Waddress-of-packed-member
Option angezeigt, die standardmäßig aktiviert ist.
Wenn die Adresse des gepackten Mitglieds von struct oder union verwendet wird, kann dies zu einem nicht ausgerichteten Zeigerwert führen. Dieser Patch fügt -Waddress-of-Packed-Member hinzu, um die Ausrichtung bei der Zeigerzuweisung zu überprüfen und nicht ausgerichtete Adressen sowie nicht ausgerichtete Zeiger zu warnen
Ich habe gerade diese Version von gcc aus dem Quellcode erstellt. Für das obige Programm werden folgende Diagnosen erstellt:
c.c: In function ‘main’:
c.c:10:15: warning: taking address of packed member of ‘struct foo’ may result in an unaligned pointer value [-Waddress-of-packed-member]
10 | int *p0 = &arr[0].x;
| ^~~~~~~~~
c.c:11:15: warning: taking address of packed member of ‘struct foo’ may result in an unaligned pointer value [-Waddress-of-packed-member]
11 | int *p1 = &arr[1].x;
| ^~~~~~~~~