Hallo,
ich stehe gerade auf dem Schlauch, wie ich folgenden Code (auf das
Problem reduziert) kompiliert bekomme:
1
typedefvoid(*tpFnct)(tSocket*pSocket);
2
3
typedefstruct
4
{
5
tpFnctpFnct;
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
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
structtSocket;
2
3
typedefvoid(*tpFnct)(structtSocket*pSocket);
4
5
structtSocket
6
{
7
tpFnctpFnct;
8
};
Danach darf man auch noch schreiben:
1
typedefstructtSockettSocket;
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.)
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é
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
structTMenuItem
3
{structTRectR;/* die Koordinaten relativ zum Owner */
conststructTMenuItem*danach;/* Listenverkettung: Item danach */
6
conststructTMenuItem*Owner;/* Item, wo dieses enthalten ist */
7
conststructTMenuItem*Members;/* Liste der enthaltenen Items */
8
dwordFlags;/* 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.
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.
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.
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.
W.S. schrieb:> Wichtig ist an dieser Stelle die ABWESENHEIT von typedef.
Warum sollte die wichtig sein? Nur, weil sie dir wichtig ist?
1
typedefstructss;
2
3
structs{
4
s*next;
5
intdata;
6
};
7
8
ssome_s;
… ist compilierbar und ganz legaler C-Code.
Oder um auf das Beispiel des TE zurückzukommen:
1
typedefstructSockettSocket;
2
3
typedefvoid(*tpFnct)(tSocket*pSocket);
4
5
structSocket
6
{
7
tpFnctpFnct;
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(intsig,void(*func)(int)))(int);
vs.
1
typedefvoid(*sig_t)(int);
2
sig_tsignal(intsig,sig_tfunc);
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.
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.
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.
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;
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.
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.
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 …
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.
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.
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 sichselbst. Dazu braucht man keine Vorwärtsdeklaration.
Du hast also ein Problem gelöst, das der TO gar nie hatte.
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.
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
structtSocket
2
{
3
void(*pFnct)(structtSocket*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
typedefvoid(*tpFnct)(structtSocket*pSocket);
2
3
structtSocket
4
{
5
tpFnctpFnct;
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
structtSocket;
2
3
typedefvoid(*tpFnct)(structtSocket*pSocket);
4
5
structtSocket
6
{
7
tpFnctpFnct;
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
typedefstructtSockettSocket;
2
typedefvoid(*tpFnct)(tSocket*pSocket);
3
4
structtSocket
5
{
6
tpFnctpFnct;
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.
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 :)
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
statefunctionTypestateA(Eventevent)
2
{
3
if(event==EvGotoStateB)returnstateB;
4
returnstateA;
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 :-)
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.
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 ;-)
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.
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
typedefenum{EvGotoStateA,EvGotoStateB}Event;
2
typedefstructStateState;
3
4
structState{
5
State(*func)(Event);
6
};
7
8
StatestateA(Event);
9
StatestateB(Event);
10
11
StatestateA(Eventevent)
12
{
13
Stateret;
14
15
if(event==EvGotoStateB)
16
ret.func=stateB;
17
else
18
ret.func=stateA;
19
returnret;
20
}
21
22
EventgetEvent(void);
23
24
intmain(void){
25
Statestate={stateA};// initial state
26
Eventevent;
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: