Forum: Compiler & IDEs Anfängerproblem: Funktionszeiger (void, typedef struct)


von Matthias H. (matthias1309)


Lesenswert?

Hallo liebe Forum-User!

Ich bin gerade dabei mir selber C beizubringen und experimentiere mit 
Funktionszeigern. Nun stoße ich auf folgendes Problem:

Im nachfolgenden Code habe ich einen "typedef struct" mit dem Namen 
"device_function". Dieser soll eine Liste mit Funktionen enthalten. Als 
Parameter wird ein int-Wert übergeben
1
#include <stdio.h>
2
#include <stdlib.h>
3
4
typedef struct {
5
    const char  *text;
6
    int         zahl;
7
} param;
8
9
typedef struct {
10
    const char  *description;
11
    void        (*funct)(int);
12
    int         parameter;
13
} device_function;
14
15
void mach_was(void* p) {
16
    int a = *(int*)p;
17
    printf("Erledige eine Aufgabe: %i\n", a);
18
}
19
20
void mach_noch_was(void* p) {
21
    int a = *(int*)p;
22
    printf("Erledige eine zweite Aufgabe: %i\n", a);
23
}
24
25
device_function programm_functions[] = {
26
    {"Funktion 1", mach_was, 123 },
27
    {"Funktion 2", mach_noch_was, 123 },
28
};
29
30
static int functions_count = sizeof(programm_functions)/sizeof(programm_functions[0]);  // Anzahl der Einträge
31
32
int main()
33
{
34
    int i;
35
36
    for (i=0;i<functions_count;i++) {
37
        printf("%s\n",programm_functions[i].description);
38
        programm_functions[i].funct(&programm_functions[i].parameter);
39
        printf("\n~~~~~~~~~~~~~~~~~~~~\n");
40
    }
41
    return 0;
42
}

Das Programm funktioniert soweit wunderbar, es kommt die Ausgabe:
1
Funktion 1
2
Erledige eine Aufgabe: 123
3
4
~~~~~~~~~~~~~~~~~~~~
5
Funktion 2
6
Erledige eine zweite Aufgabe: 123
7
8
~~~~~~~~~~~~~~~~~~~~
9
10
Process returned 0 (0x0)   execution time : 0.027 s
11
Press any key to continue.

Wenn ich aber jetzt aber anstelle des Integer-Wertes den struct "param" 
verwende, und diesen von void nach param caste, erhalte ich nachfolgende 
Fehlermeldung vom Compiler:
1
#include <stdio.h>
2
#include <stdlib.h>
3
4
typedef struct {
5
    const char  *text;
6
    int         zahl;
7
} param;
8
9
typedef struct {
10
    const char  *description;
11
    void        (*funct)(int);
12
    param       parameter;
13
} device_function;
14
15
void mach_was(void* p) {
16
    param a = *(param*)p;
17
    printf("Erledige eine Aufgabe: %i\n", a->zahl);
18
}
19
20
void mach_noch_was(void* p) {
21
    param a = *(param*)p;
22
    printf("Erledige eine zweite Aufgabe: %i\n", a->zahl);
23
}
24
25
device_function programm_functions[] = {
26
    {"Funktion 1", mach_was, {"Test", 123} },
27
    {"Funktion 2", mach_noch_was, {"Test", 1234} },
28
};
29
30
static int functions_count = sizeof(programm_functions)/sizeof(programm_functions[0]);  // Anzahl der Einträge
31
32
int main()
33
{
34
    int i;
35
36
    for (i=0;i<functions_count;i++) {
37
        printf("%s\n",programm_functions[i].description);
38
        programm_functions[i].funct(&programm_functions[i].parameter);
39
        printf("\n~~~~~~~~~~~~~~~~~~~~\n");
40
    }
41
    return 0;
42
}
1
||=== Build: Debug in funktionszeiger (compiler: GNU GCC Compiler) ===|
2
C:\gcc\funktionszeiger\main.c||In function 'mach_was':|
3
C:\gcc\funktionszeiger\main.c|17|error: invalid type argument of '->' (have 'param')|
4
C:\gcc\funktionszeiger\main.c||In function 'mach_noch_was':|
5
C:\gcc\funktionszeiger\main.c|22|error: invalid type argument of '->' (have 'param')|
6
C:\gcc\funktionszeiger\main.c|26|warning: initialization from incompatible pointer type [enabled by default]|
7
C:\gcc\funktionszeiger\main.c|26|warning: (near initialization for 'programm_functions[0].funct') [enabled by default]|
8
C:\gcc\funktionszeiger\main.c|27|warning: initialization from incompatible pointer type [enabled by default]|
9
C:\gcc\funktionszeiger\main.c|27|warning: (near initialization for 'programm_functions[1].funct') [enabled by default]|
10
C:\gcc\funktionszeiger\main.c||In function 'main':|
11
C:\gcc\funktionszeiger\main.c|40|warning: passing argument 1 of 'programm_functions[i].funct' makes integer from pointer without a cast [enabled by default]|
12
C:\gcc\funktionszeiger\main.c|40|note: expected 'int' but argument is of type 'struct param *'|
13
||=== Build failed: 2 error(s), 5 warning(s) (0 minute(s), 0 second(s)) ===|

