Forum: Compiler & IDEs Einmal void-Pointer und zurück


von Walter T. (nicolas)


Lesenswert?

Hallo zusammen,
ich habe ein großes Struct, bei dem in einem Element ein Zeiger steht, 
und zwar entweder auf
 - Eine Variable eines bestimmten Typs oder
 - Auf eine Funktion.

Auf was dieser Zeiger zeigt, sagt mir ein enum, das sich ebenfalls in 
dem Struct befindet und definiert ist als:
1
typedef void (*voidFcn_t)(void);
2
3
enum datatype_e {
4
    voidFcn,
5
    uint8,
6
    uint16,
7
    int8,
8
    int16,
9
    nothing    
10
};
und das Struct ist definiert als:
1
 typedef struct {
2
    const flash char *menutext; // Menueeintrag/Funktionsname
3
    enum datatype_e datatype;   // Datentyp des Zeigers "pointer"
4
                                // Bei function pointer wird die Funktion
5
                                // gestartet, bei Zeigern auf normale Daten
6
                                // der Datentyp im Menue bearbeitet.
7
    void * pointer;             // Funktionszeiger
8
    const int16_t varMin;       // Untere Grenze numerische Variable, alternativ: Rueckgabewert
9
    const int16_t varMax;       // Oberere Grenze numerische Variable
10
  } menudata_t;                   // Struct wird terminiert mit einer Zeile, wo in .menutext ein Nulltext
11
                                  // ({'\0'}) steht.
Ziel der ganzen Aktion ist es, eine zum Datentyp passende Aktion 
auszuführen:
1
 ...
2
switch (menudata[selectedItem].datatype) {
3
    case voidFcn: // Funktionsausfuehrung
4
        ...
5
        nextfkt = (voidFcn_t) menudata[selectedItem].pointer;
6
        if (nextfkt==NULL) return (uint8_t) selectedItem;
7
        nextfkt();
8
        ...          
9
        break;
10
    case nothing: // Kann ignoriert werden
11
        break;
12
    case int16: 
13
        ;
14
        int16_t *pi16numvar = (int16_t *) menudata[selectedItem].pointer;
15
        ...
16
}
17
18
 ...
