Forum: Compiler & IDEs [C] Henne Ei Problem - Pointer in Struct auf sich selbst


von Fabian (Gast)


Lesenswert?

Hallo,
ich stehe gerade auf dem Schlauch, wie ich folgenden Code (auf das 
Problem reduziert) kompiliert bekomme:
1
typedef void (*tpFnct)(tSocket* pSocket);
2
3
typedef struct
4
{
5
   tpFnct pFnct;
6
} tSocket;

Bei der Typendefinition der Callback-Funktion ist die Struktur noch 
nicht bekannt. Drehe ich beides um, ist die Callback-Funktion noch nicht 
bekannt...
Pointer auf void wollte ich vermeiden.

Vielen Dank, an den, der mir das Brett vor'm Kopf entfernt.

Fabian

: Verschoben durch User
von Peter II (Gast)


Lesenswert?

Fabian schrieb:
> Vielen Dank, an den, der mir das Brett vor'm Kopf entfernt.

ein vorward Deklaration für die stuct.

von Frank (Gast)


Lesenswert?

Vorwärtsdeklaration

von Rene H. (Gast)


Lesenswert?

1
struct tSocket;
2
3
typedef void (*tpFnct)(tSocket* pSocket);
4
5
struct tSocket
6
{
7
   tpFnct pFnct;
8
};

geht aber auch so
1
struct _tSocket;
2
3
typedef struct _tSocket tSocket;
4
5
typedef void (*tpFnct)(tSocket* pSocket);
6
7
struct _tSocket {
8
   tpFnct pFnct;
9
};

Sollte so gehen, habs aber nicht kompiliert.

Grüsse,
René

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


Lesenswert?

Rene H. schrieb:
> struct tSocket;
>
> typedef void (*tpFnct)(tSocket* pSocket);

Das geht nur in C++ so, in C muss man das "struct" schon noch mit
schreiben:
1
struct tSocket;
2
3
typedef void (*tpFnct)(struct tSocket* pSocket);
4
5
struct tSocket
6
{
7
   tpFnct pFnct;
8
};

Danach darf man auch noch schreiben:
1
typedef struct tSocket tSocket;

Man kann natürlich auf den typedef-Salat verzichten, und immer
"struct tSocket" benutzen.

(Wobei ich persönlich die ungarische Notation nich sinnvoll finde
und nicht benutzen würde, aber das ist für das Beispiel erstmal
egal.)

von Fabian (Gast)


Lesenswert?

Super!
Vielen Dank!

von Rene H. (Gast)


Lesenswert?

Jörg W. schrieb:
> Das geht nur in C++ so, in C muss man das "struct" schon noch mit
> schreiben:

Ah ja, stimmt, das habe ich nicht beachtet :-).

Grüsse,
René

von W.S. (Gast)


Lesenswert?

Fabian schrieb:
> typedef struct
> {
>    tpFnct pFnct;
> } tSocket;

Brett vor dem Kopf?

Ja.

Offenbar meinen die meisten C-Programmierer, daß man zum Definieren 
eines typs typedef zu benutzen hat. Eben weil keinem klar ist, daß 
typedef nur ein Mittel zum UMBENENNEN eines vorhandenen Typs ist.

Wenn du wissen willst, wie man es richtig macht, dann schau in die 
Lernbetty hier im Forum. Hier mal ein Auszug:
1
#define RCST  const struct TMenuItem
2
struct TMenuItem
3
{ struct TRect R;                  /* die Koordinaten relativ zum Owner       */
4
  const struct TMenuItem *davor;   /* Listenverkettung: Item davor            */
5
  const struct TMenuItem *danach;  /* Listenverkettung: Item danach           */
6
  const struct TMenuItem *Owner;   /* Item, wo dieses enthalten ist           */
7
  const struct TMenuItem *Members; /* Liste der enthaltenen Items             */
8
  dword Flags;                     /* diverse Flag-Bits                       */
9
  void  *Data;                     /* Zeiger auf fallspezifische Daten        */
10
  void  (*OnKey)  (RCST* self, word *aKey);     /* wertet Tastendrücke aus    */
11
  void  (*OnEvent)(RCST* self, word *aEvent);   /* wertet Ereignisse aus      */
12
  void  (*Draw)   (RCST* self);    /* zeichnet sich und Members  */
13
};

