Forum: Compiler & IDEs Menu LCD Pointer - Funktionsaufruf


von Stier (Gast)


Lesenswert?

Hallo,

bin an einem Menü mit Pointern dran, dass switchen im Menü funktioniert 
schon sehr gut. Was bei mir noch nicht klappt ist, dass er die 
Funktionen nicht aufruft.
1
...
2
typedef struct def_menustruktur {
3
    const int8_t *text;
4
    int8_t prev;
5
    int8_t next;
6
    int8_t u_prev;
7
    int8_t u_next;
8
    void (*funktion)(void);
9
} menustruktur;
10
...
11
void menu_verlassen(void);
12
13
const uint8_t m_100[] PROGMEM = "1. Menu";
14
const uint8_t m_200[] PROGMEM = "2. Menu";
15
const uint8_t m_210[] PROGMEM = "2.1 Menu";
16
const uint8_t m_220[] PROGMEM = "2.2 Menu";
17
const uint8_t m_300[] PROGMEM = "3. Verlassen";
18
19
menustruktur test_m[] PROGMEM = {
20
    {m_100, 4, 1, 0, 0},
21
    {m_200, 0, 4, 3, 2},
22
        {m_210, 3, 3, 2, 2},
23
        {m_220, 2, 2, 3, 3},
24
    {m_300, 1, 0, 4, 4, menu_verlassen}
25
}
26
27
void menu_verlassen(void) {
28
    menuAktiv = false;
29
    lcd_clear();
30
}

Funktionsaufruf mache ich so:
1
...
2
state = pgm_read_word(&test_m[state].prev);
3
...
4
pgm_read_word(&test_m[state].funktion)

Wo ist mein Denkfehler?

von Klaus W. (mfgkw)


Lesenswert?

Mit einer vernünftigen Beschreibung und ORIGINAL-Quelltext
könnte man aus dem Problem vielleicht noch etwas machen.

von Stier (Gast)


Lesenswert?

naja ich möchte mit

pgm_read_word

die Funktion aufrufen die in m_300[] hinterlegt ist.

von Klaus W. (mfgkw)


Lesenswert?

pgm_read_word(&test_m[state].funktion) liefert dir bestenfalls
einen Zeiger auf eine Funktion, ruft sie aber sicher nicht auf.
Dazu fehlt noch der cast auf den richtigen Typ (Zeiger auf
Funktion, die <parameterliste> bekommt und <Rückgabetyp> liefert)
sowie die runden Kalmmern, die ein Funktionsaufruf braucht.

von Klaus W. (mfgkw)


Lesenswert?

Außer bei dem letzten Eintrag fehlen übrigens die Zeiger auf
die Funktionen, und überhaupt ist der Quelltext doof zu lesen.
Mies formatiert, unvollständig, nicht kompilierbar.

von Stier (Gast)


Lesenswert?


von Klaus W. (mfgkw)


Lesenswert?

Na und?
Die Bruchstücke dort funktionieren für sich alleine auch nicht.
Was soll mir der Link sagen?

Du musst dich schon mit C beschäftigen, wenn du mit
Funktionszeigern arbeiten willst, und nicht nur irgendwelche
Fragmente zusammenkopieren.

von Stier (Gast)


Lesenswert?

Klaus Wachtler schrieb:
> Mies formatiert, unvollständig, nicht kompilierbar.

Mies formatiert?
unvollständig -> ja ist richtig, gut erkannt
nicht kompilierbar -> richtig, da er ja nicht vollständig ist

hat schon einen Grund warum ich nicht alles gepostet habe!

Klaus Wachtler schrieb:
> Was soll mir der Link sagen?
Klaus Wachtler schrieb:
> ORIGINAL-Quelltext

von Klaus W. (mfgkw)


Lesenswert?

Wenn in den paar Zeilen von dir schon 2 Semikolon fehlen, von
verwendeten Variablen keine Definition oder Initialisierung zu
sehen ist, und als Fehlerbeschreibung nur ein "geht nicht",
dann brauche ich doch gar nicht groß Fehler zu suchen.

