Forum: Compiler & IDEs Funktionsreferenz in einem Struct


von Markus (Gast)


Lesenswert?

Hallo

Ich hab in einem Struct als Element eine Referenz auf eine Funktion:
1
typedef struct Node {  // 1
2
  struct Node *next;  // 2
3
  struct Node *prev;  // 3
4
  struct Node *parent;  // 4
5
  struct Node *child;  // 5
6
  char *text;    // 6
7
  void (*fp)(uint8_t);  // 7
8
} Node;      // 8

Zusätzlich habe die Funktion run folgendermassen deklariert
1
void run(uint8_t);

Nun möchte ich die Adresse einer Funktion an eine Instanz des Structs 
Node vergeben:
1
Node start = {&stop, &logs, NULL, NULL, menuL0, (*run)(5)}; // 9

(Die structs stop und logs habe ich bereits definiert, ebenso das 
chararray menuL0.)


Wieso erhalte ich aber nun wegen des Funktionsaufrufs einen Fehler? Wie 
muss ich die Funktion korrekt an das Node Struct start übergeben?
Ich möchte wenn möglich verhindern, dass ich einen eigenen Pointer auf 
die Funktion zuerst definiere, den ich dann an den Struct übergebe.

Vielen Dank
Markus

von Noname (Gast)


Lesenswert?

1
Node start = {&stop, &logs, NULL, NULL, menuL0, run};

von Markus (Gast)


Lesenswert?

... und wie kann ich dann das Argument (des Typs uint8_t) übergeben?

von Noname (Gast)


Lesenswert?

Garnicht.

Das ist ja kein Aufruf, der da erfolgt, sondern eine Initialisierung.

Ein Zeiger auf eine Funktion ist genau das. Ein Zeiger auf eine Funktion 
ist nicht "ein Zeiger auf eine Funktion plus Parameterliste für einen 
zukünftigen Aufruf". Dann wäre auch unklar wie sich eine Initialisierung 
von einem tatsächlichen Aufruf syntaktisch unterscheidet. Weiter wäre 
unklar, zu welchen Zeitpunkt ein Aufruf wirklich erfolgt.

Soll die Funktion tatsächlich aufgerufen werden, wenn die Initilisierung 
erfolgt oder erst später? Was ist die Fehlermeldung (Netiquette)?

von Noname (Gast)


Lesenswert?

In C99 und C++ ist es durchaus erlaubt auch nicht konstante Ausdrücke 
für die Initialisierung von auto Variablen zu verwenden. Dann aber ist 
die Deklaration von run () falsch, das ja keinen Funktionszeiger 
zurückgibt sondern void.

von Markus (Gast)


Lesenswert?

Wie müsste ich dann die Funktionsdeklaration anpassen? Grundsätzlich ist 
doch ein Aufruf einer Funktion mit Argument per Zeiger möglich wie z. B. 
unter 
http://www.c-howto.de/tutorial-funktionen-teil2-zeiger-auf-funktionen.html 
beschrieben ?!
Somit sollte doch die Technik auch anwendbar sein, wenn ich den Zeiger 
als Struct Element speichern will oder?
Die Fehlermeldung könnte ich erst abends wieder reproduzieren, ich hab 
verschiedene Varianten ausprobiert, bei einer war die Meldung effektiv 
irgend was von wegen nicht konstanter Ausdruck.

von Oliver (Gast)


Lesenswert?

Markus schrieb:
> Wie müsste ich dann die Funktionsdeklaration anpassen?

Gar nicht. fp ist ein Zeiger auf eine Funktion des Typs void 
foo(uint8_t).
run ist solch eine Funktion. Bei der Initialisierung des Structs bekommt 
fp die Adresse von run zugewiesen. Mehr nicht.

Ein Wert für das Argunemt wird erst beim Aufruf der Funktion übergeben. 
Das geht dann über run(5), oder über deinen Struct mit Node.fp(5).

Oliver

von Oliver (Gast)


Lesenswert?

Oliver schrieb:
> Node.fp(5).

muss matürlich heissen:

start.fp(5);

Oliver

von Noname (Gast)


Lesenswert?

>Grundsätzlich ist doch ein Aufruf einer Funktion mit Argument per Zeiger >möglich 
wie z. B. unter (...) beschrieben ?!

