c > struct > funktionszeiger casten

OP #7813591
Lesenswert?

wie castet man den funktionszeiger richtig? es gibt verschiedene funktionszeiger also verschiedene rückgabetypen..

1
float read_bat_voltage_avg();
2
uint8_t send_bat();
3

4
typedef struct{
5
  char id;
6
  void (* receive_function )( void );
7
}lora_cmd_t;
8

9
lora_cmd_t lora_cmd[] =
10
{
11
  { '0', (float)read_bat_voltage_avg }, // returns battery voltage
12
  { '1', (uint8_t)send_bat }, // dummy
13
};

Danke schon mal =)

#7813612
Lesenswert?

Jan H. schrieb:

was könnte ich stattdessen machen, wenn dort es dort mehrere rückgabetypen gibt?

Das geht nicht, das Konzept ist kaputt.

Was Du stattdessen machen könntest: Den Funktionen wird ein void-Pointer übergeben, der auf einen Puffer zeigt, und dazu ein Argument, das dessen Größe in Bytes signalisiert. Zusätzlich noch ein weiterer Pointer auf ein enum, das den schlussendlich rauskommenden Typ signalisiert.

Das Konzept entspricht dem eines "Variant"-Datentyps.

Das würde funktionieren, machts aber letztlich auch kaum sinnvoller, weil der Aufrufer jeweils das "zurückgegebene" Resultat korrekt auseinanderpflücken und einsortieren muss.

Vielleicht solltest Du nochmal genauer darüber nachdenken, welches Problem Du eigentlich lösen möchtest.

OP #7813623
Lesenswert?

Harald K. schrieb:

Jan H. schrieb:

was könnte ich stattdessen machen, wenn dort es dort mehrere rückgabetypen gibt?

Das geht nicht, das Konzept ist kaputt.

Was Du stattdessen machen könntest: Den Funktionen wird ein void-Pointer übergeben, der auf einen Puffer zeigt, und dazu ein Argument, das dessen Größe in Bytes signalisiert. Zusätzlich noch ein weiterer Pointer auf ein enum, das den schlussendlich rauskommenden Typ signalisiert.

Das Konzept entspricht dem eines "Variant"-Datentyps.

Das würde funktionieren, machts aber letztlich auch kaum sinnvoller, weil der Aufrufer jeweils das "zurückgegebene" Resultat korrekt auseinanderpflücken und einsortieren muss.

Vielleicht solltest Du nochmal genauer darüber nachdenken, welches Problem Du eigentlich lösen möchtest.

okay, ich verstehe..

es geht darum eine funktion nach erhalt eines bestimmten commands auszuführen..

empfange ich z.b.: "toggle#0" soll halt die funktion mit der id.: 0 ausgeführt werden usw.

das war der plan dahinter.. quasi eine tabelle wo meine ganzen funktionen vorhanden sind die dann nach "index" aufgerufen werden können.

Gast #7813634
Lesenswert?

Jan H. schrieb:

das war der plan dahinter.. quasi eine tabelle wo meine ganzen funktionen vorhanden sind die dann nach "index" aufgerufen werden können.

Warum nimmst du nicht einfach einrn switch-case?

Gute programmierer setzen solche Konstrukte nur ein, wenn sie wirklich notwendig sind.

#7813669
Lesenswert?

FÜr sowas sind Unions gemacht. Da kann man alle Funktionszeigertypen drin definieren und dann über das richtige Element die Funktion aufrufen:

1
union MeineFunktion
2
{
3
    float (*floatFunktion)(void);
4
    int (*intFunktion)(void);
5
};
6

7
int funktion1(void)
8
{
9
    return 42;
10
}
11

12
float funktion2(void)
13
{
14
    return M_PI;
15
}
16

17
int main()
18
{
19
    union MeineFunktion fkt;
20
    fkt.intFuktion = funktion1;
21
    fkt.intFunktion();
22

23
    fkt.floatFuktion = funktion2;
24
    fkt.floatFunktion();
25
}