Also begreife mal, daß du mit deinem "typedef { irgendwas } neuname;" 
präzise gesagt folgendes gemacht hast:
1. du hast einen namenlosen Typ erzeugt
2. du hast ihn anschließend umbenannt.

Auf diese Weise kannst du NIEMALS deinen Typ vor der Umbenennung 
referenzieren, denn es ist NAMENLOS. Kapito?

W.S.

von Rene H. (Gast)


Lesenswert?

W.S. schrieb:
> 1. du hast einen namenlosen Typ erzeugt
> 2. du hast ihn anschließend umbenannt

Ob die Variante mit Makro sauberer ist? Lesbarer?
Geschmacksache.
Trotzdem kein Grund den TO so anzupfeiffen

R.

von Nase (Gast)


Lesenswert?

W.S. schrieb:
> Kapito?

Das ist jetzt zwar kein wirklich schönes Beispiel, was du da ausgegraben 
hast. Dafür aber hat es aber wenigstens garnichts mit dem Problem des 
TO zu tun. Kapito?

Das Makro in dem Beispiel ist ziemlich hässlich und überflüssig.

Viel wichtiger wären solche Hinweise:

- Strukturen und andere Typen haben verschiedene Namensräume. "struct S" 
und "S" sind zwei verschiedene Typen, darum geht auch "typedef struct S 
S;", obwohl man damit scheinbar denselben Namen vergibt.

- Man kann eine Struktur vorwärtsdeklarieren, um danach Zeiger darauf zu 
vereinbaren. Das geht, weil bei der Vereinbarung bekannt ist, wie ein 
Zeiger aussieht (z.B. wie viel Speicher er braucht). Und das ist 
unabhängig davon, worauf er zeigt*. Darum reicht eine 
vorwärtsdeklarierte Struktur, obwohl ja noch garnicht feststeht, wie 
diese selbst aussieht.


[*] Naja fast. Zeiger auf Objekt und Zeiger auf Funktion können aber 
verschieden aussehen, auch wenn sie meistens gleich sind.

von W.S. (Gast)


Lesenswert?

Rene H. schrieb:
> Ob die Variante mit Makro sauberer ist? Lesbarer?
> Geschmacksache.

Nein, das ist überhaupt keine Geschmackssache.

In diesem Beispiel sieht man beides (wenn man mal bloß die Augen 
aufmacht), nämlich:
1. die ordentliche Typdeklaration mit "struct name {inhalt};"
2. ein Vorab-Makro, was die Lesbarkeit erheblich verbessert

Wichtig ist an dieser Stelle die ABWESENHEIT von typedef. Die Struktur 
wird klassisch deklariert, hat also von Anbeginn an einen Namen und 
deshalb kann man darin auch Zeiger (davor, danach, owner usw.) 
deklarieren. Das Henne-Ei-Problem gibt es nicht - und hier wurde zuvor 
siebenmal darüber geschrieben, ohne der Sache auf den eigentlichen Grund 
zu gehen. Selbst die zuletzt schreibende "Nase" hat es in keiner Weise 
gerafft.

Schande über euch! Steckt eure Nasen lieber mal wieder in ein C-Buch.

W.S.

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


Lesenswert?

W.S. schrieb:
> Wichtig ist an dieser Stelle die ABWESENHEIT von typedef.

Warum sollte die wichtig sein?  Nur, weil sie dir wichtig ist?
1
typedef struct s s;
2
3
struct s {
4
  s *next;
5
  int data;
6
};
7
8
s some_s;

… ist compilierbar und ganz legaler C-Code.

Oder um auf das Beispiel des TE zurückzukommen:
1
typedef struct Socket tSocket;
2
3
typedef void (*tpFnct)(tSocket* pSocket);
4
5
struct Socket
6
{
7
   tpFnct pFnct;
8
};

Da muss man nicht extra noch einen Makro in die Runde schmeißen
und erst recht nicht andere Leute dumm anmachen (aber so kennen
wir dich, leider).

Ich bin auch kein Freund davon, jeder struct noch einen typedef
hinterherzuwerfen, aber man muss deshalb nun nicht dem typedef die
Schuld in die Schuhe schieben.