Sicher ist das möglich. Das Beispiel zeigt jedoch den Aufruf einer 
Funktion über einen Zeiger innerhalb eines Anweisungsblocks der wiederum 
Teil der Funktion main ist.
Im Gegensatz dazu versuchst Du eine Variable zu initialisieren.

>Somit sollte doch die Technik auch anwendbar sein, wenn ich den Zeiger
>als Struct Element speichern will oder?

Das geht wie gesagt durchaus: Das Problem ist, das Deine Ausdrucksweise 
einen Funktionsaufruf beschreibt und nicht einen Zeiger auf eine 
Funktion.

Schau Dir den Code mal genau an:
Zum Funktionsaufruf wird der Funktionsname "rechne" durch das Anhängen 
der Klammern.
1
printf("%d hoch 2: %d\n", zahl, rechne(zahl));

Wenn Du nur den Zeiger auf die Funktion meinst, dann nennst Du nur den 
Namen der Funktion.
1
rechne = hoch2;
Dabei wird die Funktion hoch2 nicht ausgeführt.

Nehme ich das einfachste an, so willst Du eine struct-Variable mit dem 
Zeiger auf eine Funktion initialisieren.
Das geht eben mit
1
Node start = {&stop, &logs, NULL, NULL, menuL0, run};
Durch Deine Nachfrage: "... und wie kann ich dann das Argument (des Typs 
uint8_t) übergeben?"

bekommt das Problem aber noch mindestens eine weitere Dimension.
Du unterstellst nämlich in der Frage, das schrieb ich schon, dass es 
einen Weg gibt, ein Strukturmitglied, welches ein Zeiger auf eine 
Funktion ist mit sowohl dem eigentlichen Zeiger auf die Funktion als 
auch mit der Argumentliste initialisieren kannst. Das aber geht nicht.

Es gibt nur zwei Verwendungen von Funktionszeigern.
1. Zuweisungen an Zeiger gleichen Typs. Dann ist eine Argumentliste 
nicht möglich.
2. Aufruf der Funktion auf die gezeigt wird. Dann ist eine 
(möglicherweise leere) Argumentliste notwendig.


Um aber direkt auf Deine Frage "Wie müsste ich dann die 
Funktionsdeklaration anpassen?" zu antworten will ich folgendes 
hinzufügen auch wenn Du das vermutlich garnicht so machen willst.

Du bräuchtest eine weitere Funktion GetRunFunc mit uint8_t Argument die 
einen Zeiger auf eine Funktion mit void Resultat und uint8_t Argument 
zurückliefert.
1
void (*GetRunFunc (uint8_t code))(uint8_t)
2
{
3
   switch (code)
4
   {
5
     case blabla:
6
       return xyz;
7
       break;
8
     default:
9
       return run;        // einfachstes Beispiel, liefert einfach den Zeiger auf run
10
       break;
11
   }
12
}
In meinem Beispiel liefert die Funktion einfach einen Zeiger auf run 
zurück (die Deklaration von oben angenommen).
Aber in Wirklichkeit würdest Du abhängig vom Parameter eine von mehreren 
Funktionen zurückliefern.

Diese Funktion könntest Du dann bei der Initialisierung einer 
Auto-Variablen (wohlgemerkt unter C99/C++) verwenden. Aber eben nur 
dort.
1
void func (...)
2
{
3
  Node autovar = {&stop, &logs, NULL, NULL, menuL0, GetRunFunc(7)};
4
  ...
5
}

Da letzlich nur zwischen verschiedenen schon definierten Funktionen 
gewählt werden kann (Du kannst zur Laufzeit keine neuen Funktionen 
hinzufügen, dann nimm lieber Lisp, Smalltalk oder sowas)
ist der Nutzen eher auf andere Fälle begrenzt. In Deinem Fall ist das 
vermutlich garnicht nötig.

Wenn Du also fragst: "Wie müsste ich dann die Funktionsdeklaration 
anpassen?" müsstest erstmal genau erklären was Du willst und weiter 
welchen Compiler/Sprache Du verwendest.

von Karl H. (kbuchegg)


Lesenswert?

Es könnte natürlich auch sein, dass du das hier willst:

In deiner Datenstruktur willst du sowohl einen Funktionspointer wie 
beschrieben aufrufen, als auch die Argumente, die an die Funktion zu 
übergeben sind.
In deinem Beispiel, soll run mit dem Argument 5 aufgerufen werden. In 
einem anderen Knoten könnte es sein, dass du run mit einem Argument von 
8 aufrufen willst.
Dazu musst du dir erst mal Platz in der Struktur schaffen. Du brauchst 
einen Member, der das Funktionsargument aufnehmen kann:
1
typedef struct Node {
2
  struct Node *next;
3
  struct Node *prev;
4
  struct Node *parent;
5
  struct Node *child;
6
  char *text;
7
  void (*fp)(uint8_t);
8
  uint8_t ArgToFp;
9
} Node;

Dann kannst du in deinem konkreten Fall ein Strukturobjekt 
initialisieren mittels:
1
Node start = {&stop, &logs, NULL, NULL, menuL0, run, 5 };

oder eben auch
1
Node other = {&stop, &logs, NULL, NULL, menuL0, run, 8 };

und wenn es dann zur Ausführung kommt, machst du
1
  start.fp( start.ArgToFp );

von Noname (Gast)


Lesenswert?

>In deiner Datenstruktur willst du sowohl einen Funktionspointer wie
>beschrieben aufrufen, als auch die Argumente, die an die Funktion zu
>übergeben sind.
Mönsch, darauf hätte ich ihn gern selbst kommen lassen. Wo ist Deine 
Geduld, Karl-Heinz ;-)

von Karl H. (kbuchegg)


Lesenswert?

Noname schrieb:
>>In deiner Datenstruktur willst du sowohl einen Funktionspointer wie
>>beschrieben aufrufen, als auch die Argumente, die an die Funktion zu
>>übergeben sind.
> Mönsch, darauf hätte ich ihn gern selbst kommen lassen. Wo ist Deine
> Geduld, Karl-Heinz ;-)

:-)
Das Problem war, das ich jeglichen Hinweis in diese Richtung in deiner 
Antwort vermisst habe. Vielleicht war er auch für meine Begriffe etwas 
zu subtil versteckt.
Dafür hab ich mir den 2-ten Teil der Antwort 2 mal durchlesen müssen, 
bis ich geschnallt habe, worauf du eigentlich hinaus willst und 
festgestellt habe, dass er mit dem Problem des TO höchstwahrscheinlich 
überhaupt nichts zu tun hat. Ich hab das ein wenig als Spitzfindigkeit 
empfunden, eine Initialisiersyntax, von der klar ist dass sie falsch 
ist, unter allen Umständen zu retten.
Die erste Hälfte allerdings ist dir gut gelungen.

von Noname (Gast)


Lesenswert?

>Das Problem war, das ich jeglichen Hinweis in diese Richtung in deiner
>Antwort vermisst habe. Vielleicht war er auch für meine Begriffe etwas
>zu subtil versteckt.
Nun, Du hast das ganz richtig erkannt. Es gibt keinen direkten Hinweis 
und für den für den subtilen Teil müsste man sich durch den von Dir 
inkriminierten 2. Teil der Antwort durcharbeiten, einen Schritt 
zurückgehen und das ganze Bild betrachten. Insbesondere eine Klarheit 
darüber herbeiführen was man (der TO) eigentlich will.

Ob das didaktisch so geschickt ist? Darüber kann man verschiedener 
Meinung sein. Ich schätze jedenfalls Dein Urteil.
Ich nehme bei Dir wahr (und nicht nur bei Dir) das Du meist von dem 
einfacheren Fall ausgehst. Annimmst, das der TO die simplere der 
Möglichkeiten beabsichtigt hat, die weniger Erfahrung und Kenntnisse 
voraussetzt. Das führt häufig zum Erfolg wie ich feststelle.

Ich sehe mich durch meine Denkweise veranlasst auch die Verwicklungen zu 
berücksichtigen weil und wenn ich wenig oder garkeinen Anhaltspunkt 
habe, welche Vorkenntnisse der TO hat. Auch dazu, die Alternativen zu 
benennen und sei es um den TO erkennen zu lassen, das er diese garnicht 
wollte, aber mit dem Gewinn, das er weiss das es da noch andere Aspekte 
gibt.

