Forum: Compiler & IDEs C: Überkreuzabhängigkeiten structs in Headerfiles


von Bahara (Gast)


Lesenswert?

Hallo zusammen,

stehe ich gerade vor einem "Henne-Ei-Problem"!

Habe zwei Headerfiles in denen jeweils eine Struktur definiert wird.
Das doofe: Die Struktur aus Headerfile A beinhaltet die in Headerfile B 
definierte Struktur und umgekehrt.

File a.h:
1
#ifndef a_h_
2
#define a_h_
3
4
#include "b.h"
5
6
typedef struct 
7
{
8
  uint8 x;
9
  uint8 y;
10
  MyStructB z;
11
} MyStructA;
12
13
#endif /* a_h_ */

File b.h:
1
#ifndef b_h_
2
#define b_h_
3
4
#include "a.h"
5
6
typedef struct 
7
{
8
  uint8 af;
9
  uint8 fe;
10
  MyStructA be;
11
  MyStructA af;
12
} MyStructB;
13
14
#endif /* b_h_ */

Wie bekomme ich dieses Dilemma gelöst?

Besten Dank und viele Grüße
Bahara

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Bahara schrieb:
> Wie bekomme ich dieses Dilemma gelöst?

Erstens dürfen structs einen Namen im struct-Namensraum bekommen:
1
struct foo {
2
  ...
3
};

Mit "struct foo" können sie immer angesprochen werden, ganz ohne
typedef. :)

Zweitens kann man sie vorwärts deklarieren:
1
struct foo;
2
3
struct bar {
4
   struct foo *foop;
5
   ...
6
};

Was natürlich nicht geht ist das, was du da gezeigt hast: ich
kann keine Struktur unbekannter Größe komplett in eine andere
einbetten.  Zirkular schon gar nicht, würde ja unendlich groß
werden. :-)  Zeiger darauf geht aber, den kann der Compiler bereits
festlegen, ohne die endgültige Größe der Struktur zu kennen.

Wenn man unbedingt auf die typedefs Wert legt, geht auch das:
1
typedef struct foo {
2
  ...
3
} foo;

Das ist sogar in C++ zulässig, obwohl dort "struct foo" unmittelbar
bereits den Namen "foo" im entsprechenden Scope definiert, auch ohne
weiteres Voranstellen von "struct".

: Bearbeitet durch Moderator
von Bahara (Gast)


Lesenswert?

Hi!

Danke für die schnelle Antwort!

Also
File a.h:
1
#ifndef a_h_
2
#define a_h_
3
4
#include "b.h"
5
6
typedef struct MyStructB;
7
typedef struct MyStructA
8
{
9
  uint8 x;
10
  uint8 y;
11
  MyStructB z;
12
} MyStructA;
13
14
#endif /* a_h_ */

File b.h:
1
#ifndef b_h_
2
#define b_h_
3
4
#include "a.h"
5
6
typedef struct MyStructA;
7
typedef struct MyStructB
8
{
9
  uint8 af;
10
  uint8 fe;
11
  MyStructA be;
12
  MyStructA af;
13
} MyStructB;
14
15
#endif /* b_h_ */

Funktioniert nicht! :-(
Fehlermeldung vom Compiler:
"warning: empty declaration with storage class specifier does not 
redeclare tag"
bzw.
"useless storage class specifier in empty declaration"

Oder habe ich deine Lösung falsch verstanden?

Grüße
Bahara

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Bahara schrieb:
> Hi!
>
> Danke für die schnelle Antwort!
>
> Also

[...] Du machst es ja immer noch wie im Original-Code:  Du willst ein 
unendlich großes Objekt kreieren.


> typedef struct MyStructA
> {
>   uint8 x;
>   uint8 y;
>   MyStructB z;
> } MyStructA;

also: sizeof(MyStructA) >= 2 + sizeof(MyStructB);

> typedef struct MyStructB
> {
>   uint8 af;
>   uint8 fe;
>   MyStructA be;
>   MyStructA af;
> } MyStructB;

also: sizeof(MyStructB) >= 2 + 2 * sizeof(MyStructA);

Rekursive Datenstrukturen gehen nur wie von Jörg erklärt via Zeiger.  Da 
Zeiger auch auf einen Incomplete Type zeigen dürfen, funktioniert das.

von Markus F. (mfro)


Lesenswert?

Bahara schrieb:
> Oder habe ich deine Lösung falsch verstanden?

Offensichtlich.

Das ist kein C- sondern ein logisches Problem.

Woher soll der Compiler - wenn er sich an der einen struct müht - 
wissen, wie gross die andere ist (und umgekehrt)?

Wenn Du zwei Töpfe hast und in den einen passen 1 l plus der Inhalt des 
anderen Topfes, in den 2 l plus der Inhalt des einen passen, wie gross 
sind dann die beiden Töpfe?

von Dirk B. (dirkb2)


Lesenswert?

Jörg W. schrieb:
> Zweitens kann man sie vorwärts deklarieren:
> struct foo;
>
> struct bar {
>    struct foo *foop;
>    ...
> };
>
> ...  Zeiger darauf geht aber, den kann der Compiler bereits
> festlegen, ohne die endgültige Größe der Struktur zu kennen.

Achte auf den * vor dem foop

von Nixblicker (Gast)


Lesenswert?

Wo ist das Problem?

Wenn Header A beide Strukturen braucht, dann bitte dort deklarieren. 
Modul B nutzt ja beide Header. Also kein Problem.

von (prx) A. K. (prx)


Lesenswert?

Nixblicker schrieb:
> Wo ist das Problem?

Das Problem ist die unendliche Rekursion der Structs. Ob in einem File 
oder in zweien spielt dafür wirklich keine Rolle, es geht weder in zwei 
Files noch in einem.

Ungefähre Analogie für jene, die an Unendlichkeiten scheitern: Wenn du 2 
Kartons vom Typ A hast und einen vom Typ B, und beide A-Kartons 
nebeneinander in den Karton B passen, dann ist der B-Karton grösser als 
beide A-Kartons zusammen. Also bekommst du den B-Karton definitiv nicht 
in einen der A-Kartons, ohne ihn zu zerquetschen.

: Bearbeitet durch User
von W.S. (Gast)


Lesenswert?

Bahara schrieb:
> typedef struct MyStructB;

Begreife doch endlich mal, wozu was gut ist!
Du fummelst hier ständig mit "typedef" herum.
Hast du es denn nicht gelernt, daß typedef eben NICHT zum Definieren von 
Typen gut ist, sondern lediglich dazu, einem bereits bekannten Typ einen 
zweiten Namen zu geben?

Also, einen struct definiert man so:

struct name_des_struct { inhalt_des_struct };

und wenn man was umbenennen will, dann so:

typedef alter_name   neuer_name;

Ist dir das jetzt klarer geworden?

W.S.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Wenn du das Problem mit den rekursiven Structs in den Griff bekommen
hast, wartet schon das Problem mit den rekursiven Includes auf dich:


File a.h:
1
#ifndef a_h_
2
#define a_h_
3
4
#include "b.h"
5
...
6
#endif /* a_h_ */


File b.h:
1
#ifndef b_h_
2
#define b_h_
3
4
#include "a.h"
5
...
6
#endif /* b_h_ */

Die Dateien a.h und b.h includen sich gegenseitig. Rekursive Includes –
auch wenn sie wie bei dir mit Guards versehen sind, führen fast immer zu
(für Anfänger meist völlig unerklärlichen) Fehlern. Man sollte (und
kann) sie auf jeden Fall vermeiden.

von (prx) A. K. (prx)


Lesenswert?

W.S. schrieb:
> Ist dir das jetzt klarer geworden?

Als Anfänger von C(!) wäre ich jetzt noch verwirrter als vorher. Denn du 
verwechselst C mit C++.

Der Name einer struct hat in C einen eigenen Namespace, getrennt von 
allen anderen Namespaces, also auch dem Namespace der Typedefs. Ein 
Typedef ist also in C eben keine Umbenennung, sondern eine Abkürzung.
1
 struct S { ... };    // definiert die Struct S, aber nicht den Typ S
2
 typedef struct S S;  // definiert den Typ S als identisch mit "struct S"

> Hast du es denn nicht gelernt, daß typedef eben NICHT zum Definieren von
> Typen gut ist, sondern lediglich dazu, einem bereits bekannten Typ einen
> zweiten Namen zu geben?

Der typische Fall in C (nicht C++) ist tatsächlich die Definition von 
Structs im Rahmen eines Typedefs, unter Verwendung anonymer Structs:
  typedef struct { ... } S;

Einen Struct-Namen benötigt man nur für den hier relevanten Fall von 
Vorwärtsreferenzen:
1
 typedef struct S { struct S *p; } S;
In der Praxis sollte man die beiden S verschieden bezeichnen
1
 typedef struct S_s { struct S_s *p; } S_t;
um weniger zu verwirren. Nötig ist das aber nicht.

Typedefs sind in erst nachträglich zur Sprache hinzugefügt worden, 
wenngleich schon in K&R-C. Anfangs gab es keine Typedefs. Und sehr 
geglückt ist das Ergebnis nicht, nur muss man damit eben leben. Erst C++ 
hat das grundrenoviert, zum Preis möglicher Namenskonflikte.

: Bearbeitet durch User
von Yalu X. (yalu) (Moderator)


Lesenswert?

Hier ist mal eine Möglichkeit, wie die Rekursionsprobleme unter
Beibehaltung der Typedefs aufgelöst werden können:


a.h:
1
#ifndef a_h_
2
#define a_h_
3
4
#include <stdint.h>
5
#include "b.h"
6
7
typedef struct MyStructA
8
{
9
  uint8_t x;
10
  uint8_t y;
11
  MyStructB z;
12
} MyStructA;
13
14
#endif /* a_h_ */


b.h:
1
#ifndef b_h_
2
#define b_h_
3
4
#include <stdint.h>
5
6
typedef struct MyStructA MyStructA;
7
8
typedef struct 
9
{
10
  uint8_t af;
11
  uint8_t fe;
12
  MyStructA *be;
13
  MyStructA *ef;
14
} MyStructB;
15
16
#endif /* b_h_ */

Die MyStructA-Elemente in MyStructB werden dabei durch Pointer ersetzt.
Damit ist schon einmal das Problem mit den unendlich großen Structs
gelöst.

Da in b.h wegen der Pointer-Deklarationen nicht der vollständige Typ von
MyStructA bekannt sein muss, kann auch das #include "a.h" entfallen.
Damit das anonyme Struct, das bisher nur den Typedef-Namen MyStructA
hat, trotzdem in b.h. (wenigstens als unvollständiger Typ) verwendet
werden kann, erhält es zusätzlich denselben Namen als Struct-Namen.
Durch diese beiden Änderungen ist die Include-Rekursion beseitigt.

Anmerkung: Der Typedef von MyStructA ist doppelt (in a.h und in b.h).
Derjenige in a.h ist eigentlich überflüssig, ich würde ihn aber aus
Gründen der besseren Verständlichkeit trotzdem beibehalten.

Alternativ oder zusätzlich könntest du aber auch das MyStructB-Element
in MyStructA durch einen Pointer ersetzen. Du könntest dann auch a.h in
b.h statt b.h in a.h includen. Welche der vielen Möglichkeiten die
letztendlich die praktischere ist, hängt davon ab, in welcher Weise du
die Structs in deiner Anwendung verwenden willst.

Noch eine letzte Anmerkung: Rindfleisch heißt auf Englisch "beef" und
nicht "beaf". Dieses Wissen hilft dabei, den letzten verbleibenden
Fehler zu beseitigen ;-)