Für Funktionszeiger sind typedefs allerdings ein Segen.
1
void (*signal(int sig, void (*func)(int)))(int);

vs.
1
typedef void (*sig_t) (int);
2
sig_t signal(int sig, sig_t func);

Nase schrieb:
> Strukturen und andere Typen haben verschiedene Namensräume.

In C++ übrigens nicht, aber das „typedef struct S S;“ ist dort
(obwohl überflüssig) als Kompatibilität zu C dennoch zulässig.

: Bearbeitet durch Moderator
von Nase (Gast)


Lesenswert?

W.S. schrieb:
> Das Henne-Ei-Problem gibt es nicht - und hier wurde zuvor
> siebenmal darüber geschrieben, ohne der Sache auf den eigentlichen Grund
> zu gehen. Selbst die zuletzt schreibende "Nase" hat es in keiner Weise
> gerafft.
Meine Fresse. DU raffst garnichts und pflaumst Leute mit deinem 
Halbwissen an. Das nervt!


Dein Makro ist extrem schlechter Stil, denn es ist /absolut 
überflüssig/, hilft bei keinem der Probleme und versaut nur den 
Namensraum.

Die Abwesenheit eines "typedef" ist völlig unerheblich, was das 
Problem des OT angeht.

Dann erklär du doch mal, wie du das Problem des TO lösen würdest.
Vermutlic hast du in deiner Betriebsblindheit nichtmal gemerkt, dass die 
beiden Deklarationen des TO sich gegenseitig referenzieren.
Der TO wird schon seinen Grund haben, warum er "tpFunc" /nicht 
innerhalb/ der Struktur deklariert. Aber auch das hast du sicher 
übersehen.

von Michael F. (mifi)


Lesenswert?

Hallo,

und wie wäre diese Lösung?

1
typedef void (*tpFnct)(struct _tSocket* pSocket);
2
3
typedef struct _tSocket
4
{
5
   tpFnct pFnct;
6
} tSocket;

Gruß,
Michael

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


Lesenswert?

Michael F. schrieb:
> und wie wäre diese Lösung?

Im Prinzip genauso wie die von zwei Beiträgen darüber.  Man kommt
halt nicht umhin, der struct selbst einen Namen zu verpassen (im
struct namespace), denn nur damit geht die Vorwärts-Deklaration.

von (prx) A. K. (prx)


Lesenswert?

Michael F. schrieb:
> und wie wäre diese Lösung?

Es ist zulässig, für die struct den gleichen Namen wie für den typedef 
zu verwenden, in C wie auch in C++. Da die Bedeutung gekoppelt ist, ist 
das hier auch nicht verwirrend. Also:

typedef void (*tpFnct)(struct tSocket* pSocket);
typedef struct tSocket { ... } tSocket;

von W.S. (Gast)


Lesenswert?

Nase schrieb:
> Dann erklär du doch mal, wie du das Problem des TO lösen würdest.
> Vermutlic hast du in deiner Betriebsblindheit nichtmal gemerkt, dass die
> beiden Deklarationen des TO sich gegenseitig referenzieren.

Ich habe es bereits vollständig gelöst, aber offensichtlich kannst 
oder willst du es nicht lesen.

Ich zitiere deshalb hier mal mich selbst:

W.S. schrieb:
> struct TMenuItem ...

Wie du in meinem hier zitierten Beitrag lesen kannst, wird inmitten der 
Deklaration des Struct's selbiger als Typ bereits gebraucht und das geht 
auch problemlos. Das

#define RCST  const struct TMenuItem

könnte man an dieser Stella auch weglassen, dann müßte man bei den 
Argumenten der drei Funktionen eben immer "const struct TMenuItem" 
schreiben, was mir zu unleserlich ist.

Also lerne daraus, daß man unter Weglassung von typedef das Problem des 
TO bereits vollständig gelöst hat. Den Grund (namenloser struct) hatte 
ich auch schon ausführlich dargelegt.

Vor dem nächsten Flamen solltest du wirklich mal ein gutes C-Buch lesen 
und auch meine Postings gründlich lesen und verstehen, das meine ich 
ernst.


