Forum: Compiler & IDEs C Verständnissfrage Pointer (ja wieder mal ;-) )


von Andreas B. (bitverdreher)


Lesenswert?

Hallo,
hier habe ich einen Codeschnipsel, den ich selbst mal irgendwo (Hier im 
Forum? Leider finde ich die Stelle nicht mehr) kopiert und angepasst 
habe.
1
typedef struct {
2
   void (*func);                      // Pointer to the function
3
   char text[5];                      // command of function
4
} command_t
5
6
// command array
7
const command_t commands[] PROGMEM= {
8
   { PBlockEEprom, "BL" },
9
   { EPB, "ERB" },
10
   { EWB, "EWB" },
11
 .. hier folgen noch weitere
12
};
13
14
int8_t execute (const char *text) {
15
uint16_t i;
16
   const command_t * cmd = commands;
17
   for (i=0; i < sizeof(commands) / sizeof(command_t); i++) {
18
      if (strcasecmp_P (text, cmd->text)) {
19
        cmd++;
20
        continue;
21
      }
22
      int16_t (*func)(int16_t); // found <-- Was macht das??? 
23
      func = (int16_t(*)(int16_t)) pgm_read_word (& cmd->func); // get pointer
24
      return func(1);
25
   }
26
   if (DEBUG) UsartPuts ("Syntax Error\n");
27
   return 0;

Die Funktion Execute ruft mir die entsprechende Funktion auf, die als 
String übergeben wird. Das funktioniert auch so weit wunderbar.
Was mir hier aber unklar ist, habe ich oben markiert.

Jetzt wollte ich etwas ähnliches machen. Es sollen Zeilen aus einer 
Konfigurationsdatei eingelesen werden und entsprechende Variablen 
gesetzt werden. (Format Variable=Wert, für jede Variable eine neue 
Zeile)

Dazu habe ich den Code wie folgt angepaßt:
1
typedef struct {
2
  void (*varpointer);               // Pointer to the variable
3
  char text[10];                    // keyword
4
} keyword_t;
5
6
// default values
7
uint8 Vcc_min = 237;
8
uint8 SleepTime = 0;
9
10
// keyword array for the configuration file (pointer to the variables)
11
const keyword_t keywords[] PROGMEM= {
12
   { &SleepTime, "SLEEPTIME" },
13
   { &Vcc_min, "VCC_MIN" }
14
};       
15
16
17
relevanter Auschnitt aus der betreffenden Funktion:
18
19
   char filechar;
20
   uint8_t i;    
21
   const keyword_t * key = keywords;
22
   char sline[MAX_BUFFER];    
23
   char* vline;
24
25
          while (filechar = FAT_ReadLine(file, sline, MAX_BUFFER)) // read until EOL
26
          {             
27
            for (i=0; i < sizeof(keywords) / sizeof(keyword_t); i++) {
28
               if (strcasecmp_P (sline, key->text)) {
29
               key++;
30
               continue;
31
               }
32
               int16_t (*varpointer)(int16_t); // found
33
               varpointer = (int16_t(*)(int16_t)) pgm_read_word (& key->varpointer); // get pointer
34
35
               // replace EOL to 0x00 (use as string)               
36
               vline = strchr(sline, 0x0D);
37
               *vline = 0x00;
38
               // seek the "=" in the line, vline is now a string with the value after '='               
39
               vline++ = strchr(sline, 0x3D);
40
               
41
               // set the variable of the key table to the readed value
42
               *varpointer = atoi(vline);
43
44
            }

varpointer sollte doch jetzt ein Pointer auf meine gesuchte Variable 
sein, *varpointer somit der Inhalt.
Beim Kompilieren bekomme ich hier aber den Fehler:
error: lvalue required as left operand of assignment

Wo ist hier mein  Denkfehler?

Gruß
Andreas

von Karl H. (kbuchegg)


Lesenswert?

Du wirfst hier kunterbunt Datenzeiger und Zeiger auf Funktionen 
durcheinander.

Lern in deinem C-Buch über normale Pointer und mach erst mal einen Bogen 
um Funktionspointer.

Und so wie das aussieht, bist du gut beraten erst mal keine void Pointer 
zu benutzen. So wie das aussieht, gibt es dazu auch keinen Grund.
1
typedef struct {
2
  uint8_t* varpointer;              // Pointer to the variable
3
  char     text[10];                // keyword
4
} keyword_t;
5
6
uint8 Vcc_min = 237;
7
uint8 SleepTime = 0;
8
9
const keyword_t keywords[] PROGMEM= {
10
   { &SleepTime, "SLEEPTIME" },
11
   { &Vcc_min, "VCC_MIN" }
12
};
13
14
....
15
16
               uint8_t* varpointer = (uint8_t*)pgm_read_word (&key->varpointer);
17
18
               // replace EOL to 0x00 (use as string)               
19
               vline = strchr(sline, 0x0D);
20
               *vline = 0x00;
21
               // seek the "=" in the line, vline is now a string with the value after '='               
22
               vline++ = strchr(sline, '=');
23
               
24
               // set the variable of the key table to the readed value
25
               *varpointer = atoi(vline);
26
....

von Andreas B. (bitverdreher)


Lesenswert?

Hallo Karl-Heinz,
daß das im Original Funktionszeiger sind, ist mir schon klar. Die Basics 
von Pointern habe ich schon verstanden. Mir fehlt halt die Übung in C.
Es sollen jetzt aber Zeiger auf meine Variablen sein, die ich vorher 
deklariert habe.

Was macht denn jetzt genau int16_t (*func)(int16_t)?
Dabei wird scheibar auch die Variable func deklariert. Aber verstehen 
tue ich diese Zeile nicht.

Gruß
Andreas

von Karl H. (kbuchegg)


Lesenswert?

Das hier
1
      int16_t (*func)(int16_t);
sind Zeiger auf Funktionen. Im Original werden keine Werte 
abgespeichert, sondern abhängig vom Text Funktionen aufgerufen. Das ist 
was völlig anderes. Das ist schon etwas aus der gehobeneren Kategorie 
der Programmierung.
func ist ein Zeiger auf eine Funktion und diese Funktion will einen 
int16_t als Argument haben und liefert als Returnwert einen int16_t.



Kannst du mir erklären, warum du hier
1
               vline++ = strchr(sline, 0x3D);
0x3D schreibst? Warum nicht einfach
1
               vline++ = strchr(sline, '=');
Wäre das zu einfach? Zu Lesbar? Zu wenige Möglichkeiten Fehler zu 
machen? Nach dem Motto: "Es war schwer zu schreiben, also soll es auch 
schwer zu lesen sein!"
Im übrigen hat die ganze Anweisung undefiniertes Verhalten.
1
               vline = strchr(sline, '=');
2
               vline++;
Und abfragen, ob '=' überhaupt gefunden wurde, sollte man dann auch 
noch.
1
               vline = strchr(sline, '=');
2
               if( vline != NULL )
3
               { 
4
                 vline++;
5
                 *varpointer = atoi(vline);
6
               }

Das EOL entfernen kannst du dir hingegen sparen. atoi hört sowieso mit 
der Konvertierung auf, sobald es auf das erste Zeichen stösst, welches 
nicht mehr zu einer Zahl gehören kann. Solange dein String sauber 
aufgebaut ist, mit einem \0 Zeichen als letztes Zeichen (welches ein 
String sowieso immer haben sollte, denn sonst ist es kein String), kann 
dir da also gar nichts passieren. Aber wenn du schon ersetzen willst, 
dann bitte auch immer prüfen, ob ein gesuchtes Zeichen auch wirklich 
gefunden wurde! Alles andere ist nämlich ein Spiel mit dem Feuer
1
               // replace EOL to 0x00 (use as string)               
2
               vline = strchr(sline, 0x0D);
3
               if( vline != NULL )
4
                 *vline = 0x00;


Die ganze Schleife kann man im übrigen auch ohne von hinten durch die 
Brust ins Auge programmieren. Zum einen kann man durch umdrehen der 
strcasecmp_P Abfrage den continue wegbringen, zum anderen könntest du 
dich mit dir einigen, ob du jetzt über einen Index zugreifst, oder doch 
einen Pointer weiterzählen willst.
1
            for (i=0; i < sizeof(keywords) / sizeof(keyword_t); i++) {
2
               if (strcasecmp_P(sline, keywords[i].text) == 0) {
3
                 // gefunden!
4
               }
5
            }
beides gleichzeitig ist irgendwie doppelt gemoppelt.

von Andreas B. (bitverdreher)


Lesenswert?

Hallo Karl-Heinz,
Danke für die Tips. Ich gehe das jetzt mal alles durch. Ich habe vor 
einigen Jahren mal etwas in C programmiert. Mir fehlt da einfach die 
Übung.
Das Ende des Strings habe ich nicht beachtet, da der String ja aus einer 
Datei ausgelesen wird. Spätestes beim EOF ist ja das Ende des Strings. 
Aber Du hast recht, ich werde das etwas sauberer programmieren.

Kannst Du diese Zeile (int16_t (*func)(int16_t);) mal etwas erläutern?
Daß *func ein Pointer auf eine Funktion ist, habe ich schon verstanden. 
Das ist im Datentyp der Struktur ja so definiert. Ist das nur eine 
Deklaration für func als Funktionspointer? Wenn ja, warum definiert man 
das nicht am Anfang der Funktion selbst?

Gruß
Andreas

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Andreas B. schrieb:
> Kannst Du diese Zeile (int16_t (*func)(int16_t);) mal etwas erläutern?
1
% cdecl
2
Type `help' or `?' for help
3
explain unsigned short (*fp)(unsigned short)
4
declare fp as pointer to function (unsigned short) returning unsigned short

(Ich habe mal "fp" statt "func" benutzt, da "func" bei cdecl ein
Schlüsselwort ist.  Die Zeile mit "explain" ist meine Eingabe, die mit
"declare" die Ausgabe von cdecl.)

von Andreas B. (bitverdreher)


Lesenswert?

Hallo Jörg,
Danke Dir!
Also doch einen Delaration. Es hatte mich nur irritiert, daß diese 
Deklaration im Ursprungscode mitten im Code stand.

Gruß
Andreas

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Andreas B. schrieb:
> Es hatte mich nur irritiert, daß diese
> Deklaration im Ursprungscode mitten im Code stand.

In C99 sind Variablendefinitionen mitten im Code zulässig (und in C++ 
sowieso).

Am Anfang eines Blocks (eines in {} stehenden Abschnittes, also auch in 
einem if-Statement oder in einer for-Schleife o.ä.) sind sie das in C 
aber schon immer, auch wenn das hier nicht der Fall ist. o.g. Code lässt 
sich also nur von einem C99-toleranten Compiler übersetzen.

Ob man Variablen so definieren möchte, ist in erster Linie eine 
stilistische Frage; es gibt Leute, die der Ansicht sind, Variablen 
sollten so spät wie möglich definiert werden, und es gibt Leute, die der 
Ansicht sind, daß alle in einer Funktion verwendeten Variablen an deren 
Anfang definiert werden sollten. Letzteres hilft beim Quelltextlesen den 
Stackbedarf einzuschätzen.

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.