Hallo zusammen,
ich bin gerade an einem kleinen Programmierproblem, bei dem ich davon
ausgehe, daß ich von "incomplete"-Typen profitieren kann. Allerdings bin
ich da noch nicht ganz fit. Ich definiere in der *.h-Datei:
1
// Unvollstaendiger Typ, da Inhalt gekapselt werden soll:
So weit so gut. Was ich allerdings noch nicht verstehe:
- Wozu wird das Tag "dcmotorstruct" benötigt? Lasse ich es weg,
beschwert sich der (AVR-)GCC mit "Error: unknown type name 'dcmotor_t'"
- So beschwert er sich mit "Warning: redefinition of typedef 'dcmotor_t'
[-Wpedantic]" - wo liegt das Problem?
Viele Grüße
W.T.
Walter T. schrieb:> - Wozu wird das Tag "dcmotorstruct" benötigt?
Damit du nicht dauernd an allen Ecken und Enden in C "struct
dcmotorstruct" schreiben musst.
> Lasse ich es weg,> beschwert sich der (AVR-)GCC mit "Error: unknown type name 'dcmotor_t'"
Logisch. Woher soll er denn in
1
dcmotor_t*dcmotor_init(dcmotorinit_t*initdata);
2
*********
wissen, was ein dcmotor_t ist bzw. dass es sowas überhaupt gibt?
Auch wenn die Funktion nur einen Pointer retourniert, so möchte der
Compiler dann schon auch noch überprüfen, ob "dcmotor_t" irgendwie
definiert ist, oder ob du einfach einen Tippfehler gemacht hast.
Mit dem vorhergehenden
1
typedefstructdcmotorstructdcmotor_t;
hast du dem Compiler bekannt gegeben, dass "dcmotor_t" eine andere
Bezeichnung für "struct dcmotorstruct" ist. die beiden sind also
synonym.
Du hättest genausogut auch den typedef weglassen können und statt dessen
den Protoypen (bzw. die Funktion) auch so schreiben können
nur musst du (und derjenige der den Aufruf dieser Funktion schreibt und
wohl den zurückgegebenen Pointer irgendwo speichern wird), dann jedesmal
die Langform mit dem 'struct' schreiben.
wenn du im Header File schon einen typedef hast, der einen anderen Namen
für ein 'struct dcmotorstruct' festlegt, dann brauchst du hier keinen
mehr.
Du willst nur näher ausführen, wass denn jetzt ein struct dcmotorstruct
tatsächlich ist. Dann mach das und auch nur das
1
// Unvollstaendigen Typ fertigdeklarieren:
2
structdcmotorstruct{
3
uint8_t*GPIOA;// Beide Pins der Vollbruecke
4
uint8_t*GPIOB;// und Enable
5
uint8_t*GPIOEn;
6
uint8_tPinA;
7
uint8_tPinB;
8
uint8_tPinEn;
9
};
Vielleicht doch mal in einem C Buch nachlesen, was es mit den
Einzelteilen der Sprache so auf sich hat und nicht nur aus irgendwelchen
Web-Beispielen sich die Dinge unverstanden zusammenklauben? Deinem
Sprachverständnis würde es gewaltig auf die Füsse helfen, wenn du deine
Programmiersprache systematisch erlernen würdest und wüsstest wozu die
einzelnen Schlüsselwörter gut sind und was sie machen.
Aber das hab ich dir glaub ich schon ein paar mal gesagt.
Karl H. schrieb:> Du hättest genausogut auch den typedef weglassen können und statt dessen> den Protoypen (bzw. die Funktion) auch so schreiben können>
> nur musst du (und derjenige der den Aufruf dieser Funktion schreibt und> wohl den zurückgegebenen Pointer irgendwo speichern wird), dann jedesmal> die Langform mit dem 'struct' schreiben.
Und du müsstest natürlich im Header File dann auch noch bekannt geben,
dass es ein 'struct dcmotorstruct' auch wirklich gibt. Mit einer
Forward-Deklaration:
1
structdcmotorstruct;// die gibt es wirklich, auch wenn hier nichts darueber
ist beides in einem: Forward Deklaration und Alias-Bildung des neuen
Namens, weil mir ein typedef auch erlaubt, den Original-Datentyp, für
den der neue Name zu bilden ist, auch erst innerhalb des typedefs zu
deklarieren. Genau das hast du ja auch hier gemacht:
1
typedefstructdcmotorstruct{
2
uint8_t*GPIOA;// Beide Pins der Vollbruecke
3
uint8_t*GPIOB;// und Enable
4
uint8_t*GPIOEn;
5
uint8_tPinA;
6
uint8_tPinB;
7
uint8_tPinEn;
8
}dcmotor_t;
auch hier ist Alias-Bildung und Deklaration des originalen Datentyps an
einer Stelle beisammen. Ausgangspunkt wäre eigentlich, dass es eine
struct gibt, die so aussieht
1
structdcmotorstruct{
2
uint8_t*GPIOA;// Beide Pins der Vollbruecke
3
uint8_t*GPIOB;// und Enable
4
uint8_t*GPIOEn;
5
uint8_tPinA;
6
uint8_tPinB;
7
uint8_tPinEn;
8
};
und für die ein neuer (kürzerer) Name gebildet werden soll
1
structdcmotorstruct{
2
uint8_t*GPIOA;// Beide Pins der Vollbruecke
3
uint8_t*GPIOB;// und Enable
4
uint8_t*GPIOEn;
5
uint8_tPinA;
6
uint8_tPinB;
7
uint8_tPinEn;
8
};
9
10
typedefstructdcmotorstructdcmotor_t;
aber aus praktischen Gründen erlaubt es der typedef, die Deklaration des
Datentyps auch in den typedef hineinzuziehen. Bzw. wenn man es ganz
genau nimmt, dann ist das noch nicht einmal spezifisch für einen
typedef. An allen Stellen an denen der Name eines Datentyps steht, kann
ich den Datentyp erst dort definieren.
ein
1
voidfoo(structabc{
2
inta;
3
intb;
4
intc;
5
}arg1)
6
{
7
}
sieht zwar seltsam aus (und ist reichlich sinnlos, denn wie soll denn
der Aufrufer ein derartiges Objekt erzeugen?). Aber falsch ist im Sinne
der C Syntax daran nichts.
definiert - auch wenn's angesichts des Namens eigentlich naheliegend
wäre - streng genommen keinen neuen Typ.
Vielmehr vergibt es einen (idealerweise kürzeren und "sprechenderen")
neuen Namen ("alias") für einen bereits existierenden Typ.
Hallo Karl-Heinz,
danke für Deine ausführlichen Ausführungen.
Karl H. schrieb:> Vielleicht doch mal in einem C Buch nachlesen, was es mit den> Einzelteilen der Sprache so auf sich hat und nicht nur aus irgendwelchen> Web-Beispielen sich die Dinge unverstanden zusammenklauben?
Ich muß zugeben, daß ich den K&R tatsächlich erst wieder heute abend
griffbereit haben werde - momentan muß es im Web verfügbare Doku tun.
Leider habe ich mich irgendwann damit abfinden müssen, daß ich auch
-trotz intensiver Literaturarbeit- diese Compiler-Denkweise nie erlernen
können werde. Das führt dazu, für neue Konstrukte immer auf
vertrauenswürdige Vorlagen angewiesen zu sein. Ein bischen ist das
traurig, aber ich kann damit leben, mit C eher herumzudilletieren -
genau wie ich im Französischen und Spanischen niemals große Literatur
vollbringen werde.
Walter T. schrieb:> Leider habe ich mich irgendwann damit abfinden müssen, daß ich auch> -trotz intensiver Literaturarbeit- diese Compiler-Denkweise
das hat mit Compilern eigentlich wenig zu tun.
Auf der Uni wäre das die Vorlesung 'formale Sprachen', in der man darauf
eingeht, wie Programmiersprachen aufgebaut sind. Genauso wie hinter
einer natürlichen Sprache, steckt auch hinter Programmiersprachen eine
innere Logik. Da gibt es natürlich viele verschiedene Möglichkeiten und
Ansätze. Je besser man die Logik seiner Programiersprache versteht,
desto leichter tut man sich beim entziffern von fremden Code.
Die einfachen Dinge holt man sich schnell aus ein paar Beispielen
zusammen. Aber wenn es darum geht, dass ein paar Sprachgrundsätze
gemeinsam benutzt und inneinander verwoben benutzt werden, dann führt so
ein Vorgehen nun mal zu nichts. Kennt man aber die Grundsätze, dann
erschliesst sich einem plötzlich alles.
Ein Compiler ist nur ein Werkzeug, das überprüft, ob das Geschreibsel
dieser inneren Logik entspricht oder nicht.
Karl H. schrieb:> denn wie soll denn> der Aufrufer ein derartiges Objekt erzeugen?
Hm....das ist eine Knobelaufgabe für heute abend, warum ein anonymes
struct nicht funktioniert:
1
foo(structabc{
2
inta;
3
intb;
4
intc;
5
}a={.a=1,.b=2,.c=3});
Danke, daß Du Dir die Zeit nimmst, nochmal die Alias-Bildung mit typedef
aufzudröseln. Bislang hatte ich die Deklaration eine structs mit Namen
für ein Relikt aus der Zeit vor typedef gehalten.
Walter T. schrieb:> foo( struct abc {> int a;> int b;> int c;> } a = {.a = 1, .b= 2, .c = 3 } );
Du versuchst hier ziemlich viele Dinke in eine einzelne Anweisung zu
packen:
1. die Deklaration der Struktur abc
2. die Definition der Variable a
3. die Initialisierung dieser Variable
4. den Aufruf der Funktion foo mit a als Argument
Alle vier sind ein Bisschen arg viel.
Vor allem: Woher soll foo, das vielleicht in einem ganz anderen
Softwaremopdul definiert wurde, den Aufbau der Struktur abc kennen?
Deswegen beißen sich die Punkte 1 und 4.
Wenn du aber einen der beiden weglässt, sieht die Scahe schon ander aus:
Die Punkte 1 bis 3 bilden zusammen eine ganz normale Strukturdeklaration
mit gleichzeitiger Variablendefinition und -initialisierung:
1
structabc{
2
inta;
3
intb;
4
intc;
5
}a={.a=1,.b=2,.c=3});
Die Punkte 2 bis 4 gehen folgendermaßen:
1
foo((structabc){.a=1,.b=2,.c=3});
Die Struktur abc muss schon voeher irgendwo deklariert worden sein. Der
Variablenname a fällt weg, weil er sowieso nicht benötigt wird.
Das erste ist dabei die Vorwärts-Deklaration, die dem Compiler sagt:
„Es gibt eine Struktur, die heißt dcmotorstruct (*). Details darüber
weiß ich jetzt gerade noch nicht, die kommen bei Bedarf später.“ Danach
kommt eine Funktionsdeklaration, die diese so definierte Struktur
benutzt. Das funktioniert, da an dieser Stelle nur Zeiger auf diese
Strukturen benutzt werden müssen, die kann der Compiler gedanklich
immer bilden, ohne den Inhalt der Strukturen kennen zu müssen.
Erst, wenn man auf die Elemente der Struktur zugreifen will, muss sie
dann irgendwo vollständig definiert worden sein.
(*) Innerhalb des “struct”-Namensraums, der von den übrigen Namensräumen
bei C getrennt ist. Daher muss man das “struct” dann auch immer davor
schreiben.
Der typedef wiederum würde daraus einen neuen Namen machen, den man
ohne “struct” benutzen kann. Manche Leute finden das bequemer.
Einer der wesentlichen Unterschiede bei C++ ist es übrigens, dass es
für “struct” keinen separaten Namensraum mehr gibt, d. h. mit obiger
Deklaration hast du einen globalen Typnamen “dcmotorstruct” erzeugt.