Fabian schrieb:
> Vielen Dank, an den, der mir das Brett vor'm Kopf entfernt.

Ja, gern geschehen.

W.S.

von W.S. (Gast)


Lesenswert?

Jörg W. schrieb:
> Warum sollte die wichtig sein?  Nur, weil sie dir wichtig ist?

Du liest auch nicht, bevor du in die Tasten haust.

Erstens solltest du dein "und erst recht nicht andere Leute dumm 
anmachen" mal besser wieder runterschlucken. Guch in den Eröffnungspost, 
dann merkst du, wie sehr du daneben liegst.

Zweitens ist genau das Verwenden von typedef der Knackpunkt gewesen 
weswegen der TO sein Henne-Ei-Problem hatte. Von mir aus kann ein jeder 
soviel typedefs benutzen wie er mag, mir ist das völlig unwichtig - aber 
hier war die Verwendung der Kern des Fehlers, da der TO die 
Namenlosigkeit während der Deklaration nicht erkannt hatte. Ohne typedef 
hätte er klassisch "struct name {inhalt};" formulieren müssen und a.u.c 
kein Problem gehabt.

Und warum passiert sowas?

Weil es ganz offensichtlich die Leute genau so beigebracht kriegen. Und 
das muß raus aus den Köpfen, damit das besagte Brett vor dem Kopf 
verschwindet. Die Leute müssen lernen, Sprachelemente bewußt einzusetzen 
und nicht, weil "man das eben so macht".

Man kann das nicht deutlich genug sagen!
Sieh dir die anderen Posts hier zum Thema an, massives Geflame ohne auch 
nur den eigentlichen Kern verstanden zu haben. Ich steh da drüber, aber 
ich behalte mir vor, jemandem, der es verdient, die Ohren lang zu ziehen 
- sofern ich das als erforderlich erachte.

W.S.

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


Lesenswert?

W.S. schrieb:
> Zweitens ist genau das Verwenden von typedef der Knackpunkt gewesen
> weswegen der TO sein Henne-Ei-Problem hatte.

Nein, ist es nicht, wie oben gezeigt.

Ich habe geschrieben, dass ich auch kein großer Fan allzu vieler
typedefs bin, aber es geht mit oder ohne typedefs problemlos zu
lösen.  Das haben inzwischen mehrere Leute detailliert ausgeführt,
dich natürlich ausgenommen.  Dafür glaubst du dann auch noch, dass
dir der Dank des TEs gebühre …

von Michael (Gast)


Lesenswert?

Mal in die Runde gefragt:

Wieso nutzt ihr überhaupt so gerne typedefs? (Für Functionpointer lasse 
ich es mir ja gefallen.)

Vor den Typ "struct" zu schreiben empfinde ich beispielsweise eleganter 
als immer ein "_t" anzuhängen.

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


Lesenswert?

Michael schrieb:
> Wieso nutzt ihr überhaupt so gerne typedefs?

Das müsstest du den TE fragen. ;-)

Für structs oder enums nehme ich sie auch eher nicht.  Aber es gibt
trotzdem keinen Grund, sie zu verteufeln.

von Yalu X. (yalu) (Moderator)


Lesenswert?

W.S. schrieb:
> Nase schrieb:
>> Dann erklär du doch mal, wie du das Problem des TO lösen würdest.
>> Vermutlic hast du in deiner Betriebsblindheit nichtmal gemerkt, dass die
>> beiden Deklarationen des TO sich gegenseitig referenzieren.
>
> Ich habe es bereits vollständig gelöst, aber offensichtlich kannst
> oder willst du es nicht lesen.
>
> Ich zitiere deshalb hier mal mich selbst:
>
> W.S. schrieb:
>> struct TMenuItem ...

Trifft das Nicht-Lesen-Können-oder-Wollen nicht eher auf dich selber zu?

Die Nase schrieb richtigerweise, dass sich die beiden Deklarationen
des TO gegenseitig referenzieren. Um diesen Konflikt aufzulösen,
braucht man eine Vorwärtsdeklaration, was der TO wohl nicht gewusst hat,
jetzt aber vermutlich weiß.

In deinem Beispiel hingegen referenziert eine Deklaration sich
selbst. Dazu braucht man keine Vorwärtsdeklaration.