und definiert werden die Structs als:
1
const flash char MT_MESSAGE_TEST[]   = "Test Message";
2
const flash char MT_EDM_WRITE_TEST[] = "EDM: EEPROM write";
3
...
4
const flash char MT_EEPROMTEST_W[]   = "EEPROM-Test (W)";
5
const flash char MT_EEPROMTEST_WB[]  = "EEPROM-Test (WB)";
6
7
8
static const flash menudata_t Menudat[] = {
9
    {MT_RETURN        ,voidFcn,NULL                             ,0,0},
10
    {MT_MESSAGE_TEST  ,voidFcn,(void *) menu_test               ,0,0},
11
    ...
12
    {MT_EEPROMTEST_W  ,voidFcn,(void *) eeprom_test_write       ,0,0},
13
    {MT_EEPROMTEST_WB ,voidFcn,(void *) eeprom_test_write_block ,0,0},
14
    {MT_NULL          ,none   ,(void *) headmainmenu            ,0,0}, // Nullzeile markiert Ende des structs

Ich vermute, daß ich irgendetwas nicht bedacht habe - denn beim Cast des 
Funktionsnamens in einen void-Pointer gibt mir der AVR-GCC eine Warnung 
aus:
1
ISO C forbids conversion of function pointer to object pointer type

Habe ich bei diesem Konstrukt einen grundsätzlichen Denkfehler gemacht 
oder ist nur eine Kleinigkeit falsch?

Viele Grüße
W.T.

[Edit:]
Funktionieren tut es - nur machen mich Warnings immer misstrauisch.

: Bearbeitet durch User
von Rolf Magnus (Gast)


Lesenswert?

Walter Tarpan schrieb:
> Ich vermute, daß ich irgendetwas nicht bedacht habe - denn beim Cast des
> Funktionsnamens in einen void-Pointer gibt mir der AVR-GCC eine Warnung
> aus:
> ISO C forbids conversion of function pointer to object pointer type
>
> Habe ich bei diesem Konstrukt einen grundsätzlichen Denkfehler gemacht
> oder ist nur eine Kleinigkeit falsch?

Die Warnung ist doch eigentlich recht eindeutig. In ISO C darf man nicht 
zwischen Zeigern auf Funktionen und Zeigern auf Objekten casten. Ein 
void* gilt als Zeiger auf ein Objekt.

von Walter T. (nicolas)


Lesenswert?

Rolf Magnus schrieb:
> Die Warnung ist doch eigentlich recht eindeutig. In ISO C darf man nicht
> zwischen Zeigern auf Funktionen und Zeigern auf Objekten casten. Ein
> void* gilt als Zeiger auf ein Objekt.

Daß mich der Compiler nicht anlügt, glaube ich. Aber was ist eine 
regelkonforme Vorgehensweise?

von Rolf Magnus (Gast)


Lesenswert?

Walter Tarpan schrieb:
> Daß mich der Compiler nicht anlügt, glaube ich. Aber was ist eine
> regelkonforme Vorgehensweise?

Eine uninon nehmen. Die ist exakt für sowas gedacht.

von Walter T. (nicolas)


Lesenswert?

Hm, stimmt. Eine Union erspart mir auch das zurückgecaste. Danke für den 
Tipp!

von Peter II (Gast)


Lesenswert?

Rolf Magnus schrieb:
> Eine uninon nehmen. Die ist exakt für sowas gedacht.

ganz bestimmt nicht.


Eine zuweise zwischen einen funktionzeiger und einen normalen Zeiger ist 
überhaupt nicht zulässig.

von Hans-Georg L. (h-g-l)


Lesenswert?

Peter II schrieb:
> Rolf Magnus schrieb:
>> Eine uninon nehmen. Die ist exakt für sowas gedacht.
>
> ganz bestimmt nicht.
>
>


> Eine zuweise zwischen einen funktionzeiger und einen normalen Zeiger ist
> überhaupt nicht zulässig.

Ist es bei einer Union nicht, die beiden Zeiger teilen sich nur den 
Speicherplatz.

von Peter II (Gast)


Lesenswert?

Hans-Georg Lehnard schrieb:
> Ist es bei einer Union nicht, die beiden Zeiger teilen sich nur den
> Speicherplatz.

ach so habt ihr das gemeint. naja da kann man auch 2 variablen einlegen.

von Rolf Magnus (Gast)


Lesenswert?

Peter II schrieb:
> Hans-Georg Lehnard schrieb:
>> Ist es bei einer Union nicht, die beiden Zeiger teilen sich nur den
>> Speicherplatz.
>
> ach so habt ihr das gemeint. naja da kann man auch 2 variablen einlegen.

Die brauchen dann aber auch Platz für zwei Variablen. Der Witz bei der 
Union ist ja gerade, daß das dort eben nicht so ist.

von Walter T. (nicolas)


Lesenswert?

Ich frage mich nur gerade, wie die Initialisierung aussieht, wenn ein 
Element des Struct eine union ist:
1
typedef void (*voidFcn_t)(void);
2
3
enum datatype_e {
4
    voidFcn,
5
    uint8,
6
    uint16,
7
    int8,
8
    int16,
9
    nothing    
10
};
11
12
union datatype_u {
13
    voidFcn_t voidFcn;
14
    uint8_t   *puint8;
15
    uint16_t  *puint16;
16
    int8_t    *pint8;
17
    int16_t   *pint16;
18
    void      *pnothing;
19
};
20
21
22
typedef struct {
23
    const flash char *menutext; // Menueeintrag/Funktionsname
24
    enum datatype_e datatype;   // Datentyp des Zeigers "pointer"
25
                                // Bei function pointer wird die Funktion
26
                                // gestartet, bei Zeigern auf normale Daten
27
                                // der Datentyp im Menue bearbeitet.
28
    union datatype_u pointer;   //                
29
    const int16_t varMin;       // Untere Grenze numerische Variable, alternativ: Rueckgabewert
30
    const int16_t varMax;       // Oberere Grenze numerische Variable
31
} menudata_t;                   // Struct wird terminiert mit einer Zeile, wo in .menutext ein Nulltext
32
                                // ({'\0'}) steht.

Folgendes funktioniert schon einmal nicht:
1
static const flash menudata_t Menudat[] = {
2
    {MT_RETURN        ,voidFcn,NULL               ,0,0},
3
    {MT_MESSAGE_TEST  ,voidFcn,.voidFcn=menu_test ,0,0},
4
 ...

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Es ist nicht zulässig, weil nicht auf jeder Prozessorarchitektur ein 
Datenpointer genausogroß ist wie ein Funktionspointer.

Wenn man sich dessen bewusst ist, und nicht Wert auf 150%ige 
Portabilität legt, kann man allerdings auch "Schwamm drüber" sagen, und 
die Compilerwarnung ignorieren/abklemmen.

Mit dem sizeof-Operator lässt sich das ja immerhin überprüfen.

von Walter T. (nicolas)


Lesenswert?

Naja, das Ganze findet auf einem ARM oder AVR statt, beidesmal mit dem 
GCC.
Bei beiden sind die Zeigerbreiten gleich groß. Und es ist durchaus auch 
noch mehr plattformabhängiger Code dabei.

Allerdings finde ich auch nicht, wie ich die Warnung einzeln mit -WnoXXX 
abklemmen könnte. Bis jetzt waren auch alle "pedantic"-Warnungen 
zumindest ein guter Grund, über ein Konstrukt noch einmal in Ruhe 
nachzudenken, deswegen will ich mit dem globalen Warn-Level nicht 
herunter.

von bal (Gast)


Lesenswert?

Aber die Union müsste ja trotzdem klappen.
Wenn der Funktionspointer größer sein sollte als der Datenpointer wird 
eben die Union größer, so groß eben wie der größte Member.

Preisfrage:
Was passiert wenn ich den Datenpointer schreibe und den Funktionspointer 
lese, respektive "ausführe"?

von Walter T. (nicolas)


Lesenswert?

bal schrieb:
> Aber die Union müsste ja trotzdem klappen.

Wie initialisierst Du ein Union innerhalb eines struct-Arrays im Flash?

Der Umweg über einen Char-Zeiger bringt übrigens in Bezug auf die 
Warnung auch nichts.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Walter Tarpan schrieb:
> Wie initialisierst Du ein Union innerhalb eines struct-Arrays im Flash?

In Standard-C und mit avr-gcc überhaupt nicht :-)

Initialisierung gemäß C99 sieht z.B. so aus:
1
typedef struct
2
{
3
    int id;
4
    
5
    union
6
    {
7
        void *pd;
8
        void (*pf)();
9
    } u;
10
} s_t;
11
12
extern void foo (int);
13
14
s_t sf = { 2, { .pf = foo }};
15
s_t sd = { 1, { &sf }};

von Walter T. (nicolas)


Lesenswert?

Johann L. schrieb:
> Walter Tarpan schrieb:
>> Wie initialisierst Du ein Union innerhalb eines struct-Arrays im Flash?
>
> In Standard-C und mit avr-gcc überhaupt nicht :-)

Wahrscheinlich ein Tippfehler: Der AVR-GCC hat mit dem Konstrukt 
keinerlei Problem.

Der MSVC (C89) kriegt es allerdings nicht hin.


Ehrlich gesagt reicht mir auch die Lösung mit dem void-Zeiger von ganz 
oben. Ich würde nur gerne die Warnung einzeln unterdrücken.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Walter Tarpan schrieb:
> Johann L. schrieb:
>> Walter Tarpan schrieb:
>>> Wie initialisierst Du ein Union innerhalb eines struct-Arrays im Flash?
>>
>> In Standard-C und mit avr-gcc überhaupt nicht :-)
>
> Wahrscheinlich ein Tippfehler: Der AVR-GCC hat mit dem Konstrukt
> keinerlei Problem.

Das Ding liegt aber nicht im Flash, dazu brauchst du __flash 
oder __progmem__.

> Der MSVC (C89) kriegt es allerdings nicht hin.

Es braucht, wie gesagt, mindestens C99.  Das kann Winzigweich nun mal 
nicht.

von Walter T. (nicolas)


Lesenswert?

Johann L. schrieb:
>> Wahrscheinlich ein Tippfehler: Der AVR-GCC hat mit dem Konstrukt
>> keinerlei Problem.
>
> Das Ding liegt aber nicht im Flash, dazu brauchst du __flash
> oder __progmem__.

Momentan sind zwei Varianten vorhanden:
a) Die Definition ganz oben mit:
1
typedef struct {
2
    const flash char *menutext; // Menueeintrag/Funktionsname
3
    enum datatype_e datatype;   // Datentyp des Zeigers "pointer"
4
    void * pointer;             // Funktionszeiger
5
    //char * pointer;           // Funktionszeiger
6
    const int16_t varMin;       // Untere Grenze numerische Variable
7
    const int16_t varMax;       // Oberere Grenze numerische Variable
8
  } menudata_t;                 // Struct wird terminiert mit einer
9
                                // Zeile, wo in .menutext ein 
10
                                // Nulltext ({'\0'}) steht. 
11
12
13
static const flash menudata_t Menudat[] = {
14
    {MT_RETURN        ,voidFcn,NULL                             ,0,0},
15
    {MT_MESSAGE_TEST  ,voidFcn,(void *) menu_test               ,0,0},
16
    ...
liegt im Flash, da "flash" für den AVR zu "__flash" expandiert wird und 
die Header eingebunden sind. Läge es nicht im Flash, würde nicht 
funktionieren, da das Struct in voller Größe größer als der freie SRAM 
ist.

b) Für die Variante mit dem anonymen Union in "menudata_t" liegt auch 
alles im Flash. Sie sah so aus:
1
typedef struct {
2
    const flash char *menutext; // Menueeintrag/Funktionsname
3
    enum datatype_e datatype;   // Datentyp des Zeigers "pointer"
4
    union {
5
        void      *pnothing;
6
        voidFcn_t *voidFcn;
7
        uint8_t   *puint8;
8
        uint16_t  *puint16;
9
        int8_t    *pint8;
10
        int16_t   *pint16;
11
    };
12
    const int16_t varMin;       // Untere Grenze numerische Variable
13
    const int16_t varMax;       // Oberere Grenze numerische Variable
14
  } menudata_t;                 // Struct wird terminiert mit einer
15
                                // Zeile, wo in .menutext ein 
16
                                // Nulltext ({'\0'}) steht. 
