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
typedefstruct
7
{
8
uint8x;
9
uint8y;
10
MyStructBz;
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
typedefstruct
7
{
8
uint8af;
9
uint8fe;
10
MyStructAbe;
11
MyStructAaf;
12
}MyStructB;
13
14
#endif /* b_h_ */
Wie bekomme ich dieses Dilemma gelöst?
Besten Dank und viele Grüße
Bahara
Bahara schrieb:> Wie bekomme ich dieses Dilemma gelöst?
Erstens dürfen structs einen Namen im struct-Namensraum bekommen:
1
structfoo{
2
...
3
};
Mit "struct foo" können sie immer angesprochen werden, ganz ohne
typedef. :)
Zweitens kann man sie vorwärts deklarieren:
1
structfoo;
2
3
structbar{
4
structfoo*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
typedefstructfoo{
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".
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
typedefstructMyStructB;
7
typedefstructMyStructA
8
{
9
uint8x;
10
uint8y;
11
MyStructBz;
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
typedefstructMyStructA;
7
typedefstructMyStructB
8
{
9
uint8af;
10
uint8fe;
11
MyStructAbe;
12
MyStructAaf;
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
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.
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?
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
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.
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.
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.
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
structS{...};// definiert die Struct S, aber nicht den Typ S
2
typedefstructSS;// 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
typedefstructS{structS*p;}S;
In der Praxis sollte man die beiden S verschieden bezeichnen
1
typedefstructS_s{structS_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.
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
typedefstructMyStructA
8
{
9
uint8_tx;
10
uint8_ty;
11
MyStructBz;
12
}MyStructA;
13
14
#endif /* a_h_ */
b.h:
1
#ifndef b_h_
2
#define b_h_
3
4
#include<stdint.h>
5
6
typedefstructMyStructAMyStructA;
7
8
typedefstruct
9
{
10
uint8_taf;
11
uint8_tfe;
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.
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
structB;/* Defined in "b.h" */
5
6
typedefstructA
7
{
8
intvalue;
9
structA*pA;
10
structB*pB;
11
}A;
12
#endif /* A_H_ */
b.h
1
#ifndef B_H_
2
#define B_H_
3
4
structA;/* Defined in "a.h" */
5
6
typedefstructB
7
{
8
doublewert;
9
structA*pA;
10
structB*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
Aa={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
Aa={1,&a,NULL};
5
6
intmain()
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
externdoubleB_get_value(conststructB*);
4
externstructB*B_construct(double);
5
6
intmain()
7
{
8
Aa={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
typedefstructB
5
{
6
doublewert;
7
structA*pA;
8
structB*pB;
9
}B;
10
11
doubleB_get_value(constB*b)
12
{
13
returnb->wert;
14
}
15
16
B*B_construct(doublevalue)
17
{
18
B*b=malloc(sizeof(B));
19
b->wert=value;
20
b->pA=NULL;
21
b->pB=NULL;
22
23
returnb;
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.