C – Inkompatibler Zeigertyp

Lesezeit: 7 Minuten

Benutzer-Avatar
Erik W

Warum gibt der folgende Code Warnungen aus?

int main(void)
{
    struct {int x; int y;} test = {42, 1337};
    struct {int x; int y;} *test_ptr = &test;
}

Ergebnisse:

warning: initialization from incompatible pointer type [-Wincompatible-pointer-types]
         struct {int x; int y;} *test_ptr = &test;
                                            ^

  • Die Antwort von @ Jonathan Leffler ist richtig. Jede Konvertierung zwischen verschiedenen Arten von Zeigern löst diese Warnung aus. Da beide Strukturen unbenannt und unterschiedlich sind, können Sie leider nicht zwischen ihnen umwandeln. Deshalb sollten Sie Ihre Strukturen vorher deklarieren.

    – Radnyx

    11. Juli 2016 um 3:16 Uhr


  • Neben anderen Antworten ist es erwähnenswert, dass es möglich ist, die Warnung loszuwerden. Machen Sie einfach einen Zwischenguss dazu void*: struct {int x; int y;} *test_ptr = (void*)&test;

    – ach

    11. Juli 2016 um 11:27 Uhr


  • Wenn sie denselben Typ haben sollen, definieren Sie den Typ einmal.

    – Keith Thompson

    16. Juli 2016 um 22:13 Uhr

  • @Radnyx: Ganz so einfach ist es nicht. Konvertierungen zwischen unvereinbar Zeigertypen sind (in den meisten, aber nicht allen Fällen) eine Einschränkungsverletzung, die eine Diagnose erfordert. (gcc gibt standardmäßig eine Warnung aus, die gültig ist, aber es könnte ein schwerwiegender Fehler sein). Typen können kompatibel sein, ohne derselbe Typ zu sein; Auch void* und zum Beispiel int* sind inkompatibel, können aber einander zugeordnet werden (es findet eine implizite Konvertierung statt).

    – Keith Thompson

    16. Juli 2016 um 22:15 Uhr

Benutzer-Avatar
Jonathan Leffler

Sie sind zwei anonyme Strukturtypen (sie haben keinen Tag). Alle diese Strukturtypen (in einer einzelnen Übersetzungseinheit) sind unterschiedlich – sie sind niemals derselbe Typ. Füge einen Tag hinzu!

Der relevante Satz in der Norm ist in §6.7.2.1 Struktur- und Unionsspezifizierer:

¶8 Das Vorhandensein von a Strukturdeklarationsliste in einem struct-or-union-specifier deklariert einen neuen Typ innerhalb einer Übersetzungseinheit.

Das Strukturdeklarationsliste bezieht sich auf das Material dazwischen { und } im Typ.

Das bedeutet, dass es in Ihrem Code zwei separate Typen gibt, einen für jeden struct { … }. Die beiden Typen sind getrennt; Sie können weder offiziell einen Wert eines Typs dem anderen zuweisen noch Zeiger erstellen usw. Tatsächlich können Sie diese Typen nach dem Semikolon nicht erneut referenzieren.

Das heißt, Sie könnten Folgendes haben:

int main(void)
{
    struct {int x; int y;} test = {42, 1337}, *tp = &test;
    struct {int x; int y;} result, *result_ptr;
    result_ptr = &result;
    …
}

Jetzt test und tp beziehen sich auf denselben Typ (einer eine Struktur, einer ein Zeiger auf die Struktur) und ähnlich result und result_ptr beziehen sich auf denselben Typ, und die Initialisierungen und Zuweisungen sind in Ordnung, aber die beiden Typen sind unterschiedlich. Es ist nicht klar, dass Sie ein zusammengesetztes Literal beider Typen erstellen – Sie müssten schreiben (struct {int x; int y;}){.y = 9, .x = 8}aber die Anwesenheit der Strukturdeklarationsliste bedeutet, dass es sich um einen weiteren neuen Typ handelt.

Wie in den Kommentaren erwähnt, gibt es auch Abschnitt §6.2.7 Kompatibler Typ und zusammengesetzter Typwas sagt:

¶1 … Darüber hinaus sind zwei in getrennten Übersetzungseinheiten deklarierte Struktur-, Vereinigungs- oder Aufzählungstypen kompatibel, wenn ihre Tags und Member die folgenden Anforderungen erfüllen: Wenn einer mit einem Tag deklariert ist, muss der andere mit demselben Tag deklariert werden. Wenn beide irgendwo innerhalb ihrer jeweiligen Übersetzungseinheiten abgeschlossen werden, gelten die folgenden zusätzlichen Anforderungen: Es muss eine Eins-zu-Eins-Korrespondenz zwischen ihren Mitgliedern bestehen, so dass jedes Paar korrespondierender Mitglieder mit kompatiblen Typen deklariert wird; wenn ein Mitglied des Paars mit einem Ausrichtungsbezeichner deklariert wird, wird das andere mit einem äquivalenten Ausrichtungsbezeichner deklariert; und wenn ein Mitglied des Paars mit einem Namen deklariert wird, wird das andere mit demselben Namen deklariert. Für zwei Strukturen müssen entsprechende Elemente in derselben Reihenfolge deklariert werden. Für zwei Strukturen oder Vereinigungen sollen entsprechende Bitfelder die gleichen Breiten haben.

Grob gesagt bedeutet dies, dass, wenn die Definitionen der Typen in den beiden Übersetzungseinheiten (denken Sie an „Quelldateien“ plus enthaltene Header) gleich sind, sie sich auf denselben Typ beziehen. Gott sei Dank! Andernfalls könnte die Standard-I/O-Bibliothek neben anderen kleinen Details nicht funktionieren.

  • Das ist nicht ganz richtig, zwei anonyme Strukturen, die in verschiedenen Übersetzungseinheiten definiert sind, können kompatibel gemacht werden.

    – 2501

    11. Juli 2016 um 3:22 Uhr

  • @2501: Einverstanden (§6.2.7 Kompatibler Typ und zusammengesetzter Typ) … Ich versuche herauszufinden, wo der Standard sagt, was ich innerhalb einer einzelnen TU sage.

    – Jonathan Leffler

    11. Juli 2016 um 3:23 Uhr

Benutzer-Avatar
2501

Variablen &test und test_ptrdie anonyme Strukturen sind, haben unterschiedliche Typen.

Anonyme Strukturen, die in derselben Übersetzungseinheit definiert sind, sind niemals kompatible Typen1 da der Standard keine Kompatibilität für zwei Strukturtypdefinitionen in derselben Übersetzungseinheit definiert.

Um Ihren Code zu kompilieren, können Sie Folgendes tun:

struct {int x; int y;} test = {42, 1337} , *test_ptr;
test_ptr = &test;

1 (Zitiert aus: ISO:IEC 9899:201X 6.2.7 Kompatibler Typ und zusammengesetzter Typ 1)
Zwei Typen haben einen kompatiblen Typ, wenn ihre Typen gleich sind. Zusätzliche Regeln zum Bestimmen, ob zwei Typen kompatibel sind, werden in 6.7.2 für Typbezeichner, in 6.7.3 für Typqualifizierer und in 6.7.6 für Deklaratoren beschrieben. Darüber hinaus sind zwei Struktur-, Vereinigungs- oder Aufzählungstypen, die in separaten Übersetzungseinheiten deklariert sind, kompatibel wenn ihre Tags und Mitglieder die folgenden Anforderungen erfüllen: Wenn eines mit einem Tag deklariert ist, muss das andere mit demselben Tag deklariert werden. Wenn beide irgendwo innerhalb ihrer jeweiligen Übersetzungseinheiten abgeschlossen werden, gelten die folgenden zusätzlichen Anforderungen: Es muss eine Eins-zu-Eins-Korrespondenz zwischen ihren Mitgliedern bestehen, so dass jedes Paar korrespondierender Mitglieder mit kompatiblen Typen deklariert wird; wenn ein Mitglied des Paars mit einem Ausrichtungsbezeichner deklariert wird, wird das andere mit einem äquivalenten Ausrichtungsbezeichner deklariert; und wenn ein Mitglied des Paars mit einem Namen deklariert wird, wird das andere mit demselben Namen deklariert. Für zwei Strukturen müssen entsprechende Elemente in derselben Reihenfolge deklariert werden. Für zwei Strukturen oder Vereinigungen sollen entsprechende Bitfelder die gleichen Breiten haben. Bei zwei Aufzählungen müssen entsprechende Mitglieder die gleichen Werte haben.

  • Das scheint zu sagen, dass es funktionieren sollte, oder? Keine hat ein Tag und ihre Mitglieder sind gleich.

    – zneak

    11. Juli 2016 um 3:32 Uhr


  • @zneak Beachten Sie das erste fettgedruckte Zitat: separate Übersetzungseinheiten.

    – 2501

    11. Juli 2016 um 3:34 Uhr


  • Probieren Sie §6.7.2.1 ¶8 für die verschiedenen Typen innerhalb einer einzelnen TU aus.

    – Jonathan Leffler

    11. Juli 2016 um 3:39 Uhr

  • Ich denke, die Formatierung sollte so sein, wie ich sie bearbeitet habe. Es liest sich viel besser (IMO).

    – edmz

    11. Juli 2016 um 8:32 Uhr

  • @black Ich stimme zu, dass die Referenz unten sein sollte.

    – 2501

    11. Juli 2016 um 8:39 Uhr

Benutzer-Avatar
Superkatze

C wurde ursprünglich so konzipiert, dass Zeiger auf Strukturen mit teilweise oder vollständig identischem Layout austauschbar verwendet werden konnten, um auf die gemeinsamen Teile zuzugreifen, und Versionen der Sprache vor C89, die individuelle Namensräume für Strukturmitglieder implementierten, behielten im Allgemeinen die Möglichkeit, die Zeiger austauschbar zu verwenden mit Hilfe von Typumwandlungen, Konvertierungen durch void usw. Während es für Compiler legal wäre, unterschiedliche Füllmengen vor Arrays unterschiedlicher Größe einzufügen, geben die meisten Compiler an, dass sie das Layout ohne dies ausführen, was bedeutet, dass man leicht a schreiben könnte Funktion, die einen Zeiger auf eines der folgenden Objekte oder etwas anderes, ähnlich Deklariertes (der Größe 4, 5, 24601 usw.) akzeptieren würde

struct { int size; int foo[2]; } my_two_foos = {2, {1,2} };
struct { int size; int foo[3]; } my_three_foos = {3, {4,5,6} };

Da Implementierungen keine Garantien für das Layout bieten mussten, die solche Konstrukte unverzichtbar machen würden, lehnten es die Autoren des Standards ab, zu verlangen, dass Compiler irgendein Konzept der Layout-Kompatibilität anerkennen, da diejenigen Compiler, bei denen eine solche Fähigkeit unverzichtbar wäre (z wie das obige in konsistenter Weise ausgelegt würde) haben es bereits unterstützt, und es gab keinen Grund zu der Annahme, dass sie dies nicht auch weiterhin tun würden, unabhängig davon, ob der Standard dies vorschreibt oder nicht. Der ausschlaggebende Faktor dafür, ob eine Funktion oder Garantie vorgeschrieben werden sollte, war nicht, ob die Kosten die Vorteile auf Plattformen überwiegen würden, die diese Funktion oder Garantie billig und einfach unterstützen könnten, sondern ob die Kosten auf Plattformen, auf denen die Unterstützung am teuersten und am wenigsten nützlich wäre würden die Vorteile überwiegen auf denselben Plattformen.

Leider haben Compiler-Autoren die Tatsache aus den Augen verloren, dass der Standard nur definiert, was für eine “konforme” Implementierung erforderlich ist, und nicht definiert, welche Funktionen etwas zu einem guten Compiler für eine bestimmte Plattform machen und folglich haben werden immer aggressiver, wenn es darum geht, Ausreden zu finden, um Präzedenzfälle auf Plattformen zu ignorieren, auf denen Verhaltensweisen jahrzehntelang zu minimalen Kosten unterstützt wurden. Infolgedessen funktioniert Code, der auf Verhaltensweisen beruht, die früher alltäglich waren, möglicherweise nur dann korrekt, wenn Compileroptionen wie verwendet werden -fno-strict-aliasing die weitaus mehr Optimierungen deaktivieren, als bei Verwendung eines weniger aggressiven Compilers erforderlich gewesen wären.

1368460cookie-checkC – Inkompatibler Zeigertyp

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

Privacy policy