17
18
19
static const flash menudata_t Menudat[] = {
20
    {MT_RETURN        ,voidFcn,NULL                           ,0,0},
21
    {MT_MESSAGE_TEST  ,voidFcn,{.voidFcn=menu_test}           ,0,0},
22
    ...
und funktioniert ebenfalls auf dem AVR.

Getestet habe ich alles mit drei Compilern:
 1. AVR-GCC
 2. ARM-GCC
 3. MSVC 2010

Jetzt das Problem der Varianten:
 1a: funktioniert, gibt eine lästige Warnung aus. Wenn ich die Warnung
     unterdrückt bekomme, ohne daß andere Warnungen darunter leiden, bin
     ich zufrieden.
     Da die Structs verhältnismäßig groß sind, wird die Warnung nämlich
     sehr oft ausgegeben, daß andere Warnungen dann untergehen können.
 2a: Siehe 1a.
 3a: funktioniert.

 1b: funktioniert.
 2b: funktioniert.
 3c: funktioniert nicht, da der MSVC nur C89 kann. Sollte ich das
     vernünftig initialisiert bekommen, daß es bei den anderen beiden im
     Flash steht, bin ich auch zufrieden.

: Bearbeitet durch User
von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Für 3c (gemeint ist wohl 3b):

Gerüchten zufolge soll der C-Compiler, den MS in den letzten Jahren 
sträflich vernachlässigt hat, in der neusten Inkarnation des Visual 
Studio  (VS2013) etwas weiter in Richtung C11 entwickelt worden sein.

Ob das hier hilft, entzieht sich meiner Kenntnis.

Für 1a:

gcc kennt ein #pragma, mit dem sich spezifische Warnungen abschalten 
lassen:

  #pragma GCC diagnostic ignored "-Wxxxx"

und wieder einschalten:

  #pragma GCC diagnostic warning "-Wxxxx"

Praktischerweise gibt es bei gcc keine die spezfische Warnung 
spezifizierende Nummer, was die Suche nach der abzuklemmenden Warnung 
erschwert - auf die Schnelle habe ich "ISO C forbids conversion of 
function pointer to object pointer type" nicht in 
http://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html finden können.

Daß die Warnung prinzipiell sinnvoll ist, steht außer Frage, aber sie zu 
ignorieren, weil man weiß, daß auf der Zielmaschine Daten- und 
Funktionspointer die gleiche Größe haben, ist ein durchaus legitimer 
Wunsch.

Und es ist ja nicht so, daß alle Betriebssystem-APIs jeweils neu 
geschrieben werden, nur weil die Akademiker, die den jeweiligen 
C-Standard kredenzen, sich mal wieder was neues ausgedacht haben.

von Walter T. (nicolas)


Lesenswert?

Rufus Τ. Firefly schrieb:
> Praktischerweise gibt es bei gcc keine die spezfische Warnung
> spezifizierende Nummer, was die Suche nach der abzuklemmenden Warnung
> erschwert - auf die Schnelle habe ich "ISO C forbids conversion of
> function pointer to object pointer type" nicht in
> http://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html finden können.
>

Hallo Rufus,
genau auf der Seite habe ich auch gesucht. Die Warnung wird als 
"-Wpedantic" geführt, sie einzeln abzuschalten finde ich nichts.

> Daß die Warnung prinzipiell sinnvoll ist, steht außer Frage, aber sie zu
> ignorieren, weil man weiß, daß auf der Zielmaschine Daten- und
> Funktionspointer die gleiche Größe haben, ist ein durchaus legitimer
> Wunsch.

Zumal man es mit einer Zeile
1
static_assert((sizeof(voidFcn_t)==sizeof(uint8_t*)),\
2
   "Error: Pointers to functions and objects differ in size. Implementation"\
3
   "won't work.");
problemlos automatisch im Auge behalten kann.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Rufus Τ. Firefly schrieb:
> Praktischerweise gibt es bei gcc keine die spezfische Warnung
> spezifizierende Nummer, was die Suche nach der abzuklemmenden Warnung
> erschwert

Nummer nicht, aber einen Schalter:
1
warning: ISO C forbids initialization between function pointer and 'void *' [-Wpedantic]

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Johann L. schrieb:
> Nummer nicht, aber einen Schalter:

Der aber klemmt noch diverse andere Warnungen ab, die man vielleicht 
doch haben möchte.

Gut, bei Anwendung mit #pragma ist das nicht besonders relevant.

Also:

  #pragma GCC diagnostic ignored "-Wpedantic"

  ... Pointerzuweisung ...

  #pragma GCC diagnostic warning "-Wpedantic"

Oder, um nicht versehentlich pedantic einzuschalten, falls es vorher 
nicht eingeschaltet war:


  #pragma GCC diagnostic push
  #pragma GCC diagnostic ignored "-Wpedantic"

  ... Pointerzuweisung ...

  #pragma GCC diagnostic pop


Das dürfte der "sauberste" Weg sein, und um ganz sicher zu gehen, 
kommt noch das assert von Walter dazu.

von Walter T. (nicolas)


Lesenswert?

Rufus Τ. Firefly schrieb:
> #pragma GCC diagnostic push
>   #pragma GCC diagnostic ignored "-Wpedantic"
>
>   ... Pointerzuweisung ...
>
>   #pragma GCC diagnostic pop

Super! Vielen Dank! Das ist ein workaround, mit dem ich gut leben 
kann.

von W.S. (Gast)


Lesenswert?

Rufus Τ. Firefly schrieb:
> #pragma GCC diagnostic ignored "-Wpedantic"

Augen zu, damit man das Elend nicht sehen muß?
Funktioniert, aber mich graust es bei sowas.

Walter Tarpan schrieb:
> ch habe ein großes Struct, bei dem in einem Element ein Zeiger steht,
> und zwar entweder auf
>  - Eine Variable eines bestimmten Typs oder
>  - Auf eine Funktion.

Wozu schreibst du SOWAS bloß? Igittigitt.
Wenn du schon ein "großes" struct hast, dann spendiere diesem struct 
doch einfach noch einen oder mehrere weitere Zeiger, so daß du all diese 
Klimmzüge garnicht erst heraufbeschwören mußt.

Oder überdenke deinen gesamten Ansatz nochmal. Das Ganze soll für ein 
Menü herhalten, ja? Dann nimm nur EINEN Zeiger, nämlich einen 
Funktionszeiger, der auf eine jeweils zum Menü-Element passende Funktion 
zeigt. Damit wird deine ganze switch/case-Latte überflüssig und die 
jeweiligen Bearbeitungsfunktionen werden klein, weil speziell. Dein enum 
kannst du dir dann auch sparen, denn die jeweilige Funktion weiß schon 
selber, was sie tun muß.

W.S.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

W.S. schrieb:
> Augen zu, damit man das Elend nicht sehen muß?

Nein. Augen zu, um eine akademische Warnung abzuklemmen.

von Rolf Magnus (Gast)


Lesenswert?

bal schrieb:
> Preisfrage:
> Was passiert wenn ich den Datenpointer schreibe und den Funktionspointer
> lese, respektive "ausführe"?

Nach ISO C ist das Verhalten undefiniert. In der Praxis wird halt 
irgendein unsinniger Zeigerwert rauskommen.

Rufus Τ. Firefly schrieb:
>> Augen zu, damit man das Elend nicht sehen muß?
>
> Nein. Augen zu, um eine akademische Warnung abzuklemmen.

Hmm, jetzt verwunderst du mich doch. Bei SDHC-Karten sollen Leute auf 
eine für sie funktionierende Lösung verzichten, um standardkonform zu 
sein, bei C ist dir Konformität dann aber wurscht, solang's irgendwie 
tut.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

SD-Karten steckt man in irgendwelche Geräte, und erwartet, daß sie 
funktionieren, bei C-Programmen aber tut man das üblicherweise nicht.

von W.S. (Gast)


Lesenswert?

O doch. Die allermeisten Leute hier in diesem Forum versuchen, fertige 
C-Quellstücke in ihre Hardware zu stopfen OHNE NACHZUDENKEN und sie 
erwarten daß alles funktioniert (neudeutsch FUNZT).

Da halte ich nen pedantischen Compiler durchaus für hilfreich, um diesen 
Leuten wenigstens ein wenig auf den Zeh zu treten. Mag ja sein daß der 
eine oder andere aus dieser Gilde sowas zum Anlaß nimmt, über sein 
verkorkstes Gesamtkonzept nochmal nachzudenken - und schon ist etwas zur 
Besserung der Allgemeinheit getan. Nicht viel, aber immerhin etwas.

W.S.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Nun, dem "funzt"-Ansatz beim "proggen" (so nennt man das heutzutage 
wohl) lässt sich hier immerhin so:

Walter Tarpan schrieb:
> Zumal man es mit einer Zeile
> static_assert((sizeof(voidFcn_t)==sizeof(uint8_t*)),\
>   "Error: Pointers to functions and objects differ in size."\
>   "Implementation won't work.");
> problemlos automatisch im Auge behalten kann.

mit einer gepflegten Fehlermeldung zwar nicht gerade auf die Sprünge 
helfen, aber immerhin auf die Finger klopfen, sollte es nötig werden.

von Walter Tarpan (Gast)


Lesenswert?

W.S. schrieb:
> Oder überdenke deinen gesamten Ansatz nochmal. Das Ganze soll für ein
> Menü herhalten, ja? Dann nimm nur EINEN Zeiger, nämlich einen
> Funktionszeiger, der auf eine jeweils zum Menü-Element passende Funktion
> zeigt. Damit wird deine ganze switch/case-Latte überflüssig und die
> jeweiligen Bearbeitungsfunktionen werden klein, weil speziell. Dein enum
> kannst du dir dann auch sparen, denn die jeweilige Funktion weiß schon
> selber, was sie tun muß.

W.S. schrieb:
> O doch. Die allermeisten Leute hier in diesem Forum versuchen, fertige
> C-Quellstücke in ihre Hardware zu stopfen OHNE NACHZUDENKEN und sie
> erwarten daß alles funktioniert (neudeutsch FUNZT).

W.S. schrieb:
> Mag ja sein daß der
> eine oder andere aus dieser Gilde sowas zum Anlaß nimmt, über sein
> verkorkstes Gesamtkonzept nochmal nachzudenken - und schon ist etwas zur
> Besserung der Allgemeinheit getan.


Danke, daß Du Dir die Zeit genommen hast, den aktuellen Stand meines 
Projekts von meiner Website zu laden und das verkorkste Gesamtkonzept 
einmal durchzusehen. Wie Du schon richtig erkannt hast, ist dieser 
Quelltext zum Wohle der Menschheit gedacht. Obwohl ich kein 
Softwareentwickler bin, bin ich natürlich der Meinung, daß es genau das 
Bastelprojekt ist, was der Menschheit bislang gefehlt hat.

Bislang dachte ich, eine einzige Warnung bei "-pedantic" bei einem 
Projekt mit etwas mehr als 7kZeilen (Header+Quelltext) wäre gar nicht so 
schlecht. Zumal wenn die Warnung für eine Stelle im Quelltext eines 
Menüs ist, also dort, wo ein Fehler -sollte er auftreten- sofort für den 
Tester sichtbar ist.

Bislang war ich natürlich auch der Meinung, daß ein 
switch/case-Konstrukt mit weniger als 40 Zeilen deutlich übersichtlicher 
als sechs einzelne Funktionen ist, die auch noch alle einander sehr 
ähnlich sein müßten. Mea Culpa.

Also bitte zeige mir Dein nicht-verkorkstes Menü, damit ich lernen kann!

von Daniel A. (daniel-a)


Lesenswert?

Wie wäre es damit: Einen Funktionspointer in einer variable speichern, 
von welcher man einen Objektpointer bekommen kann. Es folgt ein 
Beispiel:
1
typedef void (*func_t)();
2
3
void a(){}
4
void b(){}
5
void c(){}
6
7
func_t funcs[] = { a,b };
8
func_t fc = c;
9
10
int main(void){
11
12
  func_t f;
13
  void* v;
14
15
  v = &funcs[1];
16
  f = (*(func_t*)v);  
17
  f();
18
19
  v = &fc;
20
  f = (*(func_t*)v);
21
  (*(func_t*)v)();
22
23
}

von Walter T. (nicolas)


Lesenswert?

Daniel A. schrieb:
> Wie wäre es damit: Einen Funktionspointer in einer variable speichern,
> von welcher man einen Objektpointer bekommen kann.

Hallo Daniel,
Zweck der ganzen Aktion war es ja, ein Menü-Strukt etwas übersichtlicher 
darzustellen. Sozusagen eine läppische Spalte (=Struct-Feld) einsparen, 
um die Verwechselbarkeit zwischen Variablenzeiger und Funktionszeiger 
auszuschließen.

Für jeden Eintrag eine extra-Funktion abzulegen, konterkariert dieses 
Ziel etwas.

Aber wie ich schon oben schrieb: Eigentlich bin ich mit der jetzigen 
Lösung auch zufrieden. Hätte dieses Forum eine "gelöst"-Markierung, wäre 
sie jetzt gesetzt.

Viele Grüße
W.T.

von W.S. (Gast)


Lesenswert?

Walter Tarpan schrieb:
> Also bitte zeige mir Dein nicht-verkorkstes Menü, damit ich lernen kann!

Ich kann mich des Eindruckes nicht erwehren, daß du beleidigt reagierst 
anstatt ein bissel nachzudenken.

Also ich erkläre dir das:

Walter Tarpan schrieb:
> ich habe ein großes Struct, bei dem in einem Element ein Zeiger steht,
> und zwar

Walter Tarpan schrieb:
> Ziel der ganzen Aktion ist es, eine zum Datentyp passende Aktion
> auszuführen:

Eben. Du denkst prozedural und du schreibst deinen Code dann auch 
prozedural, also etwa so, wie man es auf Papier in nem PAP macht. Sowas 
geht ja für ganz kleine Projekte, da braucht man nur ein Array aus 
Struct's, wo eine Bezeichnung und ein Aktionscode drinsteht. Das Ganze 
artet dann in ein Programmstück mit einem umfänglichen switch case aus.

Wie gesagt, für ganz kleine Projekte reicht sowas.

Aber sobald ein Projekt und damit ein Menüsystem etwas größer wird, ist 
dieser prozedurale Ansatz nur noch Mist. Zu Anfang geht es, dann wird#s 
umfänglicher und man verheddert sich (wie du) in der Behandlung von 
Sonderfällen - und wenn man irgendwas später ändern will, muß man an 
1000 Stellen herumdoktern.

Sowas macht man anders, nämlich objektorientiert und ereignisgesteuert. 
Ein solches Menüsystem besteht aus einem Sack von Objekten, logisch 
hierarchisch je nach Bedarf angeordnet und physisch im ROM angeordnet. 
Ja, im ROM, ohne malloc und Konsorten zu benötigen.

Jedes Objekt hat Daten, also quasi Properties, Zeiger zu seinen Peers, 
nen Zeiger zu seinen Members, nen Zeiger zu seinen variablen Daten im 
RAM und Methoden.

Jaja, C kennt keine Objekte, weswegen man - sofern man in C schreiben 
will - sowas eben zu Fuß erledigen muß. Dazu braucht es eine struct 
Definition, die für alle Menüelemente gilt. Lediglich die Methoden sind 
je nach Objekttyp unterschiedlich. Eigentlich braucht man genaugenommen 
nur 3 Methoden:
1. eine Methode, mit der sich das Objekt selbst darstellt
2. eine Methode, mit der das Objekt auf allgemeine Ereignisse reagiert
3. eine Methode, mit der das gerade fokussierte Objekt auf Usereingaben 
reagiert.

Ein so aufgebautes Menü ist wirklich universell, die Menüelemente können 
alles Mögliche sein (statischer Text, ne Eingabezeile, Buttons, 
Checkboxes usw.) und sie können Members haben, also ein Button mit nem 
Text drauf oder mit nem Icon drauf oder mit beidem usw.

Das Angenehme an so einem Menü ist, daß die verschiedenen Objekte 
voneinander getrennt ihre Arbeit verrichten, OHNE daß das übergeordnete 
Element sich um deren Details kümmern muß.

Lade dir mal die Lernbetty aus der Codesammlung herunter, dort hatte ich 
damals ein derart aufgebautes Menü vorgeturnt. Du kannst dir also dort 
deine Anregungen holen.

W.S.

von Walter T. (nicolas)


Lesenswert?

W.S. schrieb:
> Ich kann mich des Eindruckes nicht erwehren, daß du beleidigt reagierst
> anstatt ein bissel nachzudenken.

Der Eindruck mag entstehen, allerdings neige ich dazu, das Angenehme mit 
dem Nützlichen zu verbinden und einfach beides gleichzeitig zu machen 
:-).

W.S. schrieb:
> Eben. Du denkst prozedural und du schreibst deinen Code dann auch
> prozedural,
>
>  [...]
>
> Wie gesagt, für ganz kleine Projekte reicht sowas.

Siehst Du. Genau der Meinung bin ich auch. Ich habe maximal 5 
Datentypen, von denen 4 fast identisch behandelt werden müssen. Und die 
"Behandlung" aller Datentypen zusammen ist nur wenige Zeilen lang.

Von den Prozeduren bestehen 80% einfach daraus, das Eingabe- und das 
Ausgabegerät zur Verfügung zu stellen.

Wenn ich das Menü-Struct übersichtlich auf einer Bildschirmseite 
dargestellt bekomme, ist mir das deutlich mehr wert, als wenn das 
bischen Quelltext, das darüberläuft, schöner ist.

Ich bin ein Freund objektorientierter Arbeitsweise. Aber nicht für ein 
popeliges Menü auf einem 128x64-Zeichen-Textdisplay.

von W.S. (Gast)


Lesenswert?

Na, dann war das Problem ja schon von Anfang an gelöst und der ganze 
Aufriß hier völlig umsonst. Und dann brauschst du ja das "voidFcn" auch 
bloß nicht, weil du garantiert massenweise Platz in deinem "datatype" 
hast, um alle vorkommenden Fälle abzudecken, ohne die Adresse einer 
Funktion mitzuliefern.

Schreib das beim nächsten Mal doch gleich hin, das spart ne Menge 
Aufregung.

W.S.

von Walter Tarpan (Gast)


Lesenswert?

W.S. schrieb:
> Schreib das beim nächsten Mal doch gleich hin, das spart ne Menge
> Aufregung.

Hm. Ich könnte mich nicht daran erinnern, aufgeregt gewesen zu sein. 
Seit dem Beitrag mit dem "unnamed"-union habe ich eine sinnvolle und 
schöne Lösung für C99, und seit diesem Beitrag 
Beitrag "Re: Einmal void-Pointer und zurück" sogar eine Lösung, 
die unter C89 gut funktioniert.

Danach kam ein Beitrag mit "Igittigitt". Aufregend ist der Thread 
seitdem auch noch nicht. Aber interessant genug, wenn man etwas 
streitlustig ist und gleichzeitig etwas Langeweile hat.

W.S. schrieb:
> Na, dann war das Problem ja schon von Anfang an gelöst und der ganze
> Aufriß hier völlig umsonst. Und dann brauschst du ja das "voidFcn" auch
> bloß nicht, weil du garantiert massenweise Platz in deinem "datatype"
> hast, um alle vorkommenden Fälle abzudecken, ohne die Adresse einer
> Funktion mitzuliefern.

Warum ich einen objektorientierten Ansatz brauche, wenn ich schlappe 5 
Datentypen habe, von denen einer ein function handle ist und die anderen 
4 fast identisch sind, geht mir trotzdem nicht in den Kopf. Und warum 
das plötzlich massiv Platz braucht, wenn ich einen enum (1 byte) und 
einen Zeiger (2 byte) in ein Struct packe, ist mir auch rätselhaft. Aber 
egal. Ich hab's ja. Der Flash meines ATmega32 ist ja gerade mal zu 74% 
voll. Da passen noch viele Struct-Zeilen hinein.

Viele Grüße
W.T.

von W.S. (Gast)


Lesenswert?

Nanana.. DU hast ganz am Anfang von deinem "großen struct" geschrieben 
- alle anderen haben lediglich genau darauf ihre Vermutungen aufgebaut.

Und warum du 5 Integertypen und einen Funktionszeiger denn überhaupt 
brauchst, hast du noch immer nicht erklärt. Wenn du z.B. alle Variablen 
als 'long' deklarierst, brauchst du überhaupt keine Fallunterscheidung, 
sondern bloß einen long* Zeiger.

Ich hab allerdings in deinen anderen Beiträgen gesehen, daß du teilweise 
sehr seltsame Denkansätze verfolgst, die ich so nie und nimmer andenken 
würde, beispielsweise dein Versuch mit vereinheitlichtem PortA, PortB 
usw. um textuelle Gleichheit zwischen ARM und AVR herzustellen.

Normalerweise schreibt man sich die Initialisierung des µC genau passend 
zum konkreten Chip ganz individuell. Da ist kein Platz für irgendwelche 
Vereinheitlichungen mit ganz anderen µC-Familien.

W.S.

von Walter T. (nicolas)


Lesenswert?

OK, diskutieren wir die Sinnhaftigkeit meines Vorgehens hier. Thematisch 
ist der Thread ja abgeschlossen. Und neben meiner natürlichen Streitlust 
hat das für mich den Vorteil, daß man dadurch, daß man ein Vorhaben 
erklärt, selbst merkt, wenn man eine Dummheit vor hat :-).

W.S. schrieb:
> Und warum du 5 Integertypen und einen Funktionszeiger denn überhaupt
> brauchst, hast du noch immer nicht erklärt. Wenn du z.B. alle Variablen
> als 'long' deklarierst, brauchst du überhaupt keine Fallunterscheidung,
> sondern bloß einen long* Zeiger.

Oben habe ich die Deklaration meines "großen Structs" hingeschrieben. 
Aber ich paste es noch einmal in der aktuellen Form:
1
      __extension__ typedef struct {
2
        const flash char *menutext; // Menueeintrag/Funktionsname
3
        enum datatype_e datatype;   // Datentyp des Zeigers "pointer"
4
                                    // Bei function pointer wird die Funktion
5
                                    // gestartet, bei Zeigern auf normale Daten
6
                                    // der Datentyp im Menue bearbeitet.
7
          union {
8
              voidFcn_t pvoidFcn;   // Funktionszeiger
9
              uint8_t  *puint8;     // Zeiger auf Variablentypen
10
              uint16_t *puint16;     
11
              int8_t   *pint8;
12
              int16_t  *pint16;
13
              void     *pvoid;
14
          };                                            
15
        const int16_t varMin;       // Untere Grenze numerische Variable
16
        const int16_t varMax;       // Oberere Grenze numerische Variable
17
      } menudata_t;                 // Struct wird terminiert mit einer Zeile, 
18
                                    // wo in .menutext ein Nulltext
19
                                    // ({'\0'}) steht.
