Forum: Compiler & IDEs cast funktionszeiger in C


von Sina A. (sinapse)


Lesenswert?

bei der deklaration eines funktionszeigers

int (*tuWas)(char *, char *);

ist die logik ja, bei tuWas anzufangen und sich dann von innen nach 
aussen zu hangeln. Also tuWas ist ein zeiger auf eine funktion die zwei 
zeiger auf char erwartet und int ausgibt.

möchte man diesen funktionspointer casten wuerde das beispielsweise so

(int (*)(void*,void*))tuWas

aussehen. hier ist mir nicht wirklich klar, wo man im cast anzufangen 
hat zu lesen... irgendwie muesste das ja beim ersten stern anfangen. 
Also caste auf einen zeiger auf eine funktion die zwei void zeiger 
erwartet und int ausgibt. ich kann mir das zwar denken, aber woher weiss 
das der compiler?? mich interessiert also die logik, die dahinterliegt. 
weiss einer von euch mehr? (oder kann mich auf eine gut erklärende 
quelle verweisen?)

und warum muss der erste stern im cast geklammert werden?


dankö

von Karl H. (kbuchegg)


Lesenswert?

sina anargo schrieb:
> bei der deklaration eines funktionszeigers
>

> aussehen. hier ist mir nicht wirklich klar, wo man im cast anzufangen
> hat zu lesen...

genau gleich.

Nimm deine erste Erklärung her, lass alle Variablen-Namen weg, und du 
landest beim gesuchten Cast.

> Also caste auf einen zeiger auf eine funktion die zwei void zeiger
> erwartet und int ausgibt. ich kann mir das zwar denken, aber woher weiss
> das der compiler?? mich interessiert also die logik, die dahinterliegt.
> weiss einer von euch mehr? (oder kann mich auf eine gut erklärende
> quelle verweisen?)

Allerdings:

* gerade bei Funktionszeigerm kann man sich mit ein paar typedef das 
Leben als Programmierer signifikant einfacher machen.

* Funktionszeiger zu casten ist eine problematische Sache. Wenn man 
weiß, was man tut, und zb abhängig von einem Typ-Flag den cast 
durchführt, ist es in Ordnung. Aber abgesehen von diesem Fall, muss man 
sich allerdings der Frage stellen: Warum muss man da überhaupt casten 
und wäre eine union nicht die bessere Lösung gewesen?

> und warum muss der erste stern im cast geklammert werden?

Musste er ja hier auch
1
int (*tuWas)(char *, char *);

der * beim 'tuWas' ist ja auch in der Klammer. Und der muss dort auch 
sein, dann sonst würde es sich ja um einen Funktionsprototypen handeln, 
für eine Funktion tuWas, die zwei char-Pointer übernimmt und einen 
int-Pointer liefert.

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


Lesenswert?

Karl Heinz schrieb:
> Warum muss man da überhaupt casten und wäre eine union nicht die bessere
> Lösung gewesen?

Ansonsten immer im Hinterkopf behalten: ein Cast zwischen Objekt- und
Funktionszeigern ist durch den C-Standard nicht gedeckt.  Es dürfen
nur Objektzeiger beliebig von einem konkreten (typisierten) Zeiger auf
void * (und aus historischen Gründen auch auf char *) sowie wieder
zurück auf den konkreten Zeiger gewandelt werden sowie Funktionszeiger
in einen anderen Funktionszeiger.

Wenn man also einen generischen Funktionszeiger braucht, dann kann man
sich mit sowas behelfen (wenn die Union aus irgendeinem Grunde nicht
sinnvoll passt):
1
typedef void (*generic_func_ptr)(void);

von Dr. Sommer (Gast)


Lesenswert?

Jörg Wunsch schrieb:
> Ansonsten immer im Hinterkopf behalten: ein Cast zwischen Objekt- und
> Funktionszeigern ist durch den C-Standard nicht gedeckt.
Unter POSIX-Systemen allerdings schon, denn dort braucht man zB zur 
Verwendung von dlsym() den Cast zwischen Objekt&Funktions-Zeiger...

von huhuuu (Gast)


Lesenswert?

ich hab mal sowas gemacht ...
1
typedef uint8_t (*Argn)  ();

dazu merke ich mir noch wieviele argn dabei sind
also in die Queue kommen der FP , anzahl argn , und die argn

der aufruf erfolgt in abhängigkeit von der anzahl argn
1
switch(argn){
2
case 0:  rVal  =((Argn)*M[i]->FP) ();
3
break;
4
case 1:  rVal =((Argn)*M[i]->FP) (M[i]->Arg[0]);
5
break;
6
case 2:  rVal =((Argn)*M[i]->FP) (M[i]->Arg[0], M[i]->Arg[1]);
7
break;
8
.
9
.
10
.
11
}

das funzt erschtauntlich gut ...
der returnwert wird dabei auch noch ausgewertet....


über eine funktion kann ich die queue füllen
1
uint8_t insertMsg( Argn p_fp ,uint8_t n_argn , ... );
die allociert speicher für die parameter und füllt die Q

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


Lesenswert?

Dr. Sommer schrieb:
> Unter POSIX-Systemen allerdings schon, denn dort braucht man zB zur
> Verwendung von dlsym() den Cast zwischen Objekt&Funktions-Zeiger...

Wobei die Rationale zu dlsym() das Dilemma vermerkt und ggf. über die
Einführung einer künftigen Alternative spricht:

http://pubs.opengroup.org/onlinepubs/009695399/functions/dlsym.html

Ein typisches Beispiel für eine Architektur, bei der ein Objektzeiger
ggf. nicht in der Lage ist, einen Funktionszeiger aufzunehmen, ist
übrigens der AVR (auf den Modellen mit mehr als 128 KiB Flash).

von Sina A. (sinapse)


Lesenswert?

Karl Heinz schrieb:
>> und warum muss der erste stern im cast geklammert werden?
>
> Musste er ja hier auch int (*tuWas)(char *, char *);

hier versteh ich ja die klammer um (*tuWas), weil es bei tuWas los geht 
und dann anstatt rechts weiter zu lesen, erstmal der stern links 
ausgewertet werden soll.

beim cast

int (*)(char *, char *)

gehts ja anscheinend schon beim ersten stern los... da brauch man doch 
den stern nicht mehr klammern... oder es geht rechts vom stern los... 
woher weiss der compiler das dann?  irgendwie fehlt mir der 
"einstiegspunkt" ohne einen bezeichner wie tuWas... würd gern die 
dahinterliegende logik verstehen.


das mit der union kenn ich noch nicht.  könnt ihr mir mal ein kurzes 
beispiel geben?


vielen dank schonmal fuer die vielen antworten

lg

von Takao K. (takao_k) Benutzerseite


Lesenswert?

sina anargo schrieb:
>
> das mit der union kenn ich noch nicht.  könnt ihr mir mal ein kurzes
> beispiel geben?
>
>
> vielen dank schonmal fuer die vielen antworten
>
> lg

Also ich habe es auch erst verstanden, als es eine sinnvolle Anwendung 
dafuer gab.
1
union _8080_sp
2
{
3
 struct _8080_sp_hl
4
 {
5
  unsigned char h;
6
  unsigned char l;
7
 }hl8;
8
 unsigned int hl16;
9
}sp_8080,pc_8080,offset_8080;

Die Syntax ist ganz C maessig etwas rueckwaerts, also z.B. der Name der 
Union _8080_sp wird garnicht verwendet.

Was ich hier konkret tue ist 3 Veriablen zu definieren:

sp_8080, pc_8080, und offset_8080.

Bei diesen koennen die oberen und unteren 8 bits hl8.h und hl8.l 
unabhaengig als uchar angesprochen werden.

sogleich wird auch hl16, uint, welche mit hl8 eine "union" bildet, den 
richtigen 16bit (uint) Wert enthalten!

Was ist der Sinn des ganzen? Man kann sich langatmige Typcasts sparen, 
wenn z.b. ein uint Offset als 2 8bit Werte aus dem Speicher geladen 
werden soll. Hierbei soll die Reihenfolge durch das Programm vorgegeben 
werden. Ausserdem ist es auch erfoderlich, auf das obere und untere Byte 
direkt zuzugreifen.