Wo liegt mein Denkfehler im obrigen Programm?

von Peter II (Gast)


Lesenswert?

Matthias Haas schrieb:
> Wo liegt mein Denkfehler im obrigen Programm?

void        (*funct)(int);
void mach_noch_was(void* p) {

warum nicht hier auch den richtigen Parameter verwenden? Dann könntest 
du die ganze Casterei sparen.

von Matthias H. (matthias1309)


Lesenswert?

Ich möchte eine flexible Struktur schaffen, und der einen Funktion z.B. 
einen Struct übergeben können, der anderen einen String, einer dritten 
Funktion einen Integer-Wert. (union)

von Daniel A. (daniel-a)


Lesenswert?

Matthias Haas schrieb:
> void        (*funct)(int);
> void mach_was(void* p)
> device_function programm_functions[] = {
>    {"Funktion 1", mach_was,
Das der implizite cast von void(*)(void*) nach void(*)(int); 
funktioniert ist reines glück!
> param a = *(param*)p;
>  a->zahl
a ist kein pointer! Versuche
param* a = (param*)p;
oder
a.zahl

von Peter II (Gast)


Lesenswert?

und warum schreibst du dann hier int?

void        (*funct)(int);

und nicht void*

von Peter II (Gast)


Lesenswert?

Matthias Haas schrieb:
> Ich möchte eine flexible Struktur schaffen, und der einen Funktion z.B.
> einen Struct übergeben können, der anderen einen String, einer dritten
> Funktion einen Integer-Wert.

und wie willst du diese werte in deiner Struktur ablegen?

von Karl H. (kbuchegg)


Lesenswert?

Matthias Haas schrieb:
> Ich möchte eine flexible Struktur schaffen, und der einen Funktion z.B.
> einen Struct übergeben können, der anderen einen String, einer dritten
> Funktion einen Integer-Wert. (union)

Wenn du sowieso schon so etwas planst
1
typedef union {
2
    const char  *text;
3
    int         zahl;
4
} funcArg;

dann solltest du das auch durchziehen. Deine Funktionen bekommen dann 
einen Pointer auf eine derartige Union, müssen also der Aufrufkonvention
1
void func( int argType, funcArg *arg )
2
{
3
  ...
4
}
genügen. Denn sonst schaffst du es ja nicht "beliebige" Argumente (im 
Rahmen dessen, was durch die union möglich ist) in die Funktionen zu 
bringen.

Damit ist dein Funktionspointer aber anders anzulegen und auch die 
Funktionen sind anders auszuführen

(Ich fasse die union und die Typkennzeichnung, welcher Member der union 
gültig ist noch in einer struct zusammen. Damit kann die Funktion 
erkennen, was sie tytsächlich übergeben bekommen hat und entsprechend 
reagieren. Entweder indem sie sich nach dem Datentyp richtet oder indem 
sie mitteilt 'Damit kann ich nichts anfangen')
1
typedef union {
2
    const char  *text;
3
    int         zahl;
4
} funcArg;
5
6
#define TEXT_ARG   0
7
#define INT_ARG    1
8
9
typedef struct
10
{
11
  int     argType;
12
  funcArg arg;
13
} funcParam;
14
15
typedef void (*funcPtr)( funcParam *arg );
16
17
typedef struct {
18
    const char  *description;
19
    funcPtr     funct;
20
    param       parameter;
21
} device_function;
22
23
void mach_was(funcParam *p) {
24
    if( p->argType == INT_ARG )
25
      printf( "mach_was: Mit Int Arg aufgerufen - %d\n", p->arg.zahl );
26
27
    else if( p->argType == TEXT_ARG )
28
      printf( "mach_was: mit Text Arg aufgerufen - %s\n", p.arg.text );
29
}
30
31
void mach_noch_was(funcParam *p) {
32
    if( p->argType != INT_ARG )
33
      printf( "mach_noch_was: Fehler - Funktion mit ungültigem Argumenttyp aufgerufen\n";
34
35
    else
36
      printf( mach_noch_was: %d\n", p->arg.zahl );
37
}
38
39
device_function programm_functions[] = {
40
    {"Funktion 1", mach_was, { INT_ARG, .zahl = 123 },
41
    {"Funktion 2", mach_noch_was, { TEXT_ARG, .text = "Test" } },
42
};

mach um void Zeiger einen großen Bogen wenn du kannst. Mit void Zeigern 
legst du die Typüberwachung des Compilers in letzter Konsequenz einfach 
lahm. Und das ist selten eine gute Idee, wenn es auch anders geht. Dinge 
einfach auf Biegen und Brechen zurecht zu casten, ist meistens eine 
schlechte Lösung - weil DU als Programmierer für die Richtigkeit ganz 
alleine verantwortlich bist. Und das geht öfter in die Hose als einem 
lieb ist. Da ist es schon besser, wenn man auf die Casts verzichten kann 
und der Compiler wenigsten ein bischen über die Schulter schaut und 
kontrolliert.

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


Lesenswert?

Matthias Haas schrieb:
> typedef struct {
>     const char  *text;
>     int         zahl;
> } param;

Bringe nach Möglichkeit die Dinge nicht mutwillig durcheinander. Ja, ich 
weiß, daß die C-Freaks am liebsten alles zusammenziehen wollen, aber ein 
sauberer Stil ist das nicht. Mit typedef kannst du einem bestehenden Typ 
einen neuen Namen geben, dazu dient dieses Statement. Zum eigentlichen 
Definieren eines Typs ist das nicht gedacht.

struct TParam
{ const char* Text;
  int Zahl;
}

So sähe das im Grunde aus. Nun kannst du dir aussuchen, ob du es 
anschließend so schreiben willst:

#define param struct TParam

oder so

typedef struct TParam param;

was im Grunde auf dasselbe hinausläuft. Du kannst aber auch schlicht und 
einfach mit TParam weiterarbeiten, ohne das #define und ohne das 
typedef. Es ist nur, daß du dann das struct beim Deklarieren von 
Variablen mit dazuschreiben mußt.

Viele Leute machen den Denkfehler, daß sie glauben, mit typedef 
tatsächlich einen Typ definieren zu können, aber das ist eben falsch und 
führt langfristig nur zu Verständnisproblemen das typedef betreffend.

W.S.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

W.S. schrieb:
> Matthias Haas schrieb:
>> typedef struct {
>>     const char  *text;
>>     int         zahl;
>> } param;
>
> Bringe nach Möglichkeit die Dinge nicht mutwillig durcheinander. Ja, ich
> weiß, daß die C-Freaks am liebsten alles zusammenziehen wollen, aber ein
> sauberer Stil ist das nicht. Mit typedef kannst du einem bestehenden Typ
> einen neuen Namen geben, dazu dient dieses Statement. Zum eigentlichen
> Definieren eines Typs ist das nicht gedacht.

Genau dafür ist es gedacht.

Der Typ "struct { const char *text, int zahl }" wird als Typ "param" 
definiert.

> #define param struct TParam

Unnötig und mieser Stil.

> typedef struct TParam param;

struct TParam ist unnötig.

von Karl H. (kbuchegg)


Lesenswert?

Johann L. schrieb:

>> typedef struct TParam param;
>
> struct TParam ist unnötig.


... und ich möchte hinzufügen:
und belastet nur den Namensraum mit dem Namen der struct, den (zumindest 
in diesem Fall) keiner braucht.

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