wie castet man den funktionszeiger richtig? es gibt verschiedene funktionszeiger also verschiedene rückgabetypen..
1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
Danke schon mal =)
|
Anzeige
|
c > struct > funktionszeiger castenwie castet man den funktionszeiger richtig? es gibt verschiedene funktionszeiger also verschiedene rückgabetypen..
Danke schon mal =)
Casten? Gar nicht! Ein solcher Zeiger sollte Typsicher sein. Also nix mit cast!
was könnte ich stattdessen machen, wenn dort es dort mehrere rückgabetypen gibt? Moin,
Wuerde ich garnicht machen. Ich halt's nicht fuer klug, dem Compiler erst zu versichern, dass eine Funktion so rein ueberhaupt garnix zurueckgibt, und dann tut sie's ploetzlich doch, und dann auch noch verschiedenes... Gruss WK Ansonsten, wenn wirklich, dann den Umweg über void zeiger.
Oder was mit union basteln.
ich bin für vorschläge offen, wie würdet ihr das machen?
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.
Sag doch erstmal wie du den Zeiger aufrufen und dabei den Rückgabewert nutzen willst... Moin,
Bau wrapper um deine Funktionen, die verschiedene Typen zurueckgeben, die die verschiedenen Typen auf einen Typen entsprechend wandeln. Diese Wrapperfunktionen kannste dann ueber fktptr aufrufen. Gruss WK Hey, du willst zu einem Funktionspointer casten und nicht einfach zum Rückgabetyp. Also nach (void ()(void)) und beim Aufruf der Funktion nach ((T ()(void)) Gruß emko
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.
was soll "T" sein? :-O
Damit haben send_bat_wrapper() und read_bat_voltage_avg() die selbe Geschmacksrichtung von Funktionspointer:
Gruss WK
Dann solltest du aber deine Datenstruktur überdenken und nicht versuchen alles mit nur einem Funktionspointer zu lösen.
T ist ein Platzhalter für den Rückgabetyp der Funktion die aufgerufen wird.
Gast
#7813634
Warum nimmst du nicht einfach einrn switch-case? Gute programmierer setzen solche Konstrukte nur ein, wenn sie wirklich notwendig sind.
Das ist soweit klar, warum aber sollen all' diese Funktionen einen /unterschiedlichen/ Rückgabewert haben?
die beiden haben aber nicht die gleiche "geschmacksrichtung" das eine ist uint8_t und das andere float ;)
also bis jetzt haben nur zwei funktionen float rückgabewerde die anderen werden wohl uint8_t haben FÜr sowas sind Unions gemacht. Da kann man alle Funktionszeigertypen drin definieren und dann über das richtige Element die Funktion aufrufen:
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.
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. 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(); }
Wie genau soll da ein void nach float gecastet werden?
Ok.... Danke! Die Zeiger casterei ist mir sowieso suspekt! Und dank C++ auch meist vermeidbar.
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:
Oder man nimmt eben die unions und spart sich die ganze Cast-Orgie.
Unter POSIX-Systemen ist es erlaubt, dies ist für die Funktion dlsym() erforderlich. Aber C und C++ an sich garantieren das nicht.
Zwischen Funktionszeigern hin-und her-casten ist erlaubt, aber aufrufen darf man ihn nur über den "richtigen" (ursprünglichen) Typ.
Ja, wobei man sich natürlich fragt: Wenn man sowieso ein switch-case für den Rückgabetyp braucht - wozu dann noch der Funktionszeiger?
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.
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.
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
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.
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.
Nicht so gut wie die Tabelle meiner Meinung nach.
Jau:
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. 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.
Evtl. wäre es notwendig einen pointer auf ein struct return_typ als Ergebnis zu liefern.
Kriegt man das nicht besser mit Generic selection hin? Da muss man den Wrappercode halt für jeden Typ einzeln schreiben (statt mit dem template in C++), aber es spart die Laufzeit-Prüfung und das Verpacken/Auspacken im struct.
Dann kann ich aber nicht mit meiner "Tabelle" weiterarbeiten?
Doch, du musst die union halt statt des Funktionszeigers in die Tabelle eintragen.
Gast
#7813887
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.
ich habe das mal so in den online debugger gehauen und der wirft mir da fehler, siehe foto.
Ja es muss so sein:
Okay dann verstehe ich es. 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.
Zeig die Fehlermeldung. Es müsste eigentlich so gehen.
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.
... die kaputte Designentscheidung über den Haufen zu werfen, Funktionen mit unterschiedlicher Signatur gemischt verwenden zu wollen.
Warum nur?
Gast
#7814008
Man kann Werte von 0-256 durchaus auch als float zurück geben.
Nach dem Motto: Der Code ist schlecht, lasst uns ihn noch schlechter machen! In reinem C könnte man es mit _Generic so machen:
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
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. So ginge es in C++ mit einer std::variant:
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. 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.
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. Wie dem auch sei, wie wäre es mit einer tagged Union?
Genau das hat Nikolaus oben schon so implementiert.
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:
Ist das mit einer Union und Funktionszeiger-Tabelle so viel effizienter? Oder so viel übersichtlicher? 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.
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. 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.
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.
Es spricht nichts dagegen, Unions, void-Rückgabewerte oder irgendwas zu verwenden. Casten ist aber auch möglich. Am einfachsten mit Typedefs:
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)
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.
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 schreibenBitte melde dich an, um einen Beitrag zu schreiben. |
Anzeige
|