Forum: Mikrocontroller und Digitale Elektronik Funktinspointer und valist


von zu gast (Gast)


Lesenswert?

1
void foo(int);
2
void boo(int, int);

Wenn ich so zwei Funktionen habe kann ich mit einem
1
int (*pfunc)(int) = foo;

ja auch die erste Funktion zeigen.

Ist es generell möglich so etwas wie
1
int (*pfunc)(int, ...)

zu deklarieren, um sowohl auf foo als auch boo zeigen zu können? oder 
wie geht man da am besten vor?

von Oliver S. (oliverso)


Lesenswert?

Der Compiler frisst
1
void (*pfunc)(int, ...) = (void (*)(int, ...))foo;

https://godbolt.org/z/TEEf4ca9a

Für Risiken und Nebenwirkungen bzgl. undefined behaviour fragen Sie 
bitte den C-Standard.

Oliver

von Daniel A. (daniel-a)


Lesenswert?

Jein.

Funktionen mit unterschiedlichen Argumenten, also unterschiedliche 
Funktionstypen, sind nicht kompatibel. Jenachdem kann man sie casten, 
aber das ist UB.

Historisch hatten in C Funktionen nicht immer Argumente. Deshalb muss 
man für Argumentlose Funktionen (void) für die Argumente schreiben. Aber 
ohne das ist der Funktion prototyp auch valide, und kompatibel zu 
Funktionstypen mit Argumenten. Folgendes ist valides C:
1
typedef int (*prototype_t)();
2
3
int foo(int x){ return x; }
4
int bar(int x, int y){ return x+y; }
5
6
int main(){
7
  prototype_t a = foo;
8
  a = bar;
9
  a(1,2);
10
}

Es gibt aber glaube ich trotzdem gewisse Limitationen, was für 
Funktionstypen erlaubt sind, usw.

von Jim M. (turboj)


Lesenswert?

Riecht aber sehr nach XY-Problem.

Bei Funktionspointern sollten die Argument Typen fest stehen.

Immerhin weiss ja der Aufrufer normalerweise nicht auf welche Funktion 
der Pointer nun wirklich zeigt, bzw. sollte es ihm egal sein.

von Thilo R. (harfner)


Lesenswert?

zu gast schrieb:
> Ist es generell möglich so etwas wie
>
1
> int (*pfunc)(int, ...)
2
>
>
> zu deklarieren, um sowohl auf foo als auch boo zeigen zu können? oder
Geht, in dem Sinne dass der Compiler keinen Fehler wirft. Was dann das 
Programm macht, lese eine beliebige Einführung in "undefined behaviour" 
und warum man das nicht möchte.

> wie geht man da am besten vor?
Man lässt es.

Was möchtest Du mit einem Pointer, der mal auf eine void foo(int) zeigt 
und mal auf eine void boo(int, int)?
Doch vermutlich die Funktionen aufrufen. Damit das überhaupt 
funktionieren kann, müsstest Du wissen, wohin der Pointer gerade zeigt. 
Dann brauchst Du den Pointer nicht.

Vermutlich möchtest Du eine Form von Late Binding erzielen. Solange Du 
in C bleibst, sorge dafür, dass alle Funktionen, die über den Zeiger 
aufgerufen werden, die gleiche Signatur haben. Nur das funktioniert.
Falls Du mehr willst, must Du C++ machen. Dann hast viel mehr 
Möglichkeiten, Dir selbst ins Knie zu schießen :-)

von A. S. (Gast)


Lesenswert?

zu gast schrieb:
> um sowohl auf foo als auch boo zeigen zu können

Das zeigen ist ja meistens nicht das Problem. Sondern das korrekte 
aufrufen. Meistens passiert in C nix. Aber sobald ein Parameter manchmal 
größer ist als int, braucht er in jedem Fall die konkrete Form.

Und das nix passiert, heisst nix. Also, wie rufst Du das auf? Ggf Union.

von zu gast (Gast)


Lesenswert?

Ursprünglich hatte ich vor den Pointer mit einem enum zusammen in einer 
Struktur zu halten als Information, was jetzt erwartet wird.

Beitrag #6938121 wurde von einem Moderator gelöscht.
von Thilo R. (harfner)


Lesenswert?

Und woher kommen die Argumente beim Aufruf?

von Cyblord -. (cyblord)


Lesenswert?

zu gast schrieb:
> Ursprünglich hatte ich vor den Pointer mit einem enum zusammen in einer
> Struktur zu halten als Information, was jetzt erwartet wird.

Das Ganze wirkt einfach unausgegoren. Funktionspointer machen keinen 
Sinn, wenn man sich erst mal zusammensuchen muss, WAS genau man 
übergeben muss.
Das Gegenteil ist der Fall. Funktionspointer nimmt man dann, wenn die 
Parameter immer gleich sind, die Funktionalität sich aber Ändern soll 
ohne dass der Aufrufer etwas darüber wissen muss.

