Warum müssen alle Zeiger auf Strukturen die gleiche Größe haben?

Lesezeit: 8 Minuten

Benutzer-Avatar
Daniel Kleinstein

Die C-Norm spezifiziert:

Ein Zeiger auf void muss die gleichen Darstellungs- und Ausrichtungsanforderungen haben wie ein Zeiger auf einen Zeichentyp. In ähnlicher Weise müssen Zeiger auf qualifizierte oder nicht qualifizierte Versionen kompatibler Typen die gleichen Darstellungs- und Ausrichtungsanforderungen haben. Alle Zeiger auf Strukturtypen müssen die gleichen Darstellungs- und Ausrichtungsanforderungen haben. Alle Zeiger auf Union-Typen müssen untereinander die gleichen Darstellungs- und Ausrichtungsanforderungen haben. Zeiger auf andere Typen müssen nicht die gleichen Darstellungs- oder Ausrichtungsanforderungen haben.

dh sizeof(int*) ist nicht unbedingt gleich sizeof(char*) – aber sizeof(struct A*) ist zwangsläufig gleich sizeof(struct B*).

Was ist der Grund für diese Anforderung? So wie ich es verstehe, ist die Begründung für unterschiedliche Größen für Grundtypen soll Anwendungsfälle wie nahe/ferne/riesige Zeiger unterstützen (bearbeiten: Wie in den Kommentaren und in der akzeptierten Antwort darauf hingewiesen wurde, ist dies nicht die Begründung) – aber gilt nicht dieselbe Begründung für structs an verschiedenen Orten im Speicher?

  • Ich glaube, es liegt daran undurchsichtig Zeiger. Der Compiler muss einen Zeiger zuweisen, ohne den tatsächlichen zu kennen struct Interna.

    – Eugen Sch.

    9. August 2021 um 20:16 Uhr


  • Ohne dies wäre es unmöglich, typgelöschte Schnittstellen zu schreiben und Deklarationen von Strukturen weiterzuleiten, was den Programmierer stark einschränken würde.

    – SergejA

    9. August 2021 um 20:19 Uhr


  • @SergeyA Warum wäre es unmöglich, vom Typ gelöschte Schnittstellen zu schreiben? Funktionen wie qsort funktionieren gut bei Typen, die nicht die Beschränkung der gleichen Zeigergröße haben.

    – Daniel Kleinstein

    9. August 2021 um 20:29 Uhr

  • Sie können einen Zeiger auf struct (oder union) verwenden, bevor die relevante Struktur (oder union) definiert wird. struct node { struct node *next; /* struct node undefined here, but next is ok */ ... };

    – pmg

    9. August 2021 um 20:34 Uhr

  • Der Grund für unterschiedliche Größen sind nicht Nah/Fern-Zeiger: Diese erfordern sowieso eine Erweiterung auf Standard-C. Es sind Zeiger auf Wörter im Vergleich zu Zeigern auf Bytes auf Architekturen, bei denen auf einzelne Bytes nicht direkt zugegriffen werden kann, sondern nur auf Wörter. Ein Zeiger auf ein Byte ist also ein Wortzeiger plus einige zusätzliche Informationen, die angeben, auf welchen Teil des Wortes der Zeiger zeigt. (Auch Daten- und Codezeiger können unterschiedliche Größen haben, was etwas weniger exotisch ist.)

    – Gilles ‘SO- hör auf, böse zu sein’

    9. August 2021 um 20:38 Uhr

Benutzer-Avatar
chqrlie

Die Antwort ist ganz einfach: struct und union Typen können als undurchsichtige Typen deklariert werden, dh: ohne tatsächliche Definition der struct oder union Einzelheiten. Wenn die Darstellung von Zeigern abhängig von den Details der Strukturen unterschiedlich wäre, wie würde der Compiler bestimmen, welche Darstellung für undurchsichtige Zeiger verwendet werden soll, die als Argumente, Rückgabewerte oder sogar nur aus dem Speicher gelesen oder gespeichert werden sollen.

Die natürliche Folge der Fähigkeit, undurchsichtige Zeigertypen zu manipulieren, ist, dass alle diese Zeiger die gleiche Darstellung haben müssen. Beachten Sie jedoch, dass Zeiger auf struct und Hinweise auf union kann eine andere Darstellung haben, sowie Zeiger auf grundlegende Typen wie z char, int, double