Natürlich muss man irgendwo noch die Information haben, welcher Funktionszeiger der richtige ist, um die Funktion entsprechend aufzurufen (man darf nicht intFunktion befüllen und dann floatFunktion aufrufen) und den Returnwert korrekt zu verarbeiten, aber das müsste man beim Cast ja auch.

Arduino F. schrieb:

Ansonsten, wenn wirklich, dann den Umweg über void zeiger.

Die funktionieren zwar meistens auch, aber nur, weil die meisten Compiler an der Stelle gegen den Standard verstoßen. Eigentlich sind Zeiger auf Objekte und Zeiger auf Funktionen nicht kompatibel und können auch mit einem Cast nicht einfach in einander umgewandelt werden. Wenn man einen "generischen" Funktionszeiger in der Art nutzen möchte, sollte man void(*)(void) verwenden.

#7813672
Lesenswert?

Wie verwendest du denn die Struktur? Ich würde beim Verwenden der Struktur anhand der ID entscheiden, welchen Rückgabewert die Funktion hat und dann entsprechend casten (da es ja ein Void-Pointer ist). Also irgendwie so

typedef struct{ char id; void (* receive_function )( void ); }lora_cmd_t; lora_cmd_t lora_cmd[] = { { '0', read_bat_voltage_avg }, // returns battery voltage { '1', send_bat }, // dummy };

if(lora_cmd[0].id == 0) { float return_value = (float)lora_cmd[1].receive_function(); } else { uint8_t return_value = (uint8_t)lora_cmd[1].receive_function(); }

#7813706
Lesenswert?

Niklas G. schrieb:

Daniel V. schrieb:

if(lora_cmd[0].id == 0) { float return_value = (float)lora_cmd[1].receive_function();

Wie genau soll da ein void nach float gecastet werden?

Klappt so natürlich nicht. Und nicht nur der Cast ist falsch, sondern es wird auch noch eine Funktion über den falschen Zeigertyp aufgerufen. Es müsste eher sowas sein:

1
float return_value = (float(*)(void))(lora_cmd[1].receive_function)();

Oder man nimmt eben die unions und spart sich die ganze Cast-Orgie.

Persönliche Seite #7813708
Lesenswert?

Rolf M. schrieb:

Die funktionieren zwar meistens auch, aber nur, weil die meisten Compiler an der Stelle gegen den Standard verstoßen. Eigentlich sind Zeiger auf Objekte und Zeiger auf Funktionen nicht kompatibel und können auch mit einem Cast nicht einfach in einander umgewandelt werden.

Unter POSIX-Systemen ist es erlaubt, dies ist für die Funktion dlsym() erforderlich. Aber C und C++ an sich garantieren das nicht.

Rolf M. schrieb:

Wenn man einen "generischen" Funktionszeiger in der Art nutzen möchte, sollte man void(*)(void) verwenden.

Zwischen Funktionszeigern hin-und her-casten ist erlaubt, aber aufrufen darf man ihn nur über den "richtigen" (ursprünglichen) Typ.

Rolf M. schrieb:

Oder man nimmt eben die unions und spart sich die ganze Cast-Orgie.

Ja, wobei man sich natürlich fragt: Wenn man sowieso ein switch-case für den Rückgabetyp braucht - wozu dann noch der Funktionszeiger?

#7813715
Lesenswert?

Niklas G. schrieb:

Unter POSIX-Systemen ist es erlaubt, dies ist für die Funktion dlsym() erforderlich. Aber C und C++ an sich garantieren das nicht.

Windows hat eine vergleichbare Funktion, weshalb das da normalerweise auch funktioniert. Wie gesagt: Die meisten Compiler erlauben es. Ich vermeide es trotzdem, wenn ich kann.

Rolf M. schrieb:

Wenn man einen "generischen" Funktionszeiger in der Art nutzen möchte, sollte man void(*)(void) verwenden.

Zwischen Funktionszeigern hin-und her-casten ist erlaubt, aber aufrufen darf man ihn nur über den "richtigen" (ursprünglichen) Typ.

Ja, richtig. Man castet dann vom Typ der tatsächlichen Funktion auf void(*)(void) und vor dem Aufruf wieder zurück auf den ursprünglichen Typ. Deshalb hab ich auch geschrieben, dass man irgendwo noch die Info braucht, was denn der ursprüngliche Typ war.

Rolf M. schrieb:

Oder man nimmt eben die unions und spart sich die ganze Cast-Orgie.

Ja, wobei man sich natürlich fragt: Wenn man sowieso ein switch-case für den Rückgabetyp braucht - wozu dann noch der Funktionszeiger?

Es kann ja auch mehrere Einträge mit dem selben Typ geben. Soweit ich verstehe, will er eine Zuordnungstabelle haben zwischen bestimmten Parametern und den Funktionen, um diese in irgendeiner Form zu verarbeiten. Wenn's jetzt z.B. 100 float-Parameter gibt und 100 Funktionen dafür, muss man in der Tabelle nur die Zeiger darauf hinterlegen und nicht im switch/case 100 separate Funktionsaufrufe dafür haben, also nicht ein case pro Funktion, sondern nur einer pro Funktions-Typ.

Gast #7813720
Lesenswert?

Rolf M. schrieb:

Wenn's jetzt z.B. 100 float-Parameter gibt und 100 Funktionen dafür, muss man in der Tabelle nur die Zeiger darauf hinterlegen und nicht im switch/case 100 separate Funktionsaufrufe dafür haben, also nicht ein case pro Funktion, sondern nur einer pro Funktions-Typ.

Dafür erhält man einen Quelltext, der nur mühsam zu durchblicken ist. Das geht am Sinn einer Hochsprache vorbei. Code soll für andere Entwickler gut lesbar sein. Gut lesbarer Code vermeidet Missverständnisse und Fehler.

100 case sind gut lesbar. Deren große Anzahl ist kein Problem bezüglich Lesbarkeit.

#7813722
Lesenswert?

Sherlock 🕵🏽‍♂️ schrieb:

Dafür erhält man einen Quelltext, der nur mühsam zu durchblicken ist.

Das sehe ich anders. Ich finde so eine Tabelle lesbarer, als so ein riesiges switch/case. Und wenn pro Eintrag noch mehr als eine ID und eine Funktion benötigt werden, ist die Lesbarkeit beim switch/case endgültig dahin.

100 case sind gut lesbar. Die schiere Anzahl ist kein Problem.

Nicht so gut wie die Tabelle meiner Meinung nach.

Persönliche Seite #7813749
Lesenswert?

Arduino F. schrieb:

Und dank C++ auch meist vermeidbar.

Jau:

1
template <typename Ret>
2
void wrapTypeErased (Ret (*fPtr) ()) {
3
    // Rufe Funktion auf
4
    Ret ret (fPtr ());
5

6
    // Mach was mit dem Rückgabewert "ret"
7
    std::cout << "LoRa-Funktion wurde aufgerufen, Rückgabewert ist: "
8
            // Hack: Nutze '+' Operator um char-Typen (inklusive (u)int8_t) nach Integer zu promoten
9
            // um diese als Zahl auszugeben statt als Zeichen
10
        << +ret
11
        << std::endl;
12
}
13

14
template <auto F>
15
constexpr void (*wrap ()) () {
16
    if constexpr (std::is_void_v<decltype (F())>) return F;
17
    else return [] () { return wrapTypeErased (F); };
18
}
19

20
lora_cmd_t lora_cmd[] =
21
{
22
  { '0', wrap<read_bat_voltage_avg> () }, // returns battery voltage
23
  { '1', wrap<send_bat> () }, // dummy
24
  { '2', wrap<do_something> () } // A function that returns void
25
};

In wrapTypeErased kann man dann was beliebiges mit dem Rückgabetyp machen, und weil es ein template ist hat "ret" halt immer den richtigen Rückgabetyp der jeweiligen Funktion. Hier wird es einfach auf den stdout ausgegeben, automatisch entsprechend formatiert. Man kann den Rückgabewert auch ignorieren aber wozu gibt es ihn dann überhaupt?

Das Ganze kommt ganz ohne Casts aus und ist typsicher. Das "wrap<EineFunktion> ()" wird letztlich zu einem Funktionszeiger auf eine automatisch generierte Funktion für den richtigen Rückgabewert, welche die eigentliche Funktion aufruft und dann hier als Beispiel den Rückgabewert ausgibt. Als Sonderfall wird bei void-Funktionen direkt der gewünschte Funktionszeiger verwendet, ohne Wrapper. Es wird pro Rückgabetyp eine Funktion generiert (nicht pro Funktion).

Es gibt aber noch mehr Möglichkeiten, z.B. mit std::function oder vielleicht auch std::variant + std::visit oder ganz klassisch Polymorphie.

(Firma: Golden Delicious Computers) #7813758
Lesenswert?

Wenn es Standard-C bleiben muss (wie z.B. ein Linux-Kernel) muss man einen eigenen Datentyp definieren, den alle Funktionen als Ergebnis liefern können. Typischerweise ein Struct mit einer Typ-ID und einer eigebetteten Union.

1
struct return_typ {
2
  enum { TYP_FLOAT, TYP_UINT8, TYP_STRING } typ;
3
  union {
4
    float float_val;
5
    uint8_t uint8_val;
6
    char *string_val;
7
  } data;
8
};
9

10
struct return_typ read_bat_voltage_avg();
11
struct return_typ send_bat();
12

13
typedef struct{
14
  char id;
15
  struct return_typ (* receive_function )( void );
16
}lora_cmd_t;
17

18
lora_cmd_t lora_cmd[] =
19
{
20
  { '0', read_bat_voltage_avg }, // returns battery voltage
21
  { '1', send_bat }, // dummy
22
};
23

24
struct return_typ result = (*lora_cmd[1])();
25
switch (result.typ) {
26
  case TYP_FLOAT:  ... result.data.float_val;
27
  case TYP_UINT8:  ... result.data.uint8_val;
28
  ...
29
};

Evtl. wäre es notwendig einen pointer auf ein struct return_typ als Ergebnis zu liefern.

Gast #7813887
Lesenswert?

Jan H. schrieb:

struct return_typ result = (*lora_cmd[1])();

Was genau bewirkt diese Zeile?

Führe die Funktion aus, auf die "lora_cmd[1]" zeigt, und zwar ohne Argumente, und lege das Ergebnis in der Variable "result" ab, die eine Struktur vom Typ "return_typ" ist.

OP #7813926
Lesenswert?

Niklas G. schrieb:

Jan H. schrieb:

ich habe das mal so in den online debugger gehauen und der wirft mir da fehler, siehe foto.

Ja es muss so sein:

1 struct return_typ result = (lora_cmd[1].receive_function)();

Okay dann verstehe ich es. Und das mit den cases funktioniert so auch nicht oder?

1
struct return_typ result = (*lora_cmd[1])();
2
switch (result.typ) {
3
  case TYP_FLOAT:  ... result.data.float_val;
4
  case TYP_UINT8:  ... result.data.uint8_val;
5
  ...
6
};
OP #7813991
Lesenswert?

Niklas G. schrieb:

Jan H. schrieb:

Und das mit den cases funktioniert so auch nicht oder?

Na du musst halt noch was einfügen, was den Rückgabewert verarbeitet. Und ein "break;" in jeden "case". Schreib doch mal was du mit dem Rückgabewert überhaupt machen würdest, da könnte man dir vermutlich viel besser helfen.

Es geht ja erstmal um das Prinzip. Der Compiler findet die enums im Struct nicht.

Persönliche Seite #7813994
Lesenswert?

Jan H. schrieb:

Der Compiler findet die enums im Struct nicht.

Zeig die Fehlermeldung. Es müsste eigentlich so gehen.

Jan H. schrieb:

Es geht ja erstmal um das Prinzip.

Naja, es gibt hier viele prinzipielle Möglichkeiten. Dein ursprünglicher Wunsch passt nicht so recht auf die üblichen Prinzipien einer statisch typisierten Sprache, insbesondere nicht von C. Daher gibt es kein Patentrezept. Deine konkrete Anwendung besser zu verstehen könnte helfen das Problem vielleicht ganz anders besser zu lösen (X-Y-Problem).

Am Einfachsten ist es, die Verarbeitung, die in das switch-case kommen soll, einfach in die einzelnen Funktionen zu verschieben. Dann löst sich das alles in Luft auf.

Persönliche Seite #7814035
Lesenswert?

In reinem C könnte man es mit _Generic so machen:

1
void callWrapper_float (float (*fPtr) ()) {
2
    float result = fPtr ();
3
    printf ("Float result: %f\n", result);
4
}
5

6
void callWrapper_uint8_t (uint8_t (*fPtr) ()) {
7
    uint8_t result = fPtr ();
8
    printf ("uint8_t result: %d\n", (int) result);
9
}
10

11
#define DEFINE_WRAPPER(fun) void callWrapper_ ## fun () { \
12
                        _Generic ((fun) (), \
13
                            float: callWrapper_float ((float (*) ()) &fun), \
14
                            uint8_t: callWrapper_uint8_t ((uint8_t (*) ()) &fun) \
15
                        ); \
16
                    }
17

18
#define GET_WRAPPER(fun) (callWrapper_ ## fun)
19

20
DEFINE_WRAPPER(read_bat_voltage_avg)
21
DEFINE_WRAPPER(send_bat)
22

23
typedef struct{
24
  char id;
25
  void (* receive_function )( void );
26
} lora_cmd_t;
27

28
lora_cmd_t lora_cmd[] =
29

30
{
31
  { '0', GET_WRAPPER (read_bat_voltage_avg) }, // returns battery voltage
32
  { '1', GET_WRAPPER (send_bat) }, // dummy
33
};
34

35
int main () {
36
    lora_cmd [0].receive_function ();
37
    lora_cmd [1].receive_function ();
38
}

Man muss für jeden Typ eine eigene "callWrapper_XXX" -Funktion schreiben welche das Ergebnis verarbeitet, und dies dann im DEFINE_WRAPPER in das _Generic eintragen. Blöderweise muss man den Pointer noch mal casten, weil bei _Generic alle Zweige gültig sein müssen, auch wenn sie nie evaluiert werden. Dann muss man für jede der Handler-Funktionen per GET_WRAPPER einen Wrapper definieren und diesen per GET_WRAPPER in die Liste eintragen.

Macht im Endeffekt das Gleiche wie obige C++ -Version, aber eben in umständlich dank C, ist aber ein bisschen effizienter als das switch-case mit dem return_typ.

Gast #7814046
Lesenswert?

Johann L. schrieb:

Nach dem Motto: Der Code ist schlecht, lasst uns ihn noch schlechter machen!

Das finde ich gar nicht. Wenn alle Funktionen den gleichen Rückgabetyp haben, wäre die ganze Diskussion hier überflüssig. Man könnte das einfach so ohne Tricks runter programmieren.

Persönliche Seite #7814058
Lesenswert?

So ginge es in C++ mit einer std::variant:

1
using RetType = std::variant<std::uint8_t, float>;
2

3
typedef struct{
4

5
  char id;
6

7
  RetType (* receive_function )( void );
8

9
}lora_cmd_t;
10

11
template <auto F>
12
constexpr RetType (*wrap ()) () {
13
    return [] () -> RetType {
14
        return RetType { std::in_place_type_t<decltype (F ())> {}, F() };
15
    };
16
}
17

18
lora_cmd_t lora_cmd[] =
19
{
20
  { '0', wrap<read_bat_voltage_avg> () }, // returns battery voltage
21
  { '1', wrap<send_bat> () } // dummy
22
};
23

24
int main () {
25
    int lokaleVariable = 42;
26

27
    std::visit ([&] (const auto& ret) {
28
            // Mach was mit dem Rückgabewert "ret"
29
            std::cout << "LoRa-Funktion wurde aufgerufen, Rückgabewert ist: "
30
                // Hack: Nutze '+' Operator um char-Typen (inklusive (u)int8_t) nach Integer zu promoten
31
                // um diese als Zahl auszugeben statt als Zeichen
32
                << +ret
33
                << ", dabei ist lokaleVariable=" << lokaleVariable
34
                << std::endl;
35
    }, lora_cmd [0].receive_function ());
36
}

Der Rückgabewert wird in ein Variant eingepackt, und beim Aufruf gibt man ein generisches Lambda an, was dann mit dem korrekten Typ aufgerufen wird. Der Vorteil daran ist, dass man bei jedem Aufruf einen individuellen Codeblock angeben kann, der auch auf lokale Variablen zugreifen kann, aber es nicht wie in C für jeden Typ manuell machen muss. Im Endeffekt ist es das gleiche wie der Vorschlag mit dem return_typ, aber handlicher.

#7814166
Lesenswert?

Wozu das umständliche switch-case Gehampel. Der Aufrufer weiß doch, was er auswerten will. Einfach nur eine union aus float und int als Returnwert und gut. Für sowas sind unions schließlich gedacht.

1
#include <stdint.h>
2

3
union fi {
4
  uint8_t u8 ;
5
  float f;
6
};
7

8
union fi read_bat_voltage_avg()
9
{
10
  union fi r;
11
  r.f = 1.23;
12
  return r;
13
}
14

15
union fi send_bat()
16
{
17
  union fi r;
18
  r.u8 = 123;
19
  return r;
20
}
21

22
typedef struct{
23
  char id;
24
  union fi (* receive_function )( void );
25
}lora_cmd_t;
26

27
lora_cmd_t lora_cmd[] =
28
{
29
  { '0', read_bat_voltage_avg }, // returns battery voltage
30
  { '1', send_bat }, // dummy
31
};
#7814197
Lesenswert?

Ich kann mir noch nicht so recht vorstellen, wie das nachher aussehen / verwendet werden soll. Sind das quasi Requests, die immer genau eine Response haben, und dort ist immer nur 1 Wert drin? Dann kann man das eventuell machen.
Andernfalls hätte man normalerweise irgend eine Funktion, die eine Nachricht zurück sendet, die man dann halt aufruft, und die das dann in eine Queue packt.

Wie dem auch sei, wie wäre es mit einer tagged Union?

1
enum lora_ret_type {
2
  LRT_u8,
3
  LRT_f,
4
};
5

6
typedef struct lora_ret {
7
  enum lora_ret_type type;
8
  union {
9
    uint8_t u8;
10
    float   f;
11
  };
12
} lora_ret_t;
13

14
#define LORA_RET(T, V) return ((lora_ret_t){ .type=LRT_ ## T, .T=(V) })
15

16
typedef lora_ret_t lora_cmd_handler(void);
17

18
lora_cmd_handler read_bat_voltage_avg;
19
lora_cmd_handler send_bat;
20

21
typedef struct{
22
  char id;
23
  lora_cmd_handler* handler;
24
}lora_cmd_t;
25

26
lora_cmd_t lora_cmd[] =
27
{
28
  { '0', read_bat_voltage_avg }, // returns battery voltage
29
  { '1', send_bat }, // dummy
30
};
31

32
lora_ret_t send_bat(void){
33
  LORA_RET(u8, 123);
34
}
35

36
...
37
  lora_ret_t ret = lora_cmd[X].handler();
38
  switch(ret.type){
39
    case LRT_u8: printf("LRT_u8: %d\n", ret.u8); break;
40
    case LRT_f : printf("LRT_f: %f\n", ret.f); break;
41
  }
Persönliche Seite #7814278
Lesenswert?

Jan H. schrieb:

Und das mit den cases funktioniert so auch nicht oder?

1
> struct return_typ result = (*lora_cmd[1])();
2
> switch (result.typ) {
3
>   case TYP_FLOAT:  ... result.data.float_val;
4
>   case TYP_UINT8:  ... result.data.uint8_val;
5
>   ...
6
> };

Ich verstehe immer noch nicht, welches Problem du so lösen willst.

Spätestens wenn es an die Auswertung der Rückgabewerte geht, braucht's doch eh ein if-else oder switch-case?

Also warum dann nicht einfach:

1
switch (lora_id)
2
{
3
   case LORA_ID_DIES:
4
   {
5
      uint8_t val8 = lora_mach_dies ();
6
      // Eval val8...
7
      break;
8
   }
9
   case LORA_ID_JENES:
10
   {
11
      float f = lora_mach_jenes ();
12
      // Eval f...
13
      break;
14
   }

Ist das mit einer Union und Funktionszeiger-Tabelle so viel effizienter? Oder so viel übersichtlicher?

Kaum.

(Firma: Gast) #7814352
Lesenswert?

Johann L. schrieb:

Kaum.

Mir schmeckt die id (bei dir lora_id) auch nicht.

Alleine schon weil man min. 2 Stellen im Code hat, die man zueinander konsistent halten muss. Einmal im Array, und dann in dem switch/case Vertut man sich da, kann das zu recht schwer zu findenden Fehlern führen. Schöner ist es, wenn der Compiler die Chance hat da einen Irrtum anzumeckern.

#7815181
Lesenswert?

Jan H. schrieb:

es geht darum eine funktion nach erhalt eines bestimmten commands auszuführen..

empfange ich z.b.: "toggle#0" soll halt die funktion mit der id.: 0 ausgeführt werden usw.

Ich würde mir einen Parser schreiben der den seriellen Empfang, oder woher auch immer das kommt, aufdrösselt in das Kommando 'toggle' und den weiteren Parameter '0'. Dann hat man 2 Parameter die man auf alles vergleichen kann was man möchte. Ist irgendein Vergleich 'true' wird entsprechend irgendwas aufgerufen oder verarbeitet oder was weiß ich. Am Ende kann man sonstwas Empfangen solange das Protokoll passt und verarbeiten.

Moderator Persönliche Seite #7815396
Lesenswert?

Meiner Meinung nach ist die Verwendung von Funktionspointern bei unterschiedlichen Rückgabewerten ein kaputtes Konzept.

Begründung

Funktionspointer sind dafür da, gezielt eine Funktion aus einer Familie von Funktionen aufzurufen, die das gleiche Interface benutzen. Nur hier kann man die Parameter und Rückgabewerte gleich behandeln.

Bei unterschiedlichen Interfaces wie zum Beispiel unterschiedlichen Rückgabewerten wird immer der Kontext relevant, wie der Rückgabewert zu behandeln ist. Das heißt: Spätestens nach Aufruf der Funktion wird eine Bewertung des Kontextes notwendig, um den Rückgabewert richtig interpretieren zu können. Dann kann man auch die Funktion in der Kontextbehandlung direkt aufrufen statt vorher abstrakt über einen Pointer zu gehen.

Fazit

Nimm einen Case-Switch und rufe hier die Funktionen konkret auf. Dort kannst Du dann auch den Rückgabewert korrekt (d.h. im Kontext) auswerten.

#7815412
Lesenswert?

Frank M. schrieb:

Meiner Meinung nach ist die Verwendung von Funktionspointern bei unterschiedlichen Rückgabewerten ein kaputtes Konzept.

Begründung

Funktionspointer sind dafür da, gezielt eine Funktion aus einer Familie von Funktionen aufzurufen, die das gleiche Interface benutzen. Nur hier kann man die Parameter und Rückgabewerte gleich behandeln.

Nimm einen Case-Switch und rufe hier die Funktionen konkret auf. Dort kannst Du dann auch den Rückgabewert korrekt (d.h. im Kontext) auswerten.

Ich verstehe Ihren Standpunkt, aber manchmal sind Funktionszeiger nützlich, wenn Sie eine von mehreren Funktionen zur Ausführung auswählen müssen. In diesem Fall ist es wichtig, die richtige Schnittstelle zu wählen. Wenn die Rückgabewerte jedoch unterschiedlich sind, kann dies wirklich zu einem Problem werden.

#7815414
Lesenswert?

Jan H. schrieb:

wie castet man den funktionszeiger richtig?

Es spricht nichts dagegen, Unions, void-Rückgabewerte oder irgendwas zu verwenden. Casten ist aber auch möglich. Am einfachsten mit Typedefs:

1
typedef float    (*pFloatFncType)(void);
2
typedef uint8_t  (*pu8FncType)(void);
3
typedef void     (*pVoidFncType)(void);
4

5
float read_bat_voltage_avg(void);
6
uint8_t send_bat(void);
7
typedef struct{
8
   char id;
9
   pVoidFncType fnc;
10
}lora_cmd_t;
11

12
lora_cmd_t lora_cmd[] =
13
{
14
   { '0', (pVoidFncType) read_bat_voltage_avg }, 
15
   { '1', (pVoidFncType) send_bat }, 
16
};
17

18
lora_cmd_t cmd = lora_cmd[x];
19
float f;
20
uint8_t u8;
21

22
   switch(cmd.id)
23
   {
24
   case 0: f = ((pFloatFncType) cmd.fnc)(); 
25
           break;
26
   case 1: u8 = ((pu8FncType) cmd.fnc)(); 
27
           break;
28
   }

Mag sein, dass ich ein * oder sowas vergessen habe, aber prinzipiell geht das. (pVoidFncType ist nicht notwendig, aber vielleicht ein Hinweis, dass man in jedem Fall den richtigen cast auswählen muss)

#7815809
Lesenswert?

Rocco F. schrieb:

Wenn die Rückgabewerte jedoch unterschiedlich sind, kann dies wirklich zu einem Problem werden.

Darum dreht es sich hier doch die ganze Zeit, von Anfang an.

Das ist das, was an der Idee hier kaputt ist. Nicht die Verwendung von Funktionspoinern, sondern die Verwendung von Funktionspointern mit /inkompatibler/ Funktionssignatur.

Und bislang wurde auch noch nicht erwähnt, welchen tieferen Sinn es haben soll, daß die verwendeten Funktionen unterschiedliche Rückgabetypen haben.

So viel Aufwand, wie hier darauf gelegt wurde, Krücken zu basteln, um diese kaputte Idee irgendwie zu ermöglichen, so WENIG Aufwand wurde vom Threadstarter betrieben, auch nur einen einzigen Erklärungsversuch zu bringen. Mehr als "ist so" kam nicht.

#7815971
Lesenswert?

Harald K. schrieb:

Nicht die Verwendung von Funktionspoinern, sondern die Verwendung von Funktionspointern mit inkompatibler Funktionssignatur.

Ein guter Einsatz von Funktionspointern ist, wenn beim Init oder Konfigurieren (oder in der Statemachine) die (nächste) Funktion gesetzt wird und der Aufrufer einfach den Ptr aufruft.

Wenn (wie hier) ein Eintrag aus einer Liste gewählt wird, dann ist das aufwändiger und nicht mehr sooo smart. Auf den case für den Cast kommt es dann auch nicht mehr an. Meist kann das ganze Konstrukt durch plain Code vereinfacht werden.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren