Behält das __attribute__((__packed__)) von GCC die ursprüngliche Reihenfolge bei?

Lesezeit: 7 Minuten

Zweck

Ich schreibe ein Netzwerkprogramm in C (insbesondere gnu89) und ich möchte die Dinge vereinfachen, indem ich etwas uminterpretiere struct X als großes Array von Bytes (aka char), die Bytes über das Netzwerk senden und neu interpretieren als struct X auf der anderen Seite. Zu diesem Zweck habe ich mich entschieden, das __attribute__((__packed__ )) von gcc zu verwenden. Ich habe mein Bestes getan, um sicherzustellen, dass dies korrekt geschieht (dh ich habe Endianness und andere damit zusammenhängende Probleme berücksichtigt).

Frage

Außer das zu garantieren struct X so klein wie möglich ist, garantiert gcc, dass a struct definiert mit __attribute__((__packed__ )) behält die ursprüngliche Reihenfolge bei? Ich habe ziemlich viel gesucht und noch keine Dokumentation darüber gefunden, ob diese Garantie besteht oder nicht.

Anmerkungen

Es kann davon ausgegangen werden, dass sowohl beim Sender als auch beim Empfänger keine Portabilitätsprobleme auftreten (z sizeof(int) auf dem Server ist gleich sizeof(int) beim Kunden).

  • Was meinst du mit “Originalbestellung”? Meinen Sie, sind die Strukturmitglieder buchstäblich in der gleichen Reihenfolge wie in der Definition angegeben?

    – Robert Gamble

    18. November 2009 um 15:40 Uhr

  • Angesichts der Tatsache, dass Sie ein Netzwerkprogramm schreiben, steuern Sie wahrscheinlich auf eine Welt voller Schmerzen zu, wenn Ihre Daten zu einer Maschine mit einem SPARC- oder PowerPC-Chip, um nur zwei Familien zu nennen, gelangen, wenn die Quelle ein Intel-Chip ist. Wenn Sie etwas anderes tun, als die Daten mit den gepackten Strukturen zu laden und zu senden, ist die Leistungseinbuße beim Zugriff auf die gepackten Daten wahrscheinlich viel schlimmer als beim Schreiben des Codes zum Serialisieren der Daten auf plattformneutrale Weise aus ungepackten Datenstrukturen. Außerdem haben gut gestaltete Datenstrukturen keine zufälligen Löcher in sich – es ist möglich, dies zu tun.

    – Jonathan Leffler

    18. November 2009 um 15:58 Uhr

  • +1 zu Jonathans Kommentar oben. Gestalten Sie Ihre Struktur neu, sodass sie in der ersten Zeile keine Löcher enthält. (Das größte zum kleinsten Element sollte normalerweise ausreichen.)

    – DevSolar

    18. November 2009 um 16:02 Uhr

  • @Jonathan Leffler Ich schreibe dieses Programm nur für den persönlichen Gebrauch. Ich kenne die Architekturen der Verbindungsmaschinen im Voraus.

    anon

    19. November 2009 um 17:58 Uhr

Benutzer-Avatar
Ambroz Bizjak

Ja, __attribute__((packed)) (keine Notwendigkeit für einen zweiten Satz von Unterstrichen) ist ein korrekter Weg, um binäre (dh Nicht-Text-) Netzwerkprotokolle zu implementieren. Es gibt keine Lücken zwischen den Elementen.

Dafür sollte man aber Verständnis haben packed packt nicht nur die Struktur, sondern auch:

  • macht seine erforderliche Ausrichtung ein Byte, und
  • stellt sicher, dass seine Mitglieder, die durch das Packen und durch fehlende Ausrichtungsanforderungen der Struktur selbst falsch ausgerichtet werden können, korrekt gelesen und geschrieben werden, dh Die Fehlausrichtung seiner Felder wird in der Software vom Compiler behandelt.

Jedoch, behandelt der Compiler die Fehlausrichtung nur, wenn Sie direkt auf die Strukturmitglieder zugreifen. Du solltest Machen Sie niemals einen Zeiger auf ein Mitglied einer gepackten Struktur (außer wenn Sie wissen, dass die erforderliche Ausrichtung des Members 1 ist, wie char oder eine andere gepackte Struktur). Der folgende C-Code veranschaulicht das Problem:

#include <stdio.h>
#include <inttypes.h>
#include <arpa/inet.h>

struct packet {
    uint8_t x;
    uint32_t y;
} __attribute__((packed));

int main ()
{
    uint8_t bytes[5] = {1, 0, 0, 0, 2};
    struct packet *p = (struct packet *)bytes;

    // compiler handles misalignment because it knows that
    // "struct packet" is packed
    printf("y=%"PRIX32", ", ntohl(p->y));

    // compiler does not handle misalignment - py does not inherit
    // the packed attribute
    uint32_t *py = &p->y;
    printf("*py=%"PRIX32"\n", ntohl(*py));
    return 0;
}

Auf einem x86-System (das keine Speicherzugriffsausrichtung erzwingt) führt dies zu einem Ergebnis

y=2, *py=2

wie erwartet. Auf meinem ARM-Linux-Board hingegen lieferte es beispielsweise das scheinbar falsche Ergebnis

y=2, *py=1

Angenommen, Sie fragen, ob die Strukturmitglieder die in ihrer Definition angegebene Reihenfolge beibehalten, lautet die Antwort ja. Der Standard verlangt, dass aufeinanderfolgende Mitglieder zunehmende Adressen haben:

Abschnitt §6.7.2.1p13:

Innerhalb eines Strukturobjekts haben die Mitglieder, die keine Bitfelder sind, und die Einheiten, in denen sich Bitfelder befinden, Adressen, die in der Reihenfolge ansteigen, in der sie deklariert werden.