Also nochmal 5 Schritte zurück, Sinn und Zweck überlege. WAS will man 
erreichen. Dann noch mal WIE erreicht man das am saubersten.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

zu gast schrieb:
> Ursprünglich hatte ich vor den Pointer mit einem enum zusammen in einer
> Struktur zu halten als Information, was jetzt erwartet wird.

Dann ist es am Besten, die unterschiedlichen Funktionszeigertypen per 
union abzulegen und über die enum zu entscheiden, welcher verwendet 
wird. Du darfst natürlich nicht den einen Funktionszeigertyp zuweisen 
und dann den anderen auslesen.
1
enum CallType { Unary, Binary };
2
struct Call {
3
  enum CallType type;
4
  union {
5
    int (*unary)(int);
6
    int (*binary)(int, int);
7
  };
8
};
9
10
void call (struct Call* c) {
11
  switch (c->type) {
12
    case Unary:
13
      c->unary (1);
14
      break;
15
    case Binary:
16
      c->binary (1, 2);
17
      break;
18
  }
19
}

In C++ kannst du das mit std::variant sicherer und sauberer machen.

von Danilo (Gast)


Lesenswert?

Mach doch aus
void foo(int);
einfach
void foo(int,int);

Den zweiten int benutzt du halt nicht.

von Mombert H. (mh_mh)


Lesenswert?

A. S. schrieb:
> zu gast schrieb:
>> um sowohl auf foo als auch boo zeigen zu können
>
> Das zeigen ist ja meistens nicht das Problem. Sondern das korrekte
> aufrufen.
Das stimmt in diesem Fall, aber nicht generell (z.B. Pointerarithmetik).

A. S. schrieb:
> Meistens passiert in C nix.
Das ist eine sehr gefährlicher Satz.

von zu gast (Gast)


Lesenswert?

>Das Ganze wirkt einfach unausgegoren.

Ich muss gestehen, dass mir das inzwischen selber so vorkommt. Aber 
dennoch ist der Thread bis jetzt für mich wirklich lehrreich und 
hilfreich. Vielleicht ergeben sich noch andere Aspekte und ich werde 
sowiso noch mal ein paar Rollen rückwärts machen.

von Markus F. (mfro)


Lesenswert?

zu gast schrieb:
> Ursprünglich hatte ich vor den Pointer mit einem enum zusammen in einer
> Struktur zu halten als Information, was jetzt erwartet wird.

Wenn das enum den Selektor darstellt, welcher der Prototypen gemeint 
ist, dann ist der Funktionszeiger kein Funktionszeiger, sondern ein 
union.

Dafür sind unions gemacht.
1
enum f { one, two };
2
struct xxx
3
{
4
    enum f sel;
5
    union
6
    {
7
        int (*pfunc1)(int);
8
        int (*pfunc2)(int, int);
9
    } yyy;
10
}

[erlkönig war schneller...]

von M. Н. (Gast)


Lesenswert?

zu gast schrieb:
> Ist es generell möglich so etwas wieint (*pfunc)(int, ...)
>
> zu deklarieren, um sowohl auf foo als auch boo zeigen zu können? oder
> wie geht man da am besten vor?

Nein. Es ist nicht möglich.
Das Problem liegt darin, wie die vararg liste aufgebaut ist. Das ist ABI 
abhängig und kann ggf. massiv explodieren.

Beispielsweise werden bei manchen Architekturen die ersten paar 
Funktionsargumente nicht auf den Stack gelegt, sondern in Registern 
übergeben, um die Performance und Stacknutzung zu optimieren. Da ist es 
bspw. möglich, dass die ersten 4 Argumente in Registern R0 bis R3 
liegen.
Schreibt man nun eine vararg Funktion
1
void func(int, int, ...);
Varargs sind aufgrund ihrer dynamischen Größe leider schlecht auf 
Register zu mappen. Deswegen passiert häufig folgendes Mapping: Die 
beiden integer am Anfang landen in R0 und R1 und der Rest kommt auf den 
Stack, wohingegen die Funktion
1
void func(int, int, int);
alle Parameter in die register R0, R1 und R2 pressen kann.

Alle Angaben sind hier natürlich spekulativ und hängen konkret vom 
Compiler + Architektur ab -> ABI.

Ein weiteres Problem der vararg Liste ist ihre Unfähigkeit floats zu 
handhaben. Beispiel:
1
// printf(const char *format, ...);
2
3
float my_float = 2.4f;
4
printf("Float is: %f\n", my_float);
die float Variable my_float wird hier in der vararg Liste übergeben. 
Gemäß des C-Standards erfolgt nun hier die automatische double-promotion 
und der float wird in einen double umgewandelt. (Ja, das ist immer so! 
Das kann man auch nicht abstellen)

Dieses Verhalten würde deinen Funktionspointer-Casts ebenfalls das 
Genick brechen.

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.