Also
1
uint offset =((unsigned int)(h_byte<<8))+((unsigned int)(l_byte))
 kann man sich sparen.

Stattdessen:
1
offset.hl8.h=h_byte;offset.hl8.l=l_byte;

Und offset.hl16 enthaelt dann den 16 bit wert.

Wenn die Reihenfolge umgekehrt sein soll, wird es einfach umgekehrt 
geladen.

Wenn man da jedesmal
1
uint offset =((unsigned int)(h_byte<<8))+((unsigned int)(l_byte))

schreiben muesste, waere C eine schreckliche Sprache.

von Karl H. (kbuchegg)


Lesenswert?

sina anargo schrieb:


> beim cast
>
> int (*)(char *, char *)
>
> gehts ja anscheinend schon beim ersten stern los... da brauch man doch
> den stern nicht mehr klammern... oder es geht rechts vom stern los...
> woher weiss der compiler das dann?  irgendwie fehlt mir der
> "einstiegspunkt" ohne einen bezeichner wie tuWas... würd gern die
> dahinterliegende logik verstehen.

Die Logik ist ganz einfach:

Nimm eine entsprechende Definition einer Variablen und lass die Namen 
weg.


Im Prinzip könnte man das in diesem speziellen Fall auch ohne die 
Klammern erkennen, denn
1
   int *(char *, char *)
ist keine gültige C Datentypbezeichnung.

Aber: Das wäre dann schon wieder ein Sonderfall. So hast du eine 
einfache Regel. Ein Cast sieht aus wie die Definition einer Variablen, 
wobei man den Variablennamen weglässt. Das ist einfach zu merken und 
einfach anzuwenden. Jeder der eine Variable korrekt definieren kann, 
kann damit auch einen Cast anschreiben, der einen Ausdruck in den 
Datentyp dieser Variablen casten kann (falls das überhaupt möglich ist).


> das mit der union kenn ich noch nicht.  könnt ihr mir mal ein kurzes
> beispiel geben?
1
typedef int (*voidFunc)( void );
2
typedef int (*oneArgFunc)( int );
3
typedef int (*twoArgFunc)( int, int );
4
5
union callBackUnion
6
{
7
  voidFunc   callBackVoid;
8
  oneArgFunc callBackOne;
9
  twoArgFunc callBackTwo;
10
};
11
12
struct callBack
13
{
14
  int funcType;   // 0 bedeutet callBackVoid ist gültig
15
                  // 1 bedeutet callBackOne  ist gültig
16
                  // 2 bedeutet callbackTwo ist gültig
17
  union callBackUnon callBack;  // der Funktionspointer
18
};
19
20
...
21
22
int foo( void )
23
{
24
  return 0;
25
}
26
27
int negate( int arg )
28
{
29
  return -arg;
30
}
31
32
int add( int lhs, int rhs )
33
{
34
  return lhs + rhs;
35
}
36
37
int doIt( struct callBack* function, int arg1, int arg2 )
38
{
39
  if( function->funcType == 0 )
40
    return function->callBack.callBackVoid();
41
42
  else if( function->funcType == 1 )
43
    return function->callBack.callBackOne( arg1 );
44
45
  else if( function->funcType == 2 )
46
    return function->callBack.callBackTwo( arg1, arg2 );
47
48
  return -1;
49
}
50
51
void setVoidFunc( struct callBack* function, voidFunc func )
52
{
53
  function->funcType = 0;
54
  function->callBack.callBackVoid = func;
55
}
56
57
void setOneArgFunc( struct callBack* function, oneArgFunc func )
58
{
59
  function->funcType = 1;
60
  function->callBack.callBackOne = func;
61
}
62
63
void setTwoArgFunc( struct callBack* function, twoArgFunc func )
64
{
65
  function->funcType = 2;
66
  function->callBack.callBackTwo = func;
67
}
68
69
int main()
70
{
71
  struct callBack operation;
72
73
  setTwoArgFunc( &operation, add );
74
  doIt( &operation );
75
}

Vorteil ist, dass du dir nicht einfach irgendwas zurecht casten kannst, 
wie du lustig bist. Solange die 'Kennzahlen' für den Funktionstyp bei 
der Zuweisung und der Auswertung übereinstimmen, hast du dich wieder 
soweit in den sicheren Hafen manövriert, als du dem Compiler wenigstens 
ein rudimentäres Überprüfen der Datentypen ermöglichst.
Ein Cast hingegen ist ein effektives Abschalten dieser Überprüfung. Und 
die Praxis zeigt, dass das öfter als einem lieb ist schon auch mal ins 
Auge geht. Das Vertrauen in die Fähigkeiten des Programmierers war beim 
alten K&R C noch recht ausgeprägt, wurde dann aber ganz schnell immer 
weiter zurückgedrängt, als sich herausgestellt hat, dass dieses 
Vertrauen nicht unbedingt etwas ist, worauf man sichere Programme setzen 
soll. Vertrauen ist gut, Kontrolle ist besser.

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


Lesenswert?

sina anargo schrieb:
> da brauch man doch den stern nicht mehr klammern

Doch, ansonsten erzeugst du einen Zeiger auf "int", nicht einen Zeiger
auf eine Funktion, die "int" zurückgibt.

> das mit der union kenn ich noch nicht. könnt ihr mir mal ein kurzes
> beispiel geben?
1
void foo(void) {
2
   // tuwas
3
}
4
5
void bar(int someparam) {
6
   // tu was mit someparam
7
}
8
9
union func_u {
10
  void (*f_void)(void);
11
  void (*f_int) (int);
12
};
13
14
func_u u;
15
16
// ...
17
  u.f_void = foo;
18
  // ...
19
  u.f_void();
20
  // ...
21
22
  u.f_int = bar;
23
  // ...
24
  u.f_int(42);

von Takao K. (takao_k) Benutzerseite


Lesenswert?

Also soweit ich es verstehe, verwenden (Sie?) verwendest du eine union, 
um mit unterschiedlichen Datentypen umgehen zu koennen (hier: 
Funktionszeiger).

Wenn dann auf einen anderen in der union vorhandenen Typ zugegriffen 
wird macht es natuerlich keinen Sinn. Also es wird immer nur ein 
bestimmer verwendet, und der ist auswaehlbar. Diese Selektion wird in 
einer numerischen Variablen vermerkt.

Mal richtig was gelernt heute. Habe ich so nicht gesehen online, union 
habe ich mehrfach schon nachgelesen aber noch keine sinvolle Anwendung 
dafuer gefunden (bis vor kurzem).

von Karl H. (kbuchegg)


Lesenswert?

Takao K. schrieb:
> Also soweit ich es verstehe, verwenden (Sie?) verwendest du eine union,
> um mit unterschiedlichen Datentypen umgehen zu koennen (hier:
> Funktionszeiger).

Ich verwende eine union um vom Prinzip her nur eine einzige Variable zu 
haben, die ja nachdem wie ich sie anspreche, einen anderen Datentyp hat.

> Wenn dann auf einen anderen in der union vorhandenen Typ zugegriffen
> wird macht es natuerlich keinen Sinn.

Und ist offziell auch verboten.

> Also es wird immer nur ein
> bestimmer verwendet, und der ist auswaehlbar. Diese Selektion wird in
> einer numerischen Variablen vermerkt.

Genau.
Der springende Punkt ist, dass ich in der Verwendung dann nicht einfach 
caste, wie es mir in den Sinn kommt, sondern das das Typsystem der 
Sprache nach wie vor für mich arbeitet. Solange es keinen Wurschtel mit 
den Kennzahlen gibt. Und den kann ich mit Funktionen so einigermassen 
unter Kontrolle halten (na ja).

> Mal richtig was gelernt heute. Habe ich so nicht gesehen online, union
> habe ich mehrfach schon nachgelesen aber noch keine sinvolle Anwendung
> dafuer gefunden (bis vor kurzem).

: Bearbeitet durch User
von Sina A. (sinapse)


Lesenswert?

vielen dank für die antworten

lg

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.