Forum: Compiler & IDEs Struct: unvollständiger Typ


von Walter T. (nicolas)


Lesenswert?

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:
2
typedef struct dcmotorstruct dcmotor_t;
3
4
dcmotor_t * dcmotor_init(dcmotorinit_t * initdata);
und in der *.c-Datei:
1
    // Unvollstaendigen Typ fertigdeklarieren:
2
    typedef struct dcmotorstruct {
3
        uint8_t * GPIOA; // Beide Pins der Vollbruecke
4
        uint8_t * GPIOB; // und Enable
5
        uint8_t * GPIOEn; 
6
        uint8_t PinA;
7
        uint8_t PinB;
8
        uint8_t PinEn;
9
    } dcmotor_t;
10
    // TODO: "volatile"-qualifier passend hineinknobeln
11
12
13
14
    dcmotor_t * dcmotor_init(dcmotorinit_t * pinitdata)
15
    {
16
        dcmotor_t *pmotor = (dcmotor_t *) malloc(sizeof(dcmotor_t));
17
        pmotor->PinA  = pinitdata->PinA;
18
        pmotor->PinB  = pinitdata->PinB;
19
        pmotor->GPIOA = pinitdata->GPIOA;
20
        pmotor->GPIOB = pinitdata->GPIOB;
21
        
22
        // Pins auf Ausgang
23
        gpio_setOutput(pmotor->GPIOA,pmotor->PinA);
24
        gpio_setOutput(pmotor->GPIOB,pmotor->PinB);
25
        gpio_setOutput(pmotor->GPIOEn,pmotor->PinEn);        
26
        
27
        // Pins high bis auf Enable
28
        SetBit(pmotor->GPIOA,pmotor->PinA);
29
        SetBit(pmotor->GPIOB,pmotor->PinB);
30
        ClearBit(pmotor->GPIOEn,pmotor->PinEn);
31
32
        return pmotor;
33
        
34
    }
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.

von Karl H. (kbuchegg)


Lesenswert?

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
typedef struct dcmotorstruct dcmotor_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
1
struct dcmotorstruct * dcmotor_init(dcmotorinit_t * initdata);
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.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

1
    // Unvollstaendigen Typ fertigdeklarieren:
2
    typedef struct dcmotorstruct {
3
        uint8_t * GPIOA; // Beide Pins der Vollbruecke
4
        uint8_t * GPIOB; // und Enable
5
        uint8_t * GPIOEn; 
6
        uint8_t PinA;
7
        uint8_t PinB;
8
        uint8_t PinEn;
9
    } dcmotor_t;

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
    struct dcmotorstruct {
3
        uint8_t * GPIOA; // Beide Pins der Vollbruecke
4
        uint8_t * GPIOB; // und Enable
5
        uint8_t * GPIOEn; 
6
        uint8_t PinA;
7
        uint8_t PinB;
8
        uint8_t PinEn;
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.

von Karl H. (kbuchegg)


Lesenswert?

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
>
1
> struct dcmotorstruct * dcmotor_init(dcmotorinit_t * initdata);
2
>
> 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
struct dcmotorstruct;   // die gibt es wirklich, auch wenn hier nichts darueber
2
                        // ausgesagt wird, wie sie aufgebaut ist
3
4
struct dcmotorstruct * dcmotor_init(dcmotorinit_t * initdata);


Das hier
1
typedef struct dcmotorstruct dcmotor_t;
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
    typedef struct dcmotorstruct {
2
        uint8_t * GPIOA; // Beide Pins der Vollbruecke
3
        uint8_t * GPIOB; // und Enable
4
        uint8_t * GPIOEn; 
5
        uint8_t PinA;
6
        uint8_t PinB;
7
        uint8_t PinEn;
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
    struct dcmotorstruct {
2
        uint8_t * GPIOA; // Beide Pins der Vollbruecke
3
        uint8_t * GPIOB; // und Enable
4
        uint8_t * GPIOEn; 
5
        uint8_t PinA;
6
        uint8_t PinB;
7
        uint8_t PinEn;
8
    };
und für die ein neuer (kürzerer) Name gebildet werden soll
1
    struct dcmotorstruct {
2
        uint8_t * GPIOA; // Beide Pins der Vollbruecke
3
        uint8_t * GPIOB; // und Enable
4
        uint8_t * GPIOEn; 
5
        uint8_t PinA;
6
        uint8_t PinB;
7
        uint8_t PinEn;
8
    };
9
10
    typedef struct dcmotorstruct dcmotor_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
void foo( struct abc {
2
              int a;
3
              int b;
4
              int c;
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.

: Bearbeitet durch User
von Markus F. (mfro)


Lesenswert?

Vielleicht ein simples Verständnisproblem.
1
typedef
 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.

von Walter T. (nicolas)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.

: Bearbeitet durch User
von Walter T. (nicolas)


Lesenswert?

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( struct abc {
2
            int a;
3
            int b;
4
            int c;
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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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
struct abc {
2
             int a;
3
             int b;
4
             int c;
5
         } a = {.a = 1, .b= 2, .c = 3   } );

Die Punkte 2 bis 4 gehen folgendermaßen:
1
  foo((struct abc){.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.

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


Lesenswert?

Walter T. schrieb:
> Wozu wird das Tag "dcmotorstruct" benötigt?

Lass doch erstmal das typedef weg und versuche zu verstehen, wie
es ohne gehen würde:
1
struct dcmotorstruct;
2
3
struct dcmotorstruct * dcmotor_init(struct dcmotorstruct * initdata);

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.

: Bearbeitet durch Moderator
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.