und die Dokumentation für das gepackte Attribut besagt eindeutig, dass nur das Auffüllen/Ausrichten betroffen ist:

Das Attribut „packed“ gibt an, dass ein Variablen- oder Strukturfeld die kleinstmögliche Ausrichtung haben sollte – ein Byte für eine Variable und ein Bit für ein Feld, es sei denn, Sie geben mit dem Attribut „aligned“ einen größeren Wert an.

  • Das GCC-Doc-Zitat macht mir nicht klar, ob das Bit-Feld ist Bestellung ist gewartet.

    – Ciro Santilli OurBigBook.com

    30. Juli 2017 um 11:07 Uhr


Ja.

Allerdings mit __attribute__((__packed__)) ist kein guter Weg, das zu tun, was Sie tun.

  • Es löst keine Probleme mit der Byte-Reihenfolge
  • Der Zugriff auf die Struktur wird langsam sein
  • Obwohl die Popularität von gcc dazu geführt hat, dass andere Compiler häufig gcc-Erweiterungen implementieren, bedeutet die Verwendung dieser Compiler-spezifischen Erweiterung, dass Sie kein konformes C-Programm haben. Das heißt, wenn sich ein anderer Compiler oder sogar ein zukünftiger gcc ändert verpackt oder gar nicht umsetzt, hat man Pech und kann sich bei niemandem beschweren. Gcc könnte es morgen fallen lassen und immer noch ein C99-Compiler sein. (Ok, genau das ist unwahrscheinlich.) Die meisten von uns versuchen, konforme Programme zu schreiben, nicht weil wir eine abstrakte Ablehnung gegenüber der Verwendung von Herstellersoftware haben oder einen akademischen Standard der Codereinheit wünschen, sondern weil wir wissen, dass nur konforme Sprachfunktionen einen haben präzise und gemeinsame Spezifikation, so dass es viel einfacher ist, sich darauf zu verlassen, dass unser Code von Tag zu Tag und von System zu System das Richtige tut, wenn wir es so tun.
  • Sie erfinden das Rad neu; dieses Problem wurde bereits standardkonform gelöst: vgl YAML, XML, und JSON.
  • Wenn es sich um ein so einfaches Protokoll handelt, dass YAML, XML und JSON nicht verfügbar sind, sollten Sie wirklich einzelne grundlegende Typen nehmen, die Version von hton?() und ntoh?() Ihres Hosts anwenden und sie auf eine Ausgabe memcpy() anwenden Puffer. Mir ist klar, dass es eine lange Tradition gibt, direkt aus Strukturen zu lesen und zu schreiben, aber ich habe auch viel Zeit damit verbracht, diesen Code später zu reparieren, als er von 32-Bit- auf 64-Bit-Umgebungen verschoben wurde …

  • @DigitalRoss Ich bin der Erste, der Code schreibt, der strikt ISO C90 entspricht. Ich verwende nie Compiler-spezifische Hacks. In diesem Fall schreibe ich jedoch Code ausschließlich für den persönlichen Gebrauch.

    anon

    19. November 2009 um 17:54 Uhr

  • Nun, ich stimme zu, dass meine Antwort nicht sehr gut auf Ihre Frage ausgerichtet war, ich denke, ich werde sie löschen …

    – DigitalRoss

    19. November 2009 um 19:07 Uhr

  • @DigitalRoss Antwort nicht löschen! Es kann jemand anderem helfen, der sich diesem Thread anschließt.

    anon

    20. November 2009 um 0:45 Uhr

  • Wenn sich das OP nur um den “persönlichen Gebrauch” kümmert und auf beiden Seiten der Verbindung Maschinen mit der gleichen Byte-Reihenfolge hat, dann ist es fast sicher, dass beide Seiten auch die gleichen Ausrichtungsbeschränkungen haben. Obwohl die Ausrichtung durch die Implementierung definiert ist, bedeutet dies nicht, dass sie sich bei jeder Compiler-Revision unter Ihnen ändern wird. Die Beibehaltung des Ausrichtungsverhaltens ist für alle außer der trivialsten Verwendung von Bibliothekscode unerlässlich, und gcc auf x86 schließt sogar eine optimale Ausrichtung von aus double um die Kompatibilität mit bestehenden Ausrichtungs-ABI aufrechtzuerhalten.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    11. August 2010 um 7:08 Uhr

Basierend auf dem, was Sie zu tun versuchen, empfehle ich Ihnen dringend, auch Datentypen mit fester Größe (z. B. uint32_t, int16_t usw.) zu verwenden, die in stdint.h zu finden sind. Die Verwendung von Datentypen mit fester Größe erspart Ihnen Folgendes:

struct name
{
    short field : 8;
};

Wir verwenden diese Technik häufig, um Nachrichten zwischen einem Byte-Array und einer Struktur umzuwandeln, und sind damit noch nie auf Probleme gestoßen. Möglicherweise müssen Sie die Endianness-Konvertierung selbst durchführen, aber die Feldreihenfolge ist kein Problem. Wenn Sie Bedenken hinsichtlich der Datentypgröße haben, können Sie die Feldgröße jederzeit wie folgt angeben:

struct foo
{
  short someField : 16 __attribute__ ((packed));
};

Dies garantiert, dass someField als 16 Bit gespeichert wird und nicht neu angeordnet oder geändert wird, um an Byte-Grenzen zu passen.

Benutzer-Avatar
Adam Gut

Ja, C hat eine Garantie, dass Strukturelemente nicht neu angeordnet werden. (Es kann Erweiterungen oder ausgefallene Optimierungssysteme geben, die dies ändern könnten, aber nicht standardmäßig in gcc.)

1366030cookie-checkBehält das __attribute__((__packed__)) von GCC die ursprüngliche Reihenfolge bei?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy