Hallo, ist es in C möglich auch generische (also untypisierte) Pointer auf Funktionen zu verwenden. Ich möchte als Teil eines Structs einen Pointer auf eine Funktion verwenden. Später soll dieser Pointer dann allerdings für verschiedene Funktionstypen verwendet werden, also beispielsweise void Funktion_1(void); void Funktion_2(unsigned char x); int Funktion_3(int b); Ist sowas in C möglich? Wenn ja, wie? Danke schonmal, Thomas
Hallo Thomas, ich würde sagen: nein. Denn zur Übersetzungszeit weiß der Compiler noch nicht, auf welche Funktion der Zeiger später zur Laufzeit zeigen wird. Und damit weiß er noch nicht, ob die "gerufene" Funktion Argumente erwartet oder nicht und kann daher die Argumentübergabe nicht korrekt erzeugen. Gruß Martin
In der struct: void * ptr Beim Aufruf: casten Was meint der Compiler dazu? Bernhard
Gecastete Pointer sind die Geheimwaffe, um so ziemlich alles und jedes in C anstellen zu können. z.B.
1 | int test(int); |
2 | |
3 | int test(int a) |
4 | {
|
5 | return a; |
6 | }
|
7 | |
8 | |
9 | int main(void) |
10 | {
|
11 | void (*p)(void) // Ein Funktionspointer |
12 | int (*q)(int); // Ein anderer Funktionspointer |
13 | |
14 | p = (void (*)(void))test; // hingecastet |
15 | q = (int (*)(int))p; // und hergecastet |
16 | |
17 | int w = q(3); |
18 | |
19 | return 0; |
20 | }
|
Oliver
Es fehlt noch die Initialisierung: Es sei innerhalb der Struktur strukt: void * ptr; Im Code heißt es dann z.B.: int fkt(int a, void * b) { ... return 123; } ... strukt.ptr = fkt;
Vorsicht: Funktionszeiger und Objektzeiger sind vom Standard her grundsätzlich erstmal nicht miteinander kompatibel! Es ist also nicht OK, einen void * in einen Funktionszeiger per typecast zu wandeln, und bspw. ein IAR-Compiler lehnt das Ansinnen rundheraus ab. Der Standard deckt aber den cast eines Funktionszeigers in einen anderen, also ein void (*)(void) kann als generischer Funktionszeiger herhalten.
Mit type casts kann sich vor allem grandios den Boden unter den Füssen wegziehen.
Da beim späteren Aufruf der unterschiedlichen Funktionen sowieso unterschieden werden muss welche Variante es sein soll, könnte man z.B. alle möglichen Funktionszeiger definieren und dann in eine Union packen.
1 | typedef void (*IntArgFuncPtr)(int); |
2 | typedef void (*NoArgFuncPtr)(void); |
3 | typedef union { |
4 | IntArgFuncPtr IntArgPtr; |
5 | NoArgFuncPtr NoArgPTr; |
6 | } GenFuncPtr; |
7 | |
8 | typedef struct { |
9 | ...
|
10 | GenFuncPtr GenFunc; |
11 | } MyStruct; |
Noch allgemeiner ginge es nur mit Funktionen, die eine variable Argumentliste erwarten.
1 | typedef union { |
2 | char c; |
3 | int8_t i8; |
4 | ...
|
5 | } GenResult; |
6 | |
7 | void GenFunction(GenResult* grP, ...) { |
8 | |
9 | }
|
10 | |
11 | typedef void (*GenArgFuncPtr)(GenResult*, ...); |
>> Gecastete Pointer sind die Geheimwaffe, um so ziemlich alles und jedes >> in C anstellen zu können. Schöne Sache, kann aber zum Verhängnis werden, wenn man das Funktionen anwendet. Ich rate dir davon ab, es sei denn du bist dir 101% sicher dass dabei nichts schief läuft. Kann dir jetzt aber auch keine Alternative empfehlen :(
Ich bin jetzt zwar nicht so fit in sachen Funktionspointer, aber ich habe ähnliches gemacht bei meiner Debug-Schnittstelle. In einem Struct sind Funtkionsname+kurzbeschreibung, Pointer auf die Funktion und die Syntax abgelegt. Die Funktionen arbeiten alle wie main, also mit argc und argv. Von daher ist es auch damit möglich eine unterschiedliche Anzahl von Parametern pro funktion zu übergeben. Der Prototyp bleibt dabei also immer der selbe. Auf jeden fall ist man dann sicher, dass nichts schiefgeht. Das ganze sieht in etwa so aus: in der header-Datei
1 | /* Prototypen für die Kommandos */
|
2 | void cmd_add(char argc, char **argv); |
3 | void cmd_mul(char argc, char **argv); |
4 | void cmd_help(char argc, char **argv); |
5 | void cmd_set(char argc, char **argv); |
6 | void cmd_exit(char argc, char **argv); |
7 | |
8 | void debug(); |
9 | void parse_and_call_cmd(char *cmdptr); |
10 | char is_dbg_command(char *cmdptr); |
11 | |
12 | typedef void generic_f (char argc, char **argv); |
13 | |
14 | typedef struct{ |
15 | char *nptr; |
16 | generic_f *fptr; |
17 | char *syntax; |
18 | }debug_commands; |
Die entsprechende C-Datei sieht dann so aus:
1 | //#define DYN_ARGS
|
2 | #include <stdio.h> |
3 | #include <stdlib.h> /* Atoi */ |
4 | #include <conio.h> /* getch => kein Echo */ |
5 | #include "debug.h" |
6 | |
7 | #define MAX_ARGS 10 /* Anzahl der Argumente ohne realloc */ |
8 | |
9 | /* Kommando-Struktur, einfach nach Belieben erweitern
|
10 | *
|
11 | * {"BEFEHL ... Kurzbeschreibung\n", funktionsname, "Syntaxhilfe\n"},
|
12 | * ^Leerzeichen zwischen BEFEHL und "..." zwingend notwendig!!!
|
13 | * Prototypen der Funktionen in debug.h eintragen!
|
14 | */
|
15 | const debug_commands f[] = { |
16 | {"add ... add two numbers\n", cmd_add, "add <number> <number>\n"}, |
17 | {"mul ... multiply two numbers\n", cmd_mul, "mul <number> <number>\n"}, |
18 | {"help .. Command overview/help\n", cmd_help,"help [command]\n"}, |
19 | {"? .... Command overview/help\n", cmd_help,"? [command]\n"}, |
20 | {"exit .. exit\n", cmd_exit, "x\n"} |
21 | };
|
22 | |
23 | #define COMMANDS 5 /* Anzahl der Kommandos in debug_commands f[] */ |
24 | #define COMMAND_BUFFER 80 /* Eingabepuffer */ |
25 | |
26 | int exit_debug = 0; /* Wird durch cmd_exit auf 1 gesetzt */ |
27 | |
28 | /* Hauptfunktion, wird nicht verlassen
|
29 | *
|
30 | */
|
31 | void debug(){ |
32 | char debug[COMMAND_BUFFER]; /* Buffer für Debug-Kommando */ |
33 | unsigned char i; |
34 | |
35 | printf("\n========= Console ==========\n"); |
36 | printf("Enter '?' for command overview\n"); |
37 | while(1){ |
38 | i = 0; |
39 | |
40 | printf("\n# "); /* Weils toll aussieht */ |
41 | |
42 | do{ |
43 | debug[i] = getch(); |
44 | putchar(debug[i]); /* Echo */ |
45 | if(debug[i] == '\b' && i){ /* Backspace? */ |
46 | printf(" \b"); /* Leerzeichen ausgeben + Cursor zurück*/ |
47 | i--; /* letztes Zeichen löschen */ |
48 | }
|
49 | else{ |
50 | i++; |
51 | }
|
52 | if(i >= COMMAND_BUFFER -1){ /* Kommando zu lang */ |
53 | i = 0; |
54 | printf("\nCommand too long\n"); |
55 | continue; |
56 | }
|
57 | /* Schleife bei ENTER verlassen */
|
58 | }while(debug[i-1] != '\r' && debug[i-1] != '\n'); |
59 | |
60 | if(i == 1) /* Kein Kommando eingegeben */ |
61 | continue; |
62 | debug[i-1] = '\0'; /* Stringterminierung */ |
63 | putchar('\n'); |
64 | |
65 | if(i > 0) |
66 | parse_and_call_cmd(debug); /* Kommando suchen und ausführen */ |
67 | if(exit_debug) /* Wenn exit eingegeben */ |
68 | break; |
69 | }
|
70 | }
|
71 | |
72 | /* Addiert zwei Zahlen */
|
73 | void cmd_add(char argc, char **argv){ |
74 | int no1, no2; |
75 | if(argc == 3){ |
76 | no1 = atoi(argv[1]); |
77 | no2 = atoi(argv[2]); |
78 | printf("%d + %d = %d", no1, no2, no1 + no2); |
79 | }
|
80 | }
|
81 | |
82 | /* Multipliziert zwei Zahlen */
|
83 | void cmd_mul(char argc, char **argv){ |
84 | int no1, no2; |
85 | if(argc == 3){ |
86 | no1 = atoi(argv[1]); |
87 | no2 = atoi(argv[2]); |
88 | printf("%d * %d = %d", no1, no2, no1 * no2); |
89 | }
|
90 | else
|
91 | cmd_help(2,argv-1); /* Kleiner Trick für Syntax-Ausgabe */ |
92 | }
|
93 | |
94 | void cmd_exit(char argc, char **argv){ |
95 | exit_debug = 1; |
96 | }
|
97 | |
98 | void cmd_help(char argc, char **argv){ |
99 | unsigned char i; |
100 | unsigned char cmd_pos; |
101 | |
102 | if(argc == 2){ /* Kommandospez. Hilfe? */ |
103 | cmd_pos = is_dbg_command(argv[1]); |
104 | /* Debug-Kommando? Ja => Syntax ausgeben */
|
105 | if(cmd_pos != COMMANDS + 1 ){ |
106 | printf("\n[..] - optional argument\n<..> - required argument\n"); |
107 | printf("\n%sSyntax: %s", f[cmd_pos].nptr, f[cmd_pos].syntax); |
108 | }
|
109 | else /* Kommando nicht gefunden */ |
110 | printf("Command not found\n"); |
111 | }
|
112 | else{ /* Befehlsübersicht ausgeben */ |
113 | for(i = 0; i < COMMANDS; i++) |
114 | printf(f[i].nptr); |
115 | }
|
116 | }
|
117 | |
118 | /* Extrahiert und zählt die Argumente, sucht das Kommando über
|
119 | * is_dbg_command und führt es dann aus.
|
120 | *
|
121 | */
|
122 | void parse_and_call_cmd(char *cmdptr){ |
123 | unsigned char argc=0, cmd_pos; |
124 | |
125 | #ifndef DYN_ARGS
|
126 | unsigned char *argv[MAX_ARGS]; |
127 | #else
|
128 | unsigned char **argv = NULL; |
129 | argv = (unsigned char **)malloc(sizeof(char **)); |
130 | #endif
|
131 | |
132 | argv[argc++] = cmdptr; /* Kommando selbst in argv[0] */ |
133 | while(1){ |
134 | cmdptr++; |
135 | if(*cmdptr == ' '){ |
136 | *cmdptr = '\0'; |
137 | while(*++cmdptr == ' '); /* Leerzeichen überspringen */ |
138 | #ifdef DYN_ARGS
|
139 | argv = realloc(argv, (argc+1)*sizeof(char **)); |
140 | #else
|
141 | if(argc > 9){ |
142 | printf("\nToo many arguments!\n"); |
143 | return; |
144 | }
|
145 | #endif
|
146 | argv[argc++] = cmdptr; |
147 | }
|
148 | else if(*cmdptr == '\0'){ |
149 | break; |
150 | }
|
151 | }
|
152 | cmd_pos = is_dbg_command(argv[0]); |
153 | if(cmd_pos == COMMANDS + 1) /* Befehl nicht gefunden */ |
154 | printf("\nUnknown command\n"); |
155 | else{ |
156 | (f[cmd_pos].fptr)(argc, (char **)argv); /* Funktion aufrufen */ |
157 | }
|
158 | #ifdef DYN_ARGS
|
159 | free(argv); |
160 | #endif
|
161 | }
|
162 | |
163 | /* Sucht das Kommando in dem debug_commands-Struct und liefert
|
164 | * die Position im Struct zurück
|
165 | */
|
166 | char is_dbg_command(char *cmdptr){ |
167 | unsigned char i=0, j=0, error; |
168 | |
169 | while(i < COMMANDS ){ /* Alle Kommandos durchsuchen */ |
170 | error = 0; |
171 | while(f[i].nptr[j] != ' '){ |
172 | if(f[i].nptr[j] != cmdptr[j]){ |
173 | error = 1; |
174 | break; /* Abweichung => nächsten Befehl testen */ |
175 | }
|
176 | j++; |
177 | }
|
178 | if(!(error == 0 && (cmdptr[j] == ' ' || cmdptr[j] == '\0') )){ |
179 | error = 1; |
180 | i++; |
181 | j=0; |
182 | }
|
183 | else /* Kommando gefunden, error = 0 */ |
184 | break; |
185 | }
|
186 | if(error==0) |
187 | return i; |
188 | else
|
189 | return COMMANDS+1; |
190 | }
|
191 | |
192 | /* Main, nur zum testen */
|
193 | int main(){ |
194 | debug(); |
195 | return 0; |
196 | }
|
Ich glaube es ist relativ fehlerfrei und ist bei mir ständig im einsatz (hauptsächlich auf meinem AVR).
Wenn der Compiler es nicht macht, kann man's immernoch über ein union implementieren. Auch wenn's nicht besonders schön ist, es geht.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.