Wenn es zu groß ist zum Posten, dann liegt es an dir, das
Ganze auf ein kleines übersichtliches Beispiel zu
reduzieren, zu dem man dann Fragen beantworten kann.

Abgesehen davon, daß meine Aussage nach wie vor steht:
in der letzten gezeigten Zeile hast du nur ein Word,
das erst gecastet werden muß und dann mit Klammern erst
einen Funktionsaufruf gibt.
Wenn dann auch noch ein Semikolon dahinter spendiert wird...

Tip:
So etwas kann man am PC wesentlich einfacher erst mal üben.
Wenn man dann die Zeiger auf Funktionen verstanden hat,
packt man es in einen MC, in den man nicht so gut reinschauen
kann zw. Debuggen.

Das spart dir und uns viel Zeit.
Es gibt hier etliche Leute, die gerne helfen. Mich eingeschlossen.
Aber seine Zeit hat keiner gestohlen, also kannst du auch den
Hintern hochkriegen und nicht einfach irgendwas zusammenkopieren
und hier dann fertigstellen lassen.

Den Hauptfehler habe ich dir doch schon gesagt.

von Klaus W. (mfgkw)


Lesenswert?

Stier schrieb:
> Mies formatiert?

naja, es gibt viel schlimmere.

von Stier (Gast)


Lesenswert?

(uint8_t)pgm_read_word(&test_m[state].funktion);

was du mit Funktionsklammern meinst verstehe ich nicht, wenn du das 
meinst:

(uint8_t)pgm_read_word(&test_m[state].funktion());

ist das Falsch.

von Klaus W. (mfgkw)


Lesenswert?

ja, das ist falsch.
Du musst erst in den Typ "Zeiger auf Funktion ohne Parameer und
void-Rückgabewert" casten, dann Klammern drumrum und das Ganze
dann mit einem Klammerpaar dahinter aufrufen.

von Stier (Gast)


Lesenswert?

(void)pgm_read_word(&test_m[state].funktion)();

das geht auch nicht! Und was du sonst noch meinen könntest, fehlt mir 
nix mehr ein!

von Klaus W. (mfgkw)


Lesenswert?

Ich habe jetzt keine Lust, einen AVR dafür auszupacken.
Aber auf einem PC kann man das wie gesagt auch schön simulieren.
Da sind Zeiger nur halt 4 Byte groß und deshalb nehme ich eine uint32_t:
1
#include <stdio.h>
2
#include <stdint.h>
3
4
5
void f()
6
{
7
  printf( "hi\n" );
8
}
9
10
int main( int nargs, char **args )
11
{
12
  uint32_t    fi = (uint32_t)f;
13
14
  ((void(*)())fi)(); // Aufruf von f über den Zeiger bzw. die ganze Zahl fi
15
16
  return 0;
17
}

fi ist jetzt uneleganterweise eine ganzzahlige Variable, das
entspricht bei dir dem uint16_t-Ausdruck
pgm_read_word(&test_m[state].funktion).

fi ist also formal nur eine ganze Zahl (auch wenn ich weiß,
daß sich dahinter in Wirklichkeit die Adresse einer Funktion
verbirgt).

Aus dieser ganzen Zahl muß jetzt für den Compiler erstmal
ein Zeiger auf eine Funktion gemacht werden.
Dazu schreibt man den richtigen Typ in Klammern davor,
gemeinhin als cast bekannt (ähnlich wie ich bei der Initialisierung
von fi aus der Adresse der Funktion f mit (uint32_t) davor
eine ganze Zahl gemacht hatte, nur jetzt halt in die andere
Richtung).

Was ist der richtige Typ?
Das wäre in Worten "Zeiger (also: *) auf eine Funktion
(deshalb die runden Klammern am Ende), die nichts bekommt
(deshalb die runden Klammern am Ende leer) und nichts
zurückliefert (deshalb void vornedran)".
In C:
  void(*)()
Das wäre also der Typ, in den gecastet werden muss.
Das erste Klammerpaar um * ist nötig wegen der
Auswertungsreihenfolge, aus void und * würde der Compiler
sonst einen Typ void* machen und mit dem Rest dann nicht mehr
klar kommen.

Dieser Typ in Klammern vor den uint16_t- bzw. uint32_t-
Ausdruck macht aus der ganzen Zahl den Zeiger auf die Funktion:
  (void(*)())fi

Das ist insoweit erstmal nur die Adresse der Funktion, aber noch
kein Aufruf.
Dazu muß man das bisherige Gebilde klammern (wieder wegen der 
Auswertungsreihenfolge):
  ((void(*)())fi)

und -jetzt kommt's- mit einem Klammerpaar dahinter die
Funktion aufrufen:
  ((void(*)())fi)()

Sieht etwas kryptisch aus, aber ist halt C.

von Klaus W. (mfgkw)


Lesenswert?

Analog:
    exit;
ruft die Funktion exit nicht auf, sondern nimmt nur ihre Adresse
und macht nichts damit.

So dagegen:
    exit();
wird sie auch aufgerufen.

von Stier (Gast)


Lesenswert?

wow danke für deine Erklärung. Finde deine Formierung aber auch nicht 
gerade Optimal!

von Stier (Gast)


Lesenswert?

mal eine andere Frage:

Wieso vergleicht er bei mir nicht zwei uint8_t miteinander?
1
typedef struct {
2
    uint8_t sec;
3
    uint8_t minute;
4
    uint8_t hour;
5
} time_t;
6
7
time_t time0;
8
time_t time1;
9
10
time0.minute = 5;
11
time0.hour = 2;
12
time1.minute = 5;
13
time0.hour = 2;
14
15
if(time0.hour == time1.hour && time0.minute == time1.minute) {
16
    // tue was
17
}

von Stier (Gast)


Lesenswert?

ok eigener Fehler funktioniert doch

von Klaus W. (mfgkw)


Lesenswert?

erstens macht man für eine neue Frage einen neuen Thread auf,
zweitens gefällt die meine Formierung nicht, und
drittens soll das letzte time0.hour = 2 wohl eher time1.hour = 2 heißen?

von Stier (Gast)


Lesenswert?

Klaus Wachtler schrieb:
> erstens macht man für eine neue Frage einen neuen Thread auf,
> zweitens gefällt die meine Formierung nicht, und
> drittens soll das letzte time0.hour = 2 wohl eher time1.hour = 2 heißen?

1. Wollte ich das Forum hier nicht zu müllen
2. Genau deine Formierung find ich nicht toll, aber was hat es mit der 
Frage zu tun?
3. Sollte es

von Stier (Gast)


Lesenswert?

1
menustruktur test_m[] PROGMEM = {
2
    {m_100, 4, 1, 0, 0},
3
    {m_200, 0, 4, 3, 2},
4
        {m_210, 3, 3, 2, 2},
5
        {m_220, 2, 2, 3, 3},
6
    {m_300, 1, 0, 4, 4, test(bla)}
7
}
8
9
int main( int nargs, char **args )
10
{
11
  uint32_t    fi = (uint32_t)f;
12
13
  ((void(*)())fi)(); // Aufruf von f über den Zeiger bzw. die ganze Zahl fi
14
15
  return 0;
16
}

wie genau funktioniert es, wenn ich Parameter an der Funktion habe? test 
hat parameter bla, wie genau ändert sich der Aufruf der Funktion?

von Karl H. (kbuchegg)


Lesenswert?

Stier schrieb:
>
1
> menustruktur test_m[] PROGMEM = {
2
>     {m_100, 4, 1, 0, 0},
3
>     {m_200, 0, 4, 3, 2},
4
>         {m_210, 3, 3, 2, 2},
5
>         {m_220, 2, 2, 3, 3},
6
>     {m_300, 1, 0, 4, 4, test(bla)}
7
> }
8
> 
9
> int main( int nargs, char **args )
10
> {
11
>   uint32_t    fi = (uint32_t)f;
12
> 
13
>   ((void(*)())fi)(); // Aufruf von f über den Zeiger bzw. die ganze Zahl
14
> fi
15
> 
16
>   return 0;
17
> }
18
>
>
> wie genau funktioniert es, wenn ich Parameter an der Funktion habe? test
> hat parameter bla, wie genau ändert sich der Aufruf der Funktion?