Eine weitere Unterscheidung bezüglich der Zeigerdarstellung besteht zwischen Zeigern auf Daten und Zeigern auf Funktionen, die eine unterschiedliche Größe haben können. Ein solcher Unterschied ist in aktuellen Architekturen häufiger, wenn auch außerhalb von Betriebssystemen und Gerätetreibern immer noch selten. 64-Bit für Funktionszeiger scheint eine Verschwendung zu sein, da 4 GB für den Codespeicher ausreichend sein sollten, aber moderne Architekturen nutzen diesen zusätzlichen Speicherplatz, um Zeigersignaturen zu speichern, um Code gegen böswillige Angriffe zu schützen. Eine andere Verwendung besteht darin, Hardware zu nutzen, die einige der Zeigerbits ignoriert (z. B.: x86_64 ignoriert die obersten 16 Bits), um Typinformationen zu speichern oder NaN-Werte unverändert als Zeiger zu verwenden.

Darüber hinaus wurden die Near/Far/Huge-Zeigerattribute aus altem 16-Bit-Code durch diese Bemerkung im C-Standard nicht korrekt adressiert, wie es bei allen Zeigern der Fall sein könnte nahe, weit oder riesig. Die Unterscheidung zwischen Code-Zeigern und Daten-Zeigern in gemischtem Modellcode wurde jedoch dadurch abgedeckt und scheint auf einigen Betriebssystemen immer noch aktuell zu sein.

Schließlich schreibt Posix vor, dass alle Zeiger die gleiche Größe und Darstellung haben, sodass gemischter Modellcode schnell zu einer historischen Kuriosität werden sollte.

Es lässt sich argumentieren, dass Architekturen, bei denen die Darstellung für verschiedene Datentypen unterschiedlich ist, heutzutage verschwindend selten sind und es höchste Zeit ist, den Standard zu bereinigen und diese Option zu entfernen. Der Haupteinwand ist die Unterstützung von Architekturen, bei denen die adressierbaren Einheiten große Wörter sind und 8-Bit-Bytes unter Verwendung von Zusatzinformationen adressiert werden, wodurch char * und void * größer als normale Zeiger. Solche Architekturen machen die Zeigerarithmetik jedoch sehr umständlich und sind auch ziemlich selten (ich persönlich habe noch nie eine gesehen).

  • C funktioniert gut mit getaggten Zeigertypen, bei denen einige Bits des Zeigers von der Hardware zur Typprüfung verwendet werden. Ich habe das oft benutzt 🙂

    – Kuba hat Monica nicht vergessen

    9. August 2021 um 20:56 Uhr

  • 3) Ich habe einen nicht konformen Compiler verwendet, der hatte sizeof(char *) 2 aber sein sizeof(const char *) 3. Es war ungefähr so ​​ärgerlich, wie es sich anhört.

    – Josua

    9. August 2021 um 21:06 Uhr

  • @Joshua: Das ist in Ordnung, dein struct hat wahrscheinlich eine Größe von 2 und eine Ausrichtung von 1, aber ein Zeiger auf eine solche struct hat die gleiche Größe und Ausrichtung wie alle anderen struct Zeiger.

    – chqrlie

    9. August 2021 um 21:06 Uhr

  • @CraigEstey: Irgendein Microchip-Prozessor. RAM war nur 32k, aber ROM war größer als 64k und const char * könnte in ROM zeigen.

    – Josua

    9. August 2021 um 21:09 Uhr

  • “Posix-Mandate … sollten schnell zu einer historischen Kuriosität werden.” POSIX zeigt keine Anzeichen für einen bedeutenden Marktanteil im eingebetteten Bereich, der die überwiegende Mehrheit der in C programmierten Geräte ausmacht.

    – Ben Voigt

    10. August 2021 um 15:06 Uhr

In der von Dennis Ritchie erfundenen C-Sprache, als ein C-Compiler auf eine Definition für stieß struct foo *p; es müsste sich nicht darum kümmern, ob oder wie die Struktur definiert wurde, es sei denn, oder bis ein Programm Zeigerarithmetik oder die verwendet -> Operator. Andernfalls könnte es das einfach aufzeichnen p war ein Zeiger auf eine Struktur mit Tag foo ohne wissen oder sich darum kümmern zu müssen, ob, wo oder wie eine solche Struktur definiert werden könnte. Der Standard fügt eine seltsame kleine Falte hinzu, die manchmal Strukturzeiger mit übereinstimmenden Tags inkompatibel macht, aber das Problem bleibt, dass ein Compiler in der Lage sein muss, eine Deklaration eines Zeiger-zu-Struktur-Typs sowie grundlegende Zuweisungen zwischen solchen Zeigern zu verarbeiten Fälle, in denen der Inhalt einer Struktur möglicherweise nicht bekannt ist.