Du hast also ein Problem gelöst, das der TO gar nie hatte.

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


Lesenswert?

Yalu X. schrieb:
> Du hast also ein Problem gelöst, das der TO gar nie hatte.

Das lasse ich nicht gelten. Der TO wollte sich einen struct deklarieren 
und er hat das auf eine Weise getan, die ihm genau dieses Problem 
gebracht hat. Also ist eine andere Weise, die zum Ergebnis führt OHNE 
sein problem aufzuwerfen, eben genau die Lösung seines Problems.

Ich geb dir mal ein Beispiel:

Jemand klagt folgendes: "Ich will mit dem Kopf durch die Wand, aber es 
klappt nicht und ich krieg bloß ne Beule am Kopf" Und jemand wie ich 
sagt ihm: "Laß das bleiben, es ist Murks, nimm lieber die Tür, die ist 
offen".

Und du sagst nun, daß ich ein Problem gelöst habe, das der TO garnicht 
hatte? Deine Art zu argumentieren ist mir unverständlich.

W.S.

von Yalu X. (yalu) (Moderator)


Lesenswert?

W.S. schrieb:
> Yalu X. schrieb:
>> Du hast also ein Problem gelöst, das der TO gar nie hatte.
>
> Das lasse ich nicht gelten. Der TO wollte sich einen struct deklarieren
> und er hat das auf eine Weise getan, die ihm genau dieses Problem
> gebracht hat.

Ich habe mir den Thread und insbesondere deine Aussagen noch einmal
genau durchgelesen:

Du hast tatsächlich recht mit deiner Aussage, dass ohne die Verwendung
von Typedefs das Problem des TE nicht entstanden wäre. Hätte er sowohl
die Struktur als auch den Funktionszeigertyp jeweils ausgeschrieben,
also so

1
struct tSocket
2
{
3
  void (*pFnct)(struct tSocket *pSocket);
4
};

hätte er auch keine Vorwärtsdeklaration gebraucht.

Allerdings – und deswegen war auch mir zunächst nicht klar, was du mit
deinen Beiträgen bezwecken wolltest – beziehen sich deine Beiträge
ausschließlich auf den Typedef der Struktur und nicht auf den Typedef
des Funktionszeigertyps. Wenn man aber nur den Typedef auf die Struktur
weglässt, kommt folgendes heraus:

1
typedef void (*tpFnct)(struct tSocket *pSocket);
2
3
struct tSocket
4
{
5
   tpFnct pFnct;
6
};

Das zwar legaler Coder, aber nicht das, was der TE damit bezwecken
möchte. Der GCC liefert deswegen auch eine entsprechende Warnung.

Richtig wird dieser Code erst durch das Voranstellen einer
Vorwärtsdeklaration:

1
struct tSocket;
2
3
typedef void (*tpFnct)(struct tSocket *pSocket);
4
5
struct tSocket
6
{
7
   tpFnct pFnct;
8
};

was übrigens genau zu dem Code führt, den Jörg schon mehrere Stunden vor
dir gepostet hat.

Von dieser erforderlichen Vorwärtsdeklaration schreibst du aber in
deinen Beiträgen überhaupt nichts. Deine Beiträge lassen deswegen der
Eindruck entstehen, das Problem sei gelöst, wenn man lediglich den
Typedef der Struktur weglässt. Aber das ist eben nur die halbe Miete.
Und da die "volle Miete" schon deine Vorpostern geliefert haben, waren
deine Beiträge eher verwirrend als hilfreich.


Ich gehöre übrigens auch zu der Sorte von Leuten, die in C auf die
Typedefs von Strukturen meist verzichten, Typedefs aber gerne für sehr
schreibintensive Typen nutzen wie bspw. diejenigen von Zeigern auf
Funktionen mit mehreren Argumenten.

Für die Feunde der konsequenten Nutzung Typedefs für alles und jedes ist
hier noch eine Lösung, die beide Typedefs an den Anfang stellt und erst
danach die zunächst unvollständige Struktur mit Inhalt füllt:

1
typedef struct tSocket tSocket;
2
typedef void (*tpFnct)(tSocket *pSocket);
3
4
struct tSocket
5
{
6
   tpFnct pFnct;
7
};