In diesem speziellen Fall war es zwar weniger wahrscheinlich aber 
dennoch möglich, das er wirklich einen Funktionsaufruf bei der 
Initialisierung haben wollte aber nicht wusste das das nur bei 
auto-Variablen und nur ab C99/C++ geht. Genauso wie es möglich war, das 
er den Zeiger erst durch eine Funktion erzeugen wollte (wenn das auch, 
meiner Ansicht nach, in dem Fall für die Verwendung wenig Sinn macht). 
Kontext des Codesnippets, Fehlermeldung und Compiler waren ja unbekannt. 
Ich stand vor der Wahl das zu berücksichtigen oder wegzulassen. Die 
Wahrscheinlichkeiten schienen mir nicht wirklich entscheidend 
unterschiedlich zu sein. Die Seitenwege interessant genug um darüber zu 
schreiben.

Kurz und gut: Danke für den Hinweis.

von Dosmo (Gast)


Lesenswert?

Vielleicht liegt das Verständnisproblem beim Unterschied von
1
funktion()
und
1
funktion
.
1
funktion()
 = Funktionaufruf = ausführbarer Befehl
1
funktion
   = Adresse, an der die Funktion liegt

Ein Zeiger auf eine Funktion speichert eine Adresse.
Ein Zeiger ist aber auch nur eine Speicherstelle und kein ausführbarer 
Befehl.

Oder andersrum:
Wenn Du eine Funktion aufrufen/ausführen willst, braucht es im 
Assembler-Code einen "JUMP"-Befehl (o.ä.) dazu.
In
1
funktion()
steckt dieser JUMP-Befehl implizit mit drinn.
In
1
funktion
steckt hingegen nichts mit drinn.

von Markus (Gast)


Lesenswert?

ok, ich glaub ich hab inzwischen den Unterschied eines Funktionsaufrufs 
und einem Zeiger auf eine Funktion verstanden, besten Dank.

Wenn ich nun aber meine Instanz mit
1
Node start = {&stop, &logs, NULL, NULL, menuL0[0], run};
initialisiere, erhalte ich immer noch eine Fehlermeldung:

display_test.o:(.data+0xa): undefined reference to `run'

Wie muss ich die Fehlermeldung in diesem File verstehen? Der Code an 
sich scheint ja korrekt zu sein, da keine Fehlermeldung für das 
Sourcefile aufgeführt werden. Ich bin verwirrt :S

von Klaus W. (mfgkw)


Lesenswert?

Wenn du die Funktion nur deklariert hast und nicht definiert, dann ist 
es kein Wunder, daß du verwirrt bist.
Ansonsten: mehr Info liefern, sonst kann man nur spekulieren.

von Markus (Gast)


Lesenswert?

ok, die definition löst das problem;)
vielen dank für eure hilfestellungen!!!

gruss
markus

von Dosmo (Gast)


Lesenswert?

Nochmal wegen deinem Wunsch, die Funktion bei der Initialisierung der 
Struct aufzurufen:
Mach dir bewußt, wann im Prozessor tatsächlich Code ausgeführt wird.
Eine Variable (oder Struct usw) besitzt keinerlei ausführbaren Code. Es 
ist letztendlich nur eine Speicherstelle.

Jetzt könnte man meinen, daß der C-Compiler aus
1
int a = 123;
einen Assembler Code à la
[asm]move #123,$10000[asm]
erzeugt.
Das ist aber nicht so, sondern der C-Compiler tut die Variable in einen 
Speicherbereich, der am Stück durch (so etwas wie memcpy) initialisiert 
wird.
Was ich damit sagen will: man kann ein ganze C-Datei schreiben, in der 
nur Variablen angelegt sind, und erzeugt keine Zeile ausführbaren Code 
damit.

Aus diesem Grund kannst Du auch am Initialisieren keine Funktion 
aufrufen, weil es gar keinen individuellen Initialisierungscode gibt, 
der diese Funktion aufrufen könnte.

In C++ gibt es dazu übrgens den "Constructor", der genau solche 
komplexen Initialisierungen möglich macht.

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.