Edit: Ach ja, ich habe auch die uint8 durch die in stdint.h normgemäß
deklarierten uint8_t ersetzt. Dann brauchst du diese Typen nicht selber
deklarieren.

: Bearbeitet durch Moderator
von Bahara (Gast)


Lesenswert?

Abend zusammen!

Dank' euch! - Ich schau mal am Montag, wenn ich wieder in der Schule 
bin, ob ich es gelöst bekomme!

Viele Grüße
Bahara

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Yalu X. schrieb:
> Wenn du das Problem mit den rekursiven Structs in den Griff bekommen
> hast, wartet schon das Problem mit den rekursiven Includes auf dich:

Eine Möglichkeit geht so:

a.h
1
#ifndef A_H_
2
#define A_H_
3
4
struct B; /* Defined in "b.h" */
5
6
typedef struct A
7
{
8
    int value;
9
    struct A *pA;
10
    struct B *pB;
11
} A;
12
#endif /* A_H_ */
 
b.h
1
#ifndef B_H_
2
#define B_H_
3
4
struct A; /* Defined in "a.h" */
5
6
typedef struct B
7
{
8
    double wert;
9
    struct A *pA;
10
    struct B *pB;
11
} B;
12
#endif /* B_H_ */

Die Reihenfolge, in der a.h bzw. b.h includet werden, spielt dann keine 
Rolle.  Es können sogar A-Objekte angelegt werden, ohne b.h zu includen:
1
#include <stddef.h>
2
#include "a.h"
3
4
A a = { 1, NULL, NULL };
Erst wenn versucht wird, einen Zeiger auf B zu dereferenzieren, meckert 
der Compiler, da struct B ohne Include von b.h "incomplete" ist:
1
#include <stddef.h>
2
#include "a.h"
3
4
A a = { 1, &a, NULL };
5
6
int main()
7
{
8
    a.pB->wert = 0;
9
}
 
