Forum: Mikrocontroller und Digitale Elektronik Funktionszeiger und Funktionen mit unterschiedlich vielen Argumenten


von Thomas Frosch (Gast)


Lesenswert?

Hi Leute,

ich benötige einen Funktionszeiger, der auf Funktionen zeigen kann, die 
entweder 2 oder 3 Argumente haben.

Habe es schon so versucht, geht aber nicht.

void (*ArgFu_pf)(uint8 value1, ...);

Es wird durch eine weitere Abfrage sichergestellt, dass die Funktion 
immer richtig aufgerufen wird, allerdings erst zur Laufzeit.

Wie muss ich den Zeiger schreiben, damit dies Funktioniert?

von Düsendieb (Gast)


Lesenswert?

bei einem µC?

von Karl H. (kbuchegg)


Lesenswert?

Thomas Frosch schrieb:
> Hi Leute,
>
> ich benötige einen Funktionszeiger, der auf Funktionen zeigen kann, die
> entweder 2 oder 3 Argumente haben.
>
> Habe es schon so versucht, geht aber nicht.
>
> void (*ArgFu_pf)(uint8 value1, ...);

Ähm nein.
Das ist ein Pointer auf eine variadische Funktion. D.h. die Funktion 
selber muss auch variadisch sein. Du kannst diesem Pointer nicht einmal 
eine Funktion mit 2 Argumenten und ein anderes mal mit 3 Argumenten 
zuweisen.

So spielt es das nicht.

> Wie muss ich den Zeiger schreiben, damit dies Funktioniert?

Du musst dein Konzept ändern.

von Stefan E. (sternst)


Lesenswert?

3 Typedefs machen, einen generischen Funktionspointer und die beiden 
konkreten Varianten. Speichern dann als generischen Pointer, und beim 
Aufruf einen Type-Cast benutzen.

von Thomas Frosch (Gast)


Lesenswert?

Könntest du das genauer ausführen? Typ-Cast hört sich zwar sehr gut an, 
aber wie muss ich die deklaration des Zeigers machen?

von Stefan E. (sternst)


Lesenswert?

Thomas Frosch schrieb:
> aber wie muss ich die deklaration des Zeigers machen?
1
typedef void (*MyBrandNewSelfMadeGenericPointerType_t) (void);
2
3
MyBrandNewSelfMadeGenericPointerType_t ArgFu_pf;

von Karl H. (kbuchegg)


Lesenswert?

Thomas Frosch schrieb:
> Könntest du das genauer ausführen? Typ-Cast hört sich zwar sehr gut an,
> aber wie muss ich die deklaration des Zeigers machen?

Genauso wie jetzt auch.
Nur dass du typedef davor schreibst und der 'Variablenname' damit zum 
Namen deines neuen Datentyps wird.

typedef int (* IntFnct)( long arg1, double arg2 );


IntFunc ist ein neuer Datentyp (so wie auch int oder double ein Datentyp 
ist), der ein Funktionspointer ist auf eine Funktion mit genau dieser 
STruktur.

Und so wie du mit

   int i;

eine Variable i vom Typ int anlegst, legst du eben mit

  IntFnc funcPtr;

eine Variable namens funcPtr an, die vom Datentyp "Pointer auf Funktion, 
die einen long und einen double nimmt und einen int retourniert" an.

von Stefan (Gast)


Lesenswert?

Hallo,

geht das ? Ist auch noch typesicher.

typedef void (*tfnp2) (int,int);
typedef void (*tfnp3) (int,int,int);

union vproc {
  tfnp2 p2;
  tfnp3 p3;
};

typedef union vproc   tfnpx;


void fn2 (int a, int b) {

}


void fn3 (int a, int b,int c) {

}


int main(int argc, char* argv[]) {
tfnpx   pp;

pp.p2 = fn2;
pp.p2(1,2);

pp.p3 = fn3;
pp.p3(1,2,3);

return 0;
}

Stefan

von Markus B. (markus_b95)


Lesenswert?

