Struktur vs. Funktionsdefinitionen im Geltungsbereich

Lesezeit: 9 Minuten

Benutzer-Avatar
V0ldek

Soweit ich weiß, ist dies in C legal:

foo.c

struct foo {
   int a;
};

bar.c

struct foo {
    char a;
};

Aber dasselbe mit Funktionen ist illegal:

foo.c

int foo() {
    return 1;
}

bar.c

int foo() {
    return 0;
}

und führt zu einem Verknüpfungsfehler (mehrere Definitionen von function foo).

Warum ist das so? Was ist der Unterschied zwischen Strukturnamen und Funktionsnamen, der C nicht in der Lage macht, mit dem einen, aber nicht mit dem anderen umzugehen? Gilt dieses Verhalten auch für C++?

  • Sie verwechseln Deklarationen und Definitionen. struct ist eine Deklaration. Es deklariert einen neuen Datentyp, erstellt aber keine neuen Objekte. Eine Funktion hingegen wird definiert (Sie erstellen eine neue Funktionsentität).

    – DYZ

    29. Mai 2018 um 18:34 Uhr


  • Mögliches Duplikat von stackoverflow.com/questions/33163028/…

    – DYZ

    29. Mai 2018 um 18:35 Uhr

  • @DyZ Sagst du das struct foo wird hier deklariert und nicht definiert?

    – Neugieriger

    20. Juni 2018 um 16:20 Uhr

Benutzer-Avatar
R Sahu

Warum ist das so?

struct foo {
   int a;
};

definiert eine Vorlage zum Erstellen von Objekten. Es erstellt keine Objekte oder Funktionen. Wenn nicht struct foo irgendwo in Ihrem Code verwendet wird, soweit es den Compiler/Linker betrifft, können diese Codezeilen genauso gut nicht existieren.

Bitte beachten Sie, dass es einen Unterschied gibt, wie C und C++ mit Inkompatibilität umgehen struct Definitionen.

Die unterschiedlichen Definitionen von struct foo in Ihrem geposteten Code ist in einem C-Programm in Ordnung, solange Sie ihre Verwendung nicht mischen.

In C++ ist es jedoch nicht zulässig. In C++ haben sie eine externe Verknüpfung und müssen identisch definiert werden. Sehen 3.2 Eine Definitionsregel/5 für weitere Details.

  • Aber wenn ich benutzte struct foo in foo.c würde es diese Definition verwenden, und wenn ich es in bar.c verwenden würde, würde es die Definition von bar verwenden, nicht wahr?

    – V0ldek

    29. Mai 2018 um 18:40 Uhr

  • @V0ldek, ja, das ist richtig. Wenn Sie sie nicht mischen, dh nicht passieren a foo von foo.c zu einer Funktion, die a erwartet foo von bar.c, sollten Sie in Ordnung sein.

    – R Sahu

    29. Mai 2018 um 18:47 Uhr


  • Also, was ist das Verhalten? Der Compiler verwendet die lokale Definition über external? Denn sicher, wenn ich erkläre struct foo zweimal in der gleichen Datei bekomme ich eine Fehlermeldung.

    – V0ldek

    29. Mai 2018 um 18:50 Uhr

  • @V0ldek, es gibt keine externe Definition von a struct. Es ist nur das, was der Compiler sieht, wenn er eine .c-Datei kompiliert.

    – R Sahu

    29. Mai 2018 um 18:53 Uhr

  • In C++ ist es eine Verletzung von ODR

    – SergejA

    29. Mai 2018 um 19:04 Uhr

Benutzer-Avatar
AnT steht zu Russland

Der Unterscheidungsbegriff heißt in diesem Fall Verknüpfung.

In C haben struct, union oder enum Tags keine Verknüpfung. Sie sind effektiv lokal in ihrem Geltungsbereich.

6.2.2 Verknüpfungen von Identifikatoren
6 Die folgenden Bezeichner haben keine Verknüpfung: ein Bezeichner, der als etwas anderes als ein Objekt oder eine Funktion deklariert ist; ein als Funktionsparameter deklarierter Bezeichner; eine Blockbereichskennung für ein Objekt, das ohne den Speicherklassenbezeichner deklariert wurde extern.

Sie können nicht im gleichen Umfang erneut deklariert werden (außer sog Erklärungen weiterleiten). Sie können jedoch in verschiedenen Bereichen, einschließlich verschiedener Übersetzungseinheiten, frei neu deklariert werden. In verschiedenen Bereichen können sie völlig unabhängige Typen deklarieren. In Ihrem Beispiel haben Sie Folgendes: In zwei verschiedenen Übersetzungseinheiten (dh in zwei verschiedenen Dateibereichen) haben Sie zwei verschiedene und nicht zusammenhängende deklariert struct foo Typen. Das ist vollkommen legal.

Inzwischen haben Funktionen in C eine Verknüpfung. In Ihrem Beispiel definieren diese beiden Definitionen dieselbe Funktion foo mit extern Verknüpfung. Und Sie dürfen nicht mehr als eine Definition einer externen Verknüpfungsfunktion in Ihrem gesamten Programm angeben

6.9 Externe Definitionen
5 […] Wenn ein mit externer Verknüpfung deklarierter Bezeichner in einem Ausdruck verwendet wird (anders als als Teil des Operanden von a sizeof oder _Alignof Operator, dessen Ergebnis eine ganzzahlige Konstante ist), muss es irgendwo im gesamten Programm genau eine externe Definition für den Bezeichner geben; andernfalls darf es nicht mehr als einen geben.


In C++ ist das Konzept von Verknüpfung erweitert: Sie weist einer viel größeren Vielfalt von Entitäten, einschließlich Typen, spezifische Verknüpfungen zu. In C++ haben Klassentypen eine Verknüpfung. Klassen, die im Namensraumbereich deklariert sind, haben externe Verknüpfung. Und eine Definitionsregel von C++ besagt ausdrücklich, dass, wenn eine Klasse mit externer Verknüpfung mehrere Definitionen (über verschiedene Übersetzungseinheiten hinweg) hat, sie in allen diesen Übersetzungseinheiten äquivalent definiert werden muss (http://eel.is/c++draft/basic.def.odr#12). Also, in C++ Ihre struct Definitionen wären illegal.

Ihre Funktionsdefinitionen bleiben auch in C++ wegen der C++-ODR-Regel illegal (aber im Wesentlichen aus den gleichen Gründen wie in C).

  • Bedeutet Abschnitt 6.9.5, dass die Operanden der sizeof und _Alignof Operatoren können mit extern verknüpften Bezeichnern versehen werden mehr als eine Definition? Oder impliziert es, dass ein Operand dieser Operatoren kein wohldefinierter Bezeichner sein muss? Das heißt, diesen Operatoren kann ein deklarierter, aber nicht definierter Bezeichner übergeben werden.

    – Nicolas Cousar

    24. September 2021 um 19:57 Uhr

  • @Nicholas Cousar: Es soll sagen, dass keine Definition erforderlich ist.

    – AnT steht zu Russland

    25. September 2021 um 20:15 Uhr

Ihre Funktionsdefinitionen deklarieren beide eine aufgerufene Entität foo mit externer Verknüpfung, und der C-Standard besagt, dass es nicht mehr als eine Definition einer Entität mit externer Verknüpfung geben darf. Die von Ihnen definierten Strukturtypen sind keine Entitäten mit externer Verknüpfung, sodass Sie mehr als eine Definition von haben können struct foo.

Wenn Sie Objekte mit externer Verknüpfung unter demselben Namen deklariert haben, wäre dies ein Fehler:

foo.c

struct foo {
   int a;
};
struct foo obj;

bar.c

struct foo {
    char a;
};
struct foo obj;

Jetzt haben Sie zwei aufgerufene Objekte obj dass beide eine externe Verlinkung haben, was nicht erlaubt ist.

Es wäre immer noch falsch, selbst wenn eines der Objekte nur deklariert, nicht definiert ist:

foo.c

struct foo {
   int a;
};
struct foo obj;

bar.c

struct foo {
    char a;
};
extern struct foo obj;

Dies ist undefiniert, da die beiden Deklarationen von obj beziehen sich auf dasselbe Objekt, haben aber keine kompatiblen Typen (weil struct foo ist in jeder Datei anders definiert).

C++ hat ähnliche, aber komplexere Regeln zu berücksichtigen inline Funktionen u inline Variablen, Vorlagen und andere C++-Features. In C++ sind die relevanten Anforderungen als One-Definition Rule (oder ODR) bekannt. Ein bemerkenswerter Unterschied besteht darin, dass C++ nicht einmal die beiden Unterschiede zulässt struct Definitionen, auch wenn sie niemals verwendet werden, um Objekte mit externer Verknüpfung zu deklarieren oder auf andere Weise zwischen Übersetzungseinheiten “gemeinsam” zu sein.

Benutzer-Avatar
dbusch

Die beiden Deklarationen für struct foo sind nicht miteinander kompatibel, da die Typen der Mitglieder nicht gleich sind. Die Verwendung beider innerhalb jeder Übersetzungseinheit ist in Ordnung, solange Sie nichts tun, um die beiden zu verwechseln.

Wenn Sie zum Beispiel dies getan haben:

foo.c:

struct foo {
   char a;
};

void bar_func(struct foo *f);

void foo_func()
{
    struct foo f;
    bar_func(&f);
}

bar.c:

struct foo {
   int a;
};

void bar_func(struct foo *f)
{
    f.a = 1000;
}

Du würdest anrufen undefiniertes Verhalten weil die struct foo das bar_func erwartet ist nicht kompatibel mit dem struct foo das foo_func liefert.

Die Kompatibilität von Strukturen wird in Abschnitt 6.2.7 der beschrieben C-Standard:

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 in separaten Ü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. Bei zwei Aufzählungen müssen entsprechende Mitglieder die gleichen Werte haben.

2 Alle Deklarationen, die sich auf dasselbe Objekt oder dieselbe Funktion beziehen, müssen einen kompatiblen Typ haben; andernfalls ist das Verhalten undefiniert.

Zusammenfassend sind die beiden Instanzen von struct foo müssen Mitglieder mit demselben Namen und Typ und in derselben Reihenfolge haben, um kompatibel zu sein.

Solche Regeln werden benötigt, damit a struct kann einmal in einer Header-Datei definiert werden, und dieser Header wird anschließend in mehrere Quelldateien eingefügt. Daraus ergibt sich die struct in mehreren Quelldateien definiert, wobei jedoch jede Instanz kompatibel ist.

Der Unterschied liegt weniger in den Namen als in der Existenz; Eine Struct-Definition wird nirgendwo gespeichert und ihr Name existiert nur während der Kompilierung.
(Es liegt in der Verantwortung des Programmierers, sicherzustellen, dass es bei der Verwendung von Strukturen mit identischen Namen keine Konflikte gibt. Andernfalls kommt unser lieber alter Freund Undefined Behavior.)

Andererseits muss eine Funktion irgendwo gespeichert werden, und wenn sie eine externe Verknüpfung hat, benötigt der Linker ihren Namen.

Wenn Sie Ihre Funktionen machen staticalso außerhalb ihrer jeweiligen Kompilationseinheit “unsichtbar”, verschwindet der Verknüpfungsfehler.

  • Ich glaube, dass es in C nicht undefiniert ist, zwei inkompatible Typdefinitionen mit demselben Namen zu haben, solange keine zwei Deklarationen, die den Namen verwenden, auf dieselbe Entität verweisen. zB keine Deklarationen von Objekten oder Funktionen mit externer Bindung, die unterschiedliche Definitionen des Typs verwenden.

    – Jonathan Wakely

    29. Mai 2018 um 18:54 Uhr

  • @JonathanWakely Darauf wollte ich hinaus mit „Konflikt in der Verwendet…”, aber ich habe mich wohl etwas unklar ausgedrückt.

    – molbnilo

    29. Mai 2018 um 18:57 Uhr


  • Jetzt, wo ich es noch einmal gelesen habe, ist es klar, sorry für die Erbsenzählerei, was bereits richtig war!

    – Jonathan Wakely

    29. Mai 2018 um 19:01 Uhr

Benutzer-Avatar
armagedescu

Um die Funktionsdefinition vor dem Linker zu verbergen, verwenden Sie das Schlüsselwort static.

foo.c

    static int foo() {
        return 1;
    }

bar.c

    static int foo() {
        return 0;
    }

  • Ich glaube, dass es in C nicht undefiniert ist, zwei inkompatible Typdefinitionen mit demselben Namen zu haben, solange keine zwei Deklarationen, die den Namen verwenden, auf dieselbe Entität verweisen. zB keine Deklarationen von Objekten oder Funktionen mit externer Bindung, die unterschiedliche Definitionen des Typs verwenden.

    – Jonathan Wakely

    29. Mai 2018 um 18:54 Uhr

  • @JonathanWakely Darauf wollte ich hinaus mit „Konflikt in der Verwendet…”, aber ich habe mich wohl etwas unklar ausgedrückt.

    – molbnilo

    29. Mai 2018 um 18:57 Uhr


  • Jetzt, wo ich es noch einmal gelesen habe, ist es klar, sorry für die Erbsenzählerei, was bereits richtig war!

    – Jonathan Wakely

    29. Mai 2018 um 19:01 Uhr

1372320cookie-checkStruktur vs. Funktionsdefinitionen im Geltungsbereich

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

Privacy policy