Beachten Sie, dass auf Plattformen, auf denen Zeiger auf Objekte mit willkürlicher Ausrichtung größer sein können als Zeiger auf Objekte, die bekanntermaßen vorhanden sind int Ausrichtung, ein Compiler könnte sinnvollerweise angeben, dass alle Strukturen haben int Ausrichtung, auch wenn sie nur Charaktermitglieder enthalten. Darüber hinaus könnten Compiler für solche Plattformen entscheiden, Zeiger auf Vereinigungen so zu verarbeiten, dass ein Zeiger auf ein beliebiges Objekt – sogar ein Zeichen – in einen Zeiger auf eine beliebige Vereinigung umgewandelt werden kann, die ein solches Objekt enthält, und für den Zugriff verwendet wird dieses Objekt innerhalb der Union. Dies kann erfordern, dass Zeiger auf Union-Objekte die Größe eines Byte-Zeigers haben und nicht die eines kleineren Int-Zeigers.

Beachten Sie, dass in Vorstandard-Compilern, wenn zwei Strukturen übereinstimmende Member enthielten, eine Funktion, die a akzeptierte void* und es in einen Strukturtyp umzuwandeln, hätte erwartet werden können, dass es verwendbar wäre, um auf beiden Typen austauschbar zu arbeiten. Leider erlaubt der Standard Compilern anzunehmen, dass Code niemals so etwas tun wird, und bietet Programmierern keine Möglichkeit, anzugeben, wann zwei Strukturen austauschbar verwendbar sein sollten.

  • Ich denke, der Standard (sogar C89) sieht dies für alle Strukturen vor, die dieselben Anfangsmitglieder haben (wenn alle Mitglieder von Anfang bis zu denen, auf die Sie in beiden Strukturen zugreifen möchten, nicht gemeinsam sind, haben Sie dem Compiler nicht gesagt, dass er sich ausrichten soll diese Mitglieder richtig): Erstens können Sie die gemeinsamen Mitglieder in einer einzigen Strukturdefinition zusammenfassen und diese Struktur als erstes Mitglied jeder anderen Struktur verwenden, die austauschbar sein sollte; Zweitens können Sie die Strukturen in eine Union einfügen und auf die gemeinsamen Anfangselemente jeder Struktur über eines dieser Strukturelemente der Union zugreifen.

    – mtraceur

    29. November 2021 um 6:06 Uhr

  • @mtraceur: Gegebene Deklarationen struct {int x;} *p1; struct {int x;} *p2; und Code p1->x = 1; p2->x=2; return p1->x;wird die Aliasing-Optimierungslogik sowohl in clang als auch in gcc (sofern nicht mit deaktiviert -fno-strict-aliasing) Code generieren, der bedingungslos 1 zurückgibt.

    – Superkatze

    29. November 2021 um 15:40 Uhr

  • Wir müssen konkreter werden. Ich sehe nicht das gleiche Ergebnis. Wenn ich eine Datei kompiliere, die nur int f(struct {int x;} *p1, struct {int x;} *p2) { p1->x = 1; p2->x = 2; return p1->x; } mit clang -c -Os -std=c89 -pedantic (gleiches Ergebnis mit -O3 Anstatt von -Osund mit c17 Anstatt von c89), f verwandelt sich in die folgenden Maschinenanweisungen auf dieser ARMv8/AArch64-Maschine, auf der ich mich gerade befinde: mov w8, #0x1, mov w9, #0x2, str w8, [x0], str w9, [x1], ldr w0, [x0], ret.

    – mtraceur

    29. November 2021 um 18:35 Uhr


  • Dasselbe Ergebnis auf einem x86-64-Rechner, auf dem ich es gerade mit Clang ausprobiert habe. Ich habe es jedoch auf Godbolt reproduziert, und auf Godbolt sehe ich, was Sie mit dem neuesten GCC melden (aber nicht mit dem neuesten Clang). Ich bin bereit zu glauben, dass GCC hier technisch korrekt ist, aber das ist alles noch etwas nebensächlich, weil ich das nicht im Sinn hatte, als ich sagte, dass der Standard eine Möglichkeit bietet, dies zu tun.

    – mtraceur

    29. November 2021 um 18:57 Uhr

  • Was ich im Sinn hatte, ist, dass der Standard mehrere Möglichkeiten bietet, dem Compiler explizit mitzuteilen: „Ich beabsichtige, dass diese beiden Strukturtypen ein gemeinsames Layout/Mitglieder haben (zumindest am Anfang) und ich beabsichtige, dass diese Funktion mit jeder Struktur funktioniert dieses gemeinsame Layout”.

    – mtraceur

    29. November 2021 um 19:03 Uhr

1371020cookie-checkWarum müssen alle Zeiger auf Strukturen die gleiche Größe haben?

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

Privacy policy