Was dir bis jetzt noch keiner gesagt hat:
Mach dir um Himmels Willen einen typedef für den Datentyp des 
Funktionspointers. Ansonsten bringt dich die komplexe Syntax um!

Die wichtigsten Dinge über Funktionspointer findest du hier
http://www.mikrocontroller.net/articles/FAQ#Funktionszeiger
zusammengestellt. Und natürlich auch ausführlich in deinem C-Buch.

von Stier (Gast)


Lesenswert?

Leider stehe ich etwas auf dem Schlauch:
1
typedef struct def_menustruktur {
2
    const int8_t *text;
3
    int8_t prev;
4
    int8_t next;
5
    int8_t u_prev;
6
    int8_t u_next;
7
    void (*funktion)(void);
8
} menustruktur;

Hier definiere ich das die Funktionen bei

{m_300, 1, 0, 4, 4, test}

keine Argumente enthält. Doch einige Funktionen brauchen ein Argument. 
Ich kann ja nicht void (*funktion)(void); einfach ändern.

von Karl H. (kbuchegg)


Lesenswert?

Stier schrieb:

> Hier definiere ich das die Funktionen bei
>
> {m_300, 1, 0, 4, 4, test}
>
> keine Argumente enthält. Doch einige Funktionen brauchen ein Argument.
> Ich kann ja nicht void (*funktion)(void); einfach ändern.

Das kannst du nicht nur, das musst du sogar.
Im verlinkten Artikel steht ausdrücklich: Alle Funktionen müssen 
dieselbe Signatur haben.

Wenn also einige Funktionen ein Argument brauchen, dann musst du deinen 
Funktionspointer darauf auslegen. Das bedeutet allerdings dann auch, 
dass alle Funktionen ein Argument annehmen müssen, auch die die 
eigentlich keines brauchen. Das ist aber auch kein großes Problem, wenn 
sie ein Argument mitbekommen und einfach nichts damit tun.

von Stier (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> Wenn also einige Funktionen ein Argument brauchen, dann musst du deinen
> Funktionspointer darauf auslegen. Das bedeutet allerdings dann auch,
> dass alle Funktionen ein Argument annehmen müssen, auch die die
> eigentlich keines brauchen. Das ist aber auch kein großes Problem, wenn
> sie ein Argument mitbekommen und einfach nichts damit tun.

ok habs nun verstanden

von Karl H. (kbuchegg)


Lesenswert?

Stier schrieb:
> Karl heinz Buchegger schrieb:
>> Wenn also einige Funktionen ein Argument brauchen, dann musst du deinen
>> Funktionspointer darauf auslegen. Das bedeutet allerdings dann auch,
>> dass alle Funktionen ein Argument annehmen müssen, auch die die
>> eigentlich keines brauchen. Das ist aber auch kein großes Problem, wenn
>> sie ein Argument mitbekommen und einfach nichts damit tun.
>
> ok habs nun verstanden

Alternativ kann man sich natürlich auch immer noch die Dinge 
zurechtcasten wie man sie braucht, aber ich warne ausdrücklich davor. 
Sowas führt ganz schnell ins Chaos und zu schwer zu findenden Fehlern.

Eine andere Möglichkeit wäre noch zb. den Mechanismus mit dem an main 
eine beliebige Anzahl an Argumenten übergeben wird zu kopieren. 
Inwiefern das bei einer Handvoll Funktionen mit einer überschaubaren 
Anzahl an Parametern sinnvoll ist, sei jetzt erst mal dahingestellt.

Und dann gibt es ja auch noch globale Variablen mit denen man einen 
Argument Passing Mechanimus inszenieren könnte. Aber auch hier 
wiederrum: Sobald Menüs rekursiv andere Menüs für Teilaufgaben benutzen 
wirds schnell unübersichtlich und fehleranfällig.

Und da Menüsysteme nichts zeitkritisches sind, ist es auch normalerweise 
völlig unerheblich ob eine Funktion jetzt ein paar Parameter bekommt, 
die sie eigentlich gar nicht benötigt und nur deshalb hat, weil alle 
Funktionen gleich aussehen müssen.

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.