Auf dem AVR (einem ATmega32) ist jedes Struct dieses Typs im Flash 
angesiedelt und stellt den Inhalt eines Menüs dar.
Und so sieht die Speicherbelegung aus:
1
Program Memory Usage   :  27728 bytes   84,6 % Full
2
Data Memory Usage      :  1519 bytes   74,2 % Full
Wie man sieht, ist im Flash noch großzügig Platz vorhanden, während ich 
im SRAM aufpassen muß.

Das Struct mit den Konfigurationsdaten muß im SRAM Platz finden. Sogar 
zweimal (Eine Sicherheitskopie während des EEPROM-Auslesens und in 
Menüphasen, um geänderte Einstellungen verwerfen zu können). Also mag 
ich hier Datensparsamkeit. Was in ein uint8 paßt, kommt in ein uint8. 
Und was in ein int8 paßt in ein int8. Die Zeiger im struct sind immer 16 
Bit groß. Macht aber nichts. Ist ja im Flash.

Das Menü hat zwei "Kernfunktionalitäten":
 - Einstellungen (Integer-Werte) bearbeiten
 - "Programme" (void-void-Funktionen) starten - die meisten davon sind 
Diagnosefunktionen oder andere Menüs.

Für diese Kernfunktionalitäten wird extrem wenig Quelltext gebraucht. Es 
paßt alles auf eine Bildschirmseite. Der Aufwand im Menü steckt in den 
Nebenfunktionalitäten:
 - Bildschirm-Update
 - Drehgeber- und Taster-abfragen
 - Hausaufgaben machen (Scrollpositionen ausrechnen, dafür sorgen, daß 
Bedienelemente, die vom Menü ständig abgegeben und wiedergeholt werden, 
das machen, was man von ihnen erwartet usw.)

Es gibt keinerlei Grund, auf diese Nebenfunktionalitäten einen weiteren 
Abstraktionslevel aufzusetzen, um diese Handvoll Hauptfunktionalitäten 
zu trennen. Dazu ist das Menü zu einfach gestrickt.

Und natürlich ist das Menü selbst mehrere Abstraktionsebenen über dem 
Grafik-Display, dem Drehgeber und dem Taster.

-----

W.S. schrieb:
> ch hab allerdings in deinen anderen Beiträgen gesehen, daß du teilweise
> sehr seltsame Denkansätze verfolgst, die ich so nie und nimmer andenken
> würde, beispielsweise dein Versuch mit vereinheitlichtem PortA, PortB
> usw. um textuelle Gleichheit zwischen ARM und AVR herzustellen.

Der Denkansatz mag seltsam erscheinen - ich halte ihn aber für sinnvoll.

Mit dem AVR habe ich eine funktionierende Plattform (=Quelltext + 
Entwicklungsumgebung + Steuerungsleiterplatte + Gerätehardware, in die 
das Ganze eingebettet ist). Für den ARM sind drei dieser Punkte 
neu/nicht vorhanden.

Also verfolge ich den Weg:
 1. AVR-Quelltext allgemeiner (=plattformunabhängiger) machen.
 2. Plattformabhängige Komponenten kapseln
 3. Testen (mit AVR-Hardware)
(4). Für den AVR einen Bitbanging-"Treiber" schreiben (umschaltbar)
(5). Testen (mit AVR-Hardware)
(6). Bitbanging-"Treiber" plattformunabhängig schreiben.
(7). Testen (mit AVR-Hardware)
 8. Auf ARM portieren
 9. Modultest mit ARM-Hardware
10. Für den ARM einen "nativen Treiber" schreiben (umschaltbar).
11. Modultest mit ARM-Hardware
12. Verbesserungen in AVR-Quelltext einfließen lassen
13. goto 1;

Die Punkte in Klammern habe ich nur beim I2C gemacht- den fand ich ein 
wenig knifflig. Und für diese Punkte war die Austauschbarkeit einer 
funktionierenden Low-Level-Routine eine echte Hilfe. Auch wenn ich (zum 
Glück) noch keine ernsthaften Hardwarefehler finden mußte.

Vielleicht wäre ein "schmeiß alles weg und machs neu"-Ansatz schneller 
gegangen. Vielleicht hätte ich mich damit aber auch mit drei neuen 
Faktoren neben dem Quelltext (Entwicklungsumgebung, MCU, Hardware) von 
der grünen Wiese auch so ins Abseits manövriert, daß ich noch nicht so 
weit wäre.

W.S. schrieb:
> Normalerweise schreibt man sich die Initialisierung des µC genau passend
> zum konkreten Chip ganz individuell. Da ist kein Platz für irgendwelche
> Vereinheitlichungen mit ganz anderen µC-Familien.

"Normalerweise" findet vermutlich auch selten Sprung eines Projekts von 
einer MCU mit 8 Bit 16 MHz zu 32 Bit 72MHz statt. Schon allein aus 
Kostengründen würde ein knapp zu kleiner Prozessor selten durch etwas 
anderes mit soooo viel Reserve ersetzt. Oder wäre aus ebendiesen Gründen 
schon lange durch etwas Moderneres ersetzt worden.

-----

Zu guter Letzt: Durch die Migration auf andere Hardware sind eben neue 
Anforderungen an das Menü entstanden- denn jetzt muß einfach viel mehr 
einstellbar sein. Und deswegen hat "alles mit int16" den verbleibenden 
RAM unkomfortabel klein und die separate Spalte für Funktionszeiger hat 
den Quelltext schlechter lesbar gemacht.

So schließt sich der Kreis.

: Bearbeitet durch User
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.