In diesem Fall fällt die explizite Vorwärtsdeklaration weg, da sie schon
implizit im ersten Typedef enthalten ist. Letztendlich kommt man aber
auch hier um eine benannte Struktur nicht herum.

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


Lesenswert?

Diese Variante hatte ich aber auch schon vor paar Tagen nachgereicht. 
;-)

Beitrag "Re: [C] Henne Ei Problem - Pointer in Struct auf sich selbst"

von Yalu X. (yalu) (Moderator)


Lesenswert?

Jörg W. schrieb:
> Diese Variante hatte ich aber auch schon vor paar Tagen nachgereicht.
> ;-)

Hmm, offensichtlich konnte oder wollte ich das nicht lesen (um es in den
Worten von W.S. auszudrücken).

Dann würde ich sagen, ist das Thema nicht nur durchgekaut, sondern
inzwischen sogar größtenteils wiedergekäut und sollte damit für jeden
Leser dieses Threads ganz besonders leicht verdaulich sein :)

von beric (Gast)


Lesenswert?

Als Nachspeise zum noch besseren Verdauen habe ich dann folgende 
Variante des Henne-Ei-Problems:

Ich habe mich mal versucht an der Implementierung einer State-Machine, 
wobei jeder State von einer Funktion repräsentiert wurde. Diese 
State-Funktionen sollten als Rückgabewert ein Pointer auf der neue 
State, also ein Funktionspointer, haben.
(Und ein Parameter für das zu verarbeitende Event, aber das ist hier 
unwichtig).

Also etwa so:
1
statefunctionType stateA (Event event)
2
{
3
  if(event == EvGotoStateB) return stateB;
4
  return stateA;
5
}
Wie soll jetzt die Definition von statefunctionType aussehen?
Und bitte nicht mogeln mit void Pointer.
Ich glaube in dieser Form geht das einfach nicht, lasse mich aber gerne 
vom Gegenteil überzeugen :-)

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

beric schrieb:
> Wie soll jetzt die Definition von statefunctionType aussehen?

So gehts ohne Fehlermeldung durch den Compiler:
1
typedef enum ev { EvGotoStateA, EvGotoStateB } Event;
2
3
typedef int (*statefunctionType)(Event a);
4
5
statefunctionType stateA (Event);
6
statefunctionType stateB (Event);
7
8
statefunctionType stateA (Event event)
9
{
10
    if (event == EvGotoStateB) return (statefunctionType) stateB;
11
    return (statefunctionType) stateA;
12
}

> Und bitte nicht mogeln mit void Pointer.

Nö, kein void-Pointer. Ich frag mich nur, warum ich den return-Wert 
casten muss...

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


Lesenswert?

Frank M. schrieb:
> Nö, kein void-Pointer. Ich frag mich nur, warum ich den return-Wert
> casten muss...

Weil der statefunctionType eben einen "int" zurückgeben mag. :-/

Ich habe auch das Gefühl, dass das ein unlösbares Problem ist, da
es keine Möglichkeit gibt, eine Vorwärtsdeklaration zu machen, in
der man dem Compiler erstmal sagen kann: „Das da wird ein Zeiger auf
eine Funktion, Details folgen später.“  Technisch machbar wäre das,
weil Funktionszeiger unteinander kompatibel sind (allerdings nicht
zu Objektzeigern, also auch nicht zu "void *"), sodass diese Tatsache
für die Vorwärtsdeklaration eigentlich erstmal genügen würde.  Es
gibt halt nur keinen syntaktischen Konstrukt, der so einem simplen
"struct foo;" entsprechen würde.

von Eric B. (beric)


Lesenswert?

Jörg W. schrieb:
> Ich habe auch das Gefühl, dass das ein unlösbares Problem ist, da
> es keine Möglichkeit gibt, eine Vorwärtsdeklaration zu machen, in
> der man dem Compiler erstmal sagen kann: „Das da wird ein Zeiger auf
> eine Funktion, Details folgen später.“

Genau so habe ich mir das dann damals auch überlegt und dann die Idee 
aufgegeben. Mit void Pointern und dann casten geht natürlich auch, ist 
aber ähm... sub-optimal.

> Technisch machbar wäre das,
> weil Funktionszeiger unteinander kompatibel sind

Nur solange man kein Paged Memory Model hast, wobei der uC zwischen 
"near" und "far" Calls unterscheidet ;-)

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Eric B. schrieb:
> Nur solange man kein Paged Memory Model hast, wobei der uC zwischen
> "near" und "far" Calls unterscheidet ;-)

Ist egal.  In C sind per definitionem diese Zeiger zuweisungskompatibel:
1
  void (*funcptr1)(void);
2
  int (*funcptr2)(int, double, int *);

Wie der Compiler das dann bei sowas wie near und far umsetzt, ist dann
sein Problem.

NB: Objektzeiger jedoch sind mit Funktionszeigern nicht kompatibel.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Jörg W. schrieb:
> Ich habe auch das Gefühl, dass das ein unlösbares Problem ist

Dieses Gefühl teile ich, auch wenn ich mich gerne davon überzeugen
lasse, dass es trügt :)

Rekursive Datentypen scheinen in C nur mittels struct oder union möglich
zu sein (Bsp. verkettete Liste). Ein Zeigertyp, der dereferenziert
wieder denselben Typ ergibt, ist IMHO unmöglich. Ebenso unmöglich ist
ein Funktionszeigertyp, dessen Rückgabewert wieder ein Funktionszeiger
vom selbe Typ ist.

So etwas geht nicht einmal in Haskell, obwohl man dort mit Funktionen
die verrücktesten Dinge tun kann und unendliche Listen, Bäume u.ä. keine
Probleme darstellen. Auch syntaktisch wäre es überhaupt kein Problem, so
einen Funktionstyp zu definieren:

1
data Event = EvGotoStateA | EvGotoStateB  -- entspricht einem Enum in C
2
3
type State = Event -> State  -- entspricht einem Typedef in C, zu lesen als:
4
                             -- "Der Typ State wird definiert als eine
5
                             --  Funktion, die ein Event als Argument hat
6
                             --  und als Rückgabewert einen State liefert."

Wenn man aber versucht, diese Code zu kompilieren, meint der Compiler:

1
    Cycle in type synonym declarations:
2
      test.hs:3:1-27: type State = Event -> State

Schade eigentlich :-(

Die Lösung des Problems besteht darin, diesen seltsamen Typ in eine
Kiste zu packen:

1
newtype State = State (Event -> State)

Das "State" nach dem Gleichheitszeichen ist dabei der Name des
Konstruktors, der eine Funktion des Types Event->State in eine Kiste
namens State packt.

Da ein Newtype in Haskell im Prinzip nichts anderes ist als ein Struct
mit genau einem Element in C, kann man so etwas natürlich auch in C
machen, indem man den Funktionszeiger in ein Struct verpackt:

1
typedef enum { EvGotoStateA, EvGotoStateB } Event;
2
typedef struct State State;
3
4
struct State {
5
  State (*func)(Event);
6
};
7
      
8
State stateA(Event);
9
State stateB(Event);
10
11
State stateA (Event event)
12
{
13
  State ret;
14
15
  if(event == EvGotoStateB)
16
    ret.func = stateB;
17
  else
18
    ret.func = stateA;
19
  return ret;
20
}
21
22
Event getEvent(void);
23
24
int main(void) {
25
  State state = { stateA };    // initial state
26
  Event event;
27
28
  for(;;) {
29
    event = getEvent();        // next event
30
    state = state.func(event); // next state
31
  }
32
}

Damit kann man auf sämtliche Pointercasts verzichten, und wenn der
Compiler schlau ist, wird der Code dadurch nicht weniger effizient.
Zumindest in Haskell legt schon die Sprachdefinition fest, dass die
Newtype-Kiste, in die die Funktion verpackt wird, nur in Gedanken, nicht
aber im generierten Binärcode exisitiert.

Edit:

In C++ könnte man den ()-Operator von State mit der Funktion überladen,
so dass man das gesamte Objekt vom Typ State wie eine Funktion verwenden
kann. Dann würde die Zustandstransition ium Hauptprogramm so aussehen,
wie es von beric beabsichtigt war:

1
    state = state(event);

: 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.