Sowas kann garnicht compilieren, weil nirgendwo gesagt wurde, welche 
Komponenten struct B überhaupt hat:
 
1
In function 'main':
2
error: dereferencing pointer to incomplete type 'struct B'
3
     a.pB->wert = 0;
4
         ^~

Tatsächlich kann man dies als Feature verwenden, um eine rigorose 
Kapselung zu implementieren, welche die "Kapselung" in C++ mit "private" 
etc. an Strenge noch übertrifft:  Dazu nehmen wir zur Einfachheit an, 
dass sich das a-Modul auch um B kümmert, dessen Definition aber nicht 
offenlegt.  Jeglicher Zugriff auf Bs wird dann über Zugriffsfunktionen 
geregelt. Etwa:
1
#include "a.h"
2
/* Zusätzlich in "a.h" */
3
extern double B_get_value (const struct B*);
4
extern struct B* B_construct (double);
5
6
int main()
7
{
8
    A a = { 1, &a, B_construct (3.14) };
9
    printf ("Wert = %f\n", B_get_value (a.pB));
10
    /* B_free ... */
11
}
Wie B intern aufgebaut ist, bleibt Geheimnis des implementierenden 
Moduls, hier von a.c
1
#include <stdlib.h>
2
#include "a.h"
3
4
typedef struct B
5
{
6
    double wert;
7
    struct A *pA;
8
    struct B *pB;
9
} B;
10
11
double B_get_value (const B *b)
12
{
13
    return b->wert;
14
}
15
16
B* B_construct (double value)
17
{
18
    B *b = malloc (sizeof (B));
19
    b->wert = value;
20
    b->pA = NULL;
21
    b->pB = NULL;
22
23
    return b;
24
}

Alles, was Module außer a.c über B zu wissen brauchen, ist der Name der 
Struktur, und alle Module außer B bekommen lediglich ein "Handle"; hier 
die Adresse eines B.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.