Was heißt 2 oder 3 Argumente haben? Interessant wäre, ob die besagte 
Funktion neben den 2/3 übergebenen Argumenten auch welche zurückliefert.
Davon ausgehend, das sie einen int zurückliefert und 2 int als argumente 
erwartet, sähe es dann so aus:
1
int (*funktionspointer) (int, int); //Deklariert den Funktionspointer
2
3
... //Andere Definitionen, Befehle, etc.
4
5
int testfunction_add(int *a,int *b)
6
{
7
    int c = 0; //kleine Hilfsvariable
8
    c = *a + *b; //addiere a zu b
9
    return c;
10
}
11
12
funktionspointer = &testfunction_add; /*Zeige mit dem Pointer auf die add-Funktion/*
13
14
//Aufruf der Funktion mittels des Pointers:
15
int ergebnis = (*funktionspointer) (& int-Argument_1, & int-Argument_2);

Obigen Code habe ich eben mal schnell aus dem Gedächtnis in der Mensa 
getippt, keine Garantie auf Funktionstüchtigkeit, aber sofern ich die 
Informatik-Vorlesung noch richtig in Erinnerung habe, sollte das 
Grundkonzept so funktionieren.

P.s. weil ich's grad sehe...klar gibt die Funktion nur einen Wert 
zurück, wüsste nicht das es in C etwas anderes gäbe

edit und gut ja, c hätte man auch per Referenz zurückgeben können aus 
der Funktion, aber ist jetzt nebensächlich

von Stefan E. (sternst)


Lesenswert?

Markus Burger schrieb:
> Was heißt 2 oder 3 Argumente haben?
> ...
> Obigen Code habe ich eben mal schnell aus dem Gedächtnis in der Mensa
> getippt, keine Garantie auf Funktionstüchtigkeit, aber sofern ich die
> Informatik-Vorlesung noch richtig in Erinnerung habe, sollte das
> Grundkonzept so funktionieren.

Du hast die Frage nicht richtig verstanden. Er möchte einen 
Funktionszeiger haben, der sowohl auf Funktionen mit 2 Argumenten, als 
auch auf Funktionen mit 3 Argumenten zeigen können soll.

von cskulkw (Gast)


Lesenswert?

So wird im ASF vom AVR32 auf ISR-Funktionen gesprungen.

Ist ne Klasse Methode, um Devicetreiber von abstrahierter Funktionalität 
zu trennen.

Beispiel:

UART-Empfangen, um Datenstrom nach Kommandos zu durchsuchen. So etwas 
wie ein Befehlsinterpreter. Befehle interpretieren brauncht nicht 
zwingend mit einer UART oder sonst irgendeiner 
Kommunikationsschnittstelle verbunden werden.

Die Komponente Befehlsinterpreter ist es gleich, von woher die Daten der 
UART kommen.

Man kann so, relativ unkompliziert von der Hardware unabhängig 
Funktionen programmieren.

Der Clou:

Benutzt man Funktionspointer im RAM, kann man während der Laufzeit die 
Pointer ummappen. Das sogenannte "late binding". Und das geht sogar auf 
dem AVR-8Bitter. Ich habe so mein Mini-OS programmiert.

Wer das nicht will, legt die Pointervariablen in den Flash und nutzt nur 
die syntaktischen Vorteile während des Builds.

von Karl H. (kbuchegg)


Lesenswert?

Stefan schrieb:
> Hallo,
>
> geht das ?

Ja ist ok.
Wenn du jetzt noch eine Struct drüberlegst, in der du noch einen Member 
hast, der dir sagt, welcher der beiden Pointerdatentypen der richtige 
ist, dann ist es richtig typsicher und die verwendende Stelle braucht 
noch nicht einmal wissen, welcher der beiden Pointer es ist, weil sie es 
in den Daten bestimmen kann

#define ARG2  0
#define ARG3  1

struct fnct
{
  uint8_t type;   // 0 = 2 Argumente, 1 = 3 Arumgente
  vproc   fncPtr;
}

void foo( struct fnct* Function )
{
  if( Function->type == ARG2 )
    Function->fncPtr.p2( 2, 3 );

  else if( Funtion->type == ARG3 )
    Function->fncPtr.p3( 2, 3, 4 );
}

von Thomas Frosch (Gast)


Lesenswert?

Sorry, checks nicht mit der typedef Variante. Ich weiss zwar was typedef 
macht etc, aber wie ich das nun genau schreiben muss bekomme ich nicht 
hin.

Habe ... versucht.
1
typedef void (*CanIf_DynamicFunctionPointer_Type)(void);
2
typedef void (*CanIf_3ArgFunctionPointer_Type)(uint8, uint8, uint8);
3
typedef void (*CanIf_2ArgFunctionPointer_Type)(uint8, uint8);
4
5
CanIf_DynamicFunctionPointer_Type argFu;
6
7
8
void ArgFu3(uint8 value1, uint8 value2, uint8 value3)
9
{
10
    printf("ArgFu3. Value = %d \n\n", value3);
11
    return;
12
}
13
14
void ArgFu2(uint8 value1, uint8 value2)
15
{
16
    printf("ArgFu2. Value = %d \n\n", value2);
17
    return;
18
}
19
20
...
21
22
argFu = (CanIf_DynamicFunctionPointer_Type)&ArgFu2;
23
24
{
25
                CanIf_2ArgFunctionPointer_Type argFu2 = (CanIf_2ArgFunctionPointer_Type)argFu;
26
                argFu2(1,2);
27
}

Es tritt aber anscheinend ein Speicherzugriffsproblem auf. Ich teste im 
moment auf einem Windows Rechner. Später kommt das auf nen µC. Ich teste 
einfach Codeschnipsel in einer Dialogbasierten Win32 Anwendung.
Programm läuft durch und macht die Ausgabe. Dann wird angezeigt, dass 
die .exe ein Problem festgestellt hat und beendet werden muss.

von Karl H. (kbuchegg)


Lesenswert?

Thomas Frosch schrieb:
> Sorry, checks nicht mit der typedef Variante. Ich weiss zwar was typedef
> macht etc, aber wie ich das nun genau schreiben muss bekomme ich nicht
> hin.

Zeig mal dein komplettes, compilierbares Testbeispiel

von Karl H. (kbuchegg)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Thomas Frosch schrieb:
>> Sorry, checks nicht mit der typedef Variante. Ich weiss zwar was typedef
>> macht etc, aber wie ich das nun genau schreiben muss bekomme ich nicht
>> hin.
>
> Zeig mal dein komplettes, compilierbares Testbeispiel

Argh, zu spät gelesen

> Ich teste einfach Codeschnipsel in einer Dialogbasierten Win32 Anwendung.


Irgendwo hast du etwas falsch gemacht. Ist aber mit diesen Schnipseln 
nicht erkennbar.

von Markus B. (markus_b95)


Lesenswert?

Stefan Ernst schrieb:
> Markus Burger schrieb:
>> Was heißt 2 oder 3 Argumente haben?
>> ...
>> Obigen Code habe ich eben mal schnell aus dem Gedächtnis in der Mensa
>> getippt, keine Garantie auf Funktionstüchtigkeit, aber sofern ich die
>> Informatik-Vorlesung noch richtig in Erinnerung habe, sollte das
>> Grundkonzept so funktionieren.
>
> Du hast die Frage nicht richtig verstanden. Er möchte einen
> Funktionszeiger haben, der sowohl auf Funktionen mit 2 Argumenten, als
> auch auf Funktionen mit 3 Argumenten zeigen können soll.

Okay ja da hast du Recht, Missverständnis :)

von Thomas Frosch (Gast)


Lesenswert?

Oh ihr habt recht, als ich das File nun sauber gemacht habe, um es 
hochzuladen ist der Fehler verschwunden.

Funktioniert jetzt!

Allerdings muss ich es jetzt noch hinbekommen, den cast wegzubekommen 
bzw schon vorher festzulegen.

Ich habe eine Konfigurationsstruktur, die ich dann initialisiere. Der 
jenige der diese Struktur initialisiert soll nicht jedesmal den cast 
davor schreiben müssen. Gibt es da nun auch noch irgendeine Möglichkeit?

Also an die Stelle "element" soll in der Konfiguration einfach ein 
Funktionszeiger auf eine Funktion mit 3 oder 2 Argumenten hinkommen.
1
typedef struct
2
{
3
    CanIf_DynamicFunctionPointer_Type element;
4
    uint8 x;
5
} CanIf_RxPduConfig_SType;
6
7
8
CanIf_RxPduConfig_SType canIf_rxPduConfig_s
9
{
10
    (CanIf_DynamicFunctionPointer_Type)&ArgFu2,
11
    0
12
};

von Karl H. (kbuchegg)


Lesenswert?

Thomas Frosch schrieb:

> Allerdings muss ich es jetzt noch hinbekommen, den cast wegzubekommen
> bzw schon vorher festzulegen.

Scroll ein bischen hoch.
Da hab ich mal skizziert, wie man das Typsicher macht, wenn man noch ein 
paar Setzfunktionen zum Befüllen der Struktur mit dazunimmt.

> Ich habe eine Konfigurationsstruktur, die ich dann initialisiere. Der
> jenige der diese Struktur initialisiert soll nicht jedesmal den cast
> davor schreiben müssen. Gibt es da nun auch noch irgendeine Möglichkeit?

Schreib dir Funktionen dafür. Das hat dann auch den Vorteil, dass der 
Compiler überwacht, ob du auch die richtige Funktion in die struct 
stopfst oder nicht.

von Stefan (Gast)


Lesenswert?

@kbuchegg Schön das ich auch Dir helfen konnte.
Stefan

von Thomas Frosch (Gast)


Lesenswert?

Es wird eine Datei geben, in der ein Array dieser Struktur drin ist. 
Dort wird dann zig mal irgendwelche Funktionszeiger eingetragen. Später 
eventuell sogar mit Konfigurationstool.

Will ein Programmieren anderer total verhindern. Es sollen alle 
Einstellungen rein in der Struktur geschehen.

Dein Beispiel mit der Union finde ich sehr gut, jedoch funktioniert das 
Initialisieren nicht so toll.
1
CanIf_RxPduConfig_SType canIf_rxPduConfig_s
2
{
3
    0,
4
    &ArgFu2
5
6
};

Funktioniert! Aber nur mit Fehlermeldung, dass Klammern fehlen.
1
CanIf_RxPduConfig_SType canIf_rxPduConfig_s
2
{
3
    1,
4
    &ArgFu3
5
};

Funktioniert nicht. Er meckert wieder, wegen unterschiedlicher 
Argumentenzahl. Ich müsste also irgendwie angeben, dass er dies in p3 
schreiben soll. Dann kann ich aber auch wieder zwei getrennte Variablen 
nehmen.

von Karl H. (kbuchegg)


Lesenswert?

Was ich noch sagen wollte.

In solchen Fällen pfeift man auch oft auf die Unterschiede und definiert 
ganz einfach, das alle derartigen Callback-Funktionen die gleiche 
Argumentliste haben müssen. Dann machen halt einige Funktionen nix mit 
ihren übergebenen Werten, was solls. Die Unterscheidung, welche Funktion 
dann tatsächlich aufgerufen werden soll, kostet auch Zeit, so dass das 
übergeben von ein paar Werten an eine Funktion, die nix damit macht real 
gesehen auch nicht langsamer ist.

Dafür muss man aber keine Cast-Orgien veranstalten und auch sonst keine 
Sonderfälle bauen und der Compiler kann alles soweit überprüfen, dass 
man keinen Fehler machen kann.

von Stefan (Gast)


Lesenswert?

So ?

typedef void (*tfnp2) (int,int);
typedef void (*tfnp3) (int,int,int);

union vproc {
  void* p;
  tfnp2 p2;
  tfnp3 p3;
};

typedef union vproc   tfnpx;


struct iv {
  int   a;
  int   b;
  int   c;
  tfnpx p;
};


void fn2 (int a, int b) {
  printf ("%d %d\n" , a,b);
}


void fn3 (int a, int b,int c) {
  printf ("%d %d %d\n" , a,b,c);
}


struct iv INIT[] = {
  {1,2,0,fn2},
  {5,6,7,fn3},
};


void call (struct iv* n) {
  if (n->p.p == fn2) {
    fn2(n->a,n->b);
    }

  else if (n->p.p == fn3) {
    fn3(n->a,n->b,n->c);
    }
};

int main(int argc, char* argv[]) {

call (&INIT[0]);
call (&INIT[1]);

return 0;
}

Stefan

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.