Size von Vektor mit Zeigern auf char-arrays ermitteln

OP #6096055
Lesenswert?

Hallo,

ich möchte in dieser Funktion gern prüfen ob der übergebene Wert einem 
bekannten Kommando entspricht.

Diese sind in 'commands' abgelegt, einem Array aus Pointern auf 
char-Arrays.
1
int commandindex(char *input) // gibt Integerwert für command zurück, 0 wenn nicht gefunden
2
{
3
  const char *commands[] = {"","on","off","left","right","up","down","step","back","erase"};
4

5
  int r = 0;
6
  for (int i=1;i<(sizeof commands)/(sizeof &commands[0]);i++){
7
    if (strcmp(input,commands[i])==0) r = i;
8
  }
9
  return r;
10
}

Da sich die Anzahl der Kommandos ändern kann (im Original ca. 60) möchte 
ich sie nicht fest eintragen sondern berechnen (Speicher und Zeit sind 
kein Problem).

Soweit funktioniert auch alles, damit das aber sowohl auf einem PC 
(Zeiger sind 4 Byte gross) als auch im Atmel Studio für 8 Bit AVR(Zeiger 
sind 2 Byte gross) klappt  bin ich bisher auf keine bessere Lösung 
gekommen als die Größe des Arrays für die Schleife so zu berechnen:

(sizeof commands)/(sizeof &commands[0])

Das sieht nicht elegant aus, gibt es da eine bessere Lösung?

Generell bin ich auch für Tips dankbar wie man die Funktion anders 
realisieren kann, lerne da gern was dazu.
Angehängte Dateien:
Gast #6096089
Lesenswert?

Und wenn du die Größe von deinem Array nicht berechnen willst, dann 
kannst du ja das Ende vom Array irgendwie markieren ("end").

 Und solange durch Array iterieren bis du einen Treffer oder eben das 
markierte Ende erreicht hast.

Und wenn du die Daten noch sortierst (oder in sortierter Reihenfolge 
selbst einträgst), dann kannst du  schneller suchen.

leo schrieb:
> Du kannst dir auch bsearch(3) anschauen.
Gast #6096093
Lesenswert?

>> Das sieht nicht elegant aus,
>
> ... und ist auch flasch.
>
> (sizeof commands)/(sizeof commands[0])

..zweckmäßigerweise baut mach sich dafür dann auch noch gleich ein 
#define wie z.B. ARRAY_SIZE, dann gibts weniger Tippfehler und die 
Semantik wir auch gleich klar.
#6096154
Lesenswert?

Die Längenberechnung mit sizeof funktioniert nur, wenn der 
Array-Bezeichner noch nicht zur einem Zeigertyp zerfallen (decay-ed) 
ist. Daher ist diese Längenberechnung etwas "wackeling" ...

Besser mit Sentinel arbeiten. Etwa:
1
#include <stdio.h>
2
#include <stdbool.h>
3
#include <assert.h>
4
#include <string.h>
5

6
size_t commandindex(const char * const input) {
7
        assert(input);
8
  const char* const commands[] = {"on","off","left","right","up","down","step","back","erase", NULL};
9

10
    for(size_t i = 0; commands[i]; ++i) {
11
        if (strcmp(input, commands[i]) == 0) {
12
            return i;
13
        }
14
    }
15
    return -1;    
16
}
17

18
int main(const int argc, const char* const* argv) {
19
    assert(argc > 1);
20
    return commandindex(argv[1]);
21
}

Auf die Spezifika von AVR (PROGMEM, size_t vs uint8_t, etc.) bin ich 
hier nicht eingegangen, da Du das in PC-Prog gepostet hast.
Gast #6096207
Lesenswert?

Eigentlich könntest du doch deine Befehle durchnummerieren und per 
ID-Nummer identifizieren. Ganz ohne String und strcmp(...).

Nur C (und wahrscheinlich kritikwürdig ;-)
1
#include <stdio.h>
2
#include <stdbool.h>
3

4
typedef enum{OFF, ON, LEFT, RIGTH, TOP, DOWN, END} command_t;
5

6
bool is_legal_command(command_t command){
7
    for(command_t i=OFF; i!=END; i++){
8
        if(i==command)
9
            return true;
10
    }
11
    
12
    return false; 
13
}
14

15

16
int main(void){
17

18
    command_t command=LEFT;
19
    
20
    if(is_legal_command(command))
21
        puts("do something");
22
    else
23
        puts("wrong command");
24

25
    command=42;
26

27
    if(is_legal_command(command))
28
        puts("do something");
29
    else
30
        puts("wrong command");
31

32
    return 0;
33
}
OP #6096549
Lesenswert?

Wilhelm M. schrieb:

> Ich denke, er will die commands aus den Strings (UI) ableiten.

Ja, so ist es.

Aber das Problem interessiert mich auch ganz allgemein. So eine Liste 
mit Zeigern auf Strings ist ja für vieles praktisch und dass die 
Bestimmung der Anzahl der Elemente da gar nicht so einfach ist hatte 
mich erstaunt.

Ich habe jetzt verschiedene Anregungen umgesetzt, danke an alle. Die 
Berechnung der Anzahl der Elemente ist etwas geändert und die Auswertung 
bricht nach dem Treffer ab. Ausserdem entfällt die Hilfsvariable r:
1
int commandindex(char *input) // gibt Integerwert für command zurück, 0 wenn nicht gefunden
2
{
3
  const char *commands[] = {"","on","off","left","right","up","down","step","back","erase"};
4

5
  for (int i=1;i<(sizeof commands)/(sizeof *commands);i++){
6
    if (strcmp(input,commands[i])==0) return i;
7
  }
8
  return 0;
9
}
#6096577
Lesenswert?

Lutz S. schrieb:
> Ja, so ist es.
>
> Aber das Problem interessiert mich auch ganz allgemein. So eine Liste
> mit Zeigern auf Strings ist ja für vieles praktisch und dass die
> Bestimmung der Anzahl der Elemente da gar nicht so einfach ist hatte
> mich erstaunt.

Zitat: B. Stroustrup:

"Rohe C-Arrays sind so dumm, dass sie ihre eigene Länge nicht wissen."

Folgendes Beispiel: nehemn wir an, Du möchtests das strcmp() auf eine 
Menge von String anwenden, wie in Deinem Problem. Du schreibst Dir dafür 
ein Funktion find() (s.u.). Find arbeitet hier mit einem Wächterelement 
(sentinel), daher raucht find nicht explizit die Länge des Arrays 
wissen, sondern der Abbruch der Schleifer ergibt sich in der schleife 
aus dem letzten Element. Das geht natürlich nur für DT, die einen 
ungültigen Wert in ihrem Wertebereich haben, etwa die Zeigertypen 
haben ja 0 dazu.

Die andere Variante find_wrong() wendet eine falsch (hier bekommt man 
übrigend auch eine Warnung vom Compiler) Berechnung der Arraygröße an. 
Den bei der Parameterübergabe an find_wrong() zerfällt der 
Arraybereichner commands zu einem Zeiger. Daher wird bei sizeof(array) 
die größe eines Zeigers bestimmt. sizeof(array[0]) berechnet auch die 
Größe eines Zeigers (const char*). Daher ist der Quotient immer 1.

Vielleicht hast Du nun meine Bemerkung von oben verstanden.
1
#include <stdio.h>
2
#include <stdbool.h>
3
#include <assert.h>
4
#include <string.h>
5

6
ssize_t find_wrong(const char* const* array, const char* pattern) {
7
    assert(array);
8
    const size_t length = sizeof(array) / sizeof(array[0]); // falsch
9
    for(size_t i = 0; i < length; ++i) {
10
        assert(array[i]);
11
        if (strcmp(pattern, array[i]) == 0) {
12
            return i;
13
        }
14
    }
15
    return -1;    
16
    
17
} 
18

19
ssize_t find(const char* const* it, const char* pattern) {
20
    assert(it);
21
    for(size_t i = 0; it[i]; ++i) {
22
        assert(it[i]);
23
        if (strcmp(pattern, it[i]) == 0) {
24
            return i;
25
        }
26
    }
27
    return -1;    
28
    
29
} 
30

31
ssize_t commandindex(const char* const input) {
32
    assert(input);
33
  const char* const commands[] = {"","on","off","left","right","up","down","step","back","erase", NULL};
34
    return find(commands, input);
35
}
36

37
ssize_t commandindex2(const char* const input) {
38
    assert(input);
39
  const char* const commands[] = {"","on","off","left","right","up","down","step","back","erase"};
40
    return find_wrong(commands, input);
41
}
42

43
int main(const int argc, const char* const* argv) {
44
    assert(argc > 1);
45
    return commandindex2(argv[1]);
46
}

Es gibt viele Lösungen aus diesem Dilemma:

1) Vermeide C (geht wohl bei Dir nicht, da Du AVR im Auge hast)
2) sentinel benutzen
3) zu jedem Array die Länge übergeben (zwei Parameter der Funktion 
daraus machen.
4) Ein struct benutzen
5) ...

Wächterelement (sentinel) geht natürlich nur bei DT, die das zulassen 
(s.o.).

BTW: das ist der Grund, warum die andere Signatur von main(int, const 
char**) so aussieht.
OP #6096613
Lesenswert?

Danke für deine Erläuterungen, Wilhelm.

Ja, in dem Fall möchte ich wirklich C verwenden. Das Programm läuft auf 
einem  kleinen ATTiny. Auf dem PC teste ich gern mal kleinere Schnipsel 
dazu.

Hier noch eine Variante ohne Größenbestimmung, mit Sentinel. Diesmal mit 
do while:
1
int commandindex(char *input) // gibt Integerwert für command zurück, 0 wenn nicht gefunden
2
{
3
  #define END "_end_"
4
  const char *commands[] = {"on","off","left","right","up","down","step","back","erase",END};
5
  int i=0;
6
  do {
7
    if (strcmp(input,commands[i++])==0) return i;
8
  } while (strcmp(END,commands[i])!=0);
9
  return 0;
10
}
#6096619
Lesenswert?

Lutz S. schrieb:

> Ja, in dem Fall möchte ich wirklich C verwenden. Das Programm läuft auf
> einem  kleinen ATTiny. Auf dem PC teste ich gern mal kleinere Schnipsel
> dazu.

Gut, dann packe die Stringkonstanten ins PROGMEM, sonst hast Du bald 
kein RAM mehr.

> Hier noch eine Variante ohne Größenbestimmung, mit Sentinel. Diesmal mit
> do while:
>
>
1
> int commandindex(char *input) // gibt Integerwert für command zurück, 0 
2
> wenn nicht gefunden
3
> {
4
>   #define END "_end_"
5
>   const char *commands[] = 
6
> {"on","off","left","right","up","down","step","back","erase",END};
7
> 
8
>   int i=0;
9
>   do {
10
>     if (strcmp(input,commands[i++])==0) return i;
11
>   } while (strcmp(END,commands[i])!=0);
12
>   return 0;
13
> 
14
> }
15
>

Das ist eine Verschlimmbesserung, weil:

1) für das Sentinel mehr Platz (ein ganzer String) verbraucht wird als 
nötig
2) je Iteration 2x strcmp()
3) nicht idiomatisch C.
4) im eigentlichen Sinne ist das kein Wächterlement.
OP #6096650
Lesenswert?

D.h. so lange ich commands nicht an eine Funktion übergebe besteht kein 
Problem durch die Berechnung?

Die letzte Variante habe ich jetzt noch einmal überarbeitet:
1
int commandindex(char *input) // gibt Integerwert für command zurück, 0 wenn nicht gefunden
2
{
3
  const char *commands[] = {"on","off","left","right","up","down","step","back","erase",NULL};
4
  int i=0;
5
  do {
6
    if (strcmp(input,commands[i++])==0) return i;
7
  } while (commands[i]!=NULL);
8
  return 0;
9
}
#6096651
Lesenswert?

Lutz S. schrieb:
> D.h. so lange ich commands nicht an eine Funktion übergebe besteht kein
> Problem durch die Berechnung?

Ja.

> Die letzte Variante habe ich jetzt noch einmal überarbeitet:
>
>
1
> int commandindex(char *input) // gibt Integerwert für command zurück, 0 
2
> wenn nicht gefunden
3
> {
4
>   const char *commands[] = 
5
> {"on","off","left","right","up","down","step","back","erase",NULL};
6
>   int i=0;
7
>   do {
8
>     if (strcmp(input,commands[i++])==0) return i;
9
>   } while (commands[i]!=NULL);
10
>   return 0;
11
> }
12
>

return 0 als nicht-gefunden ist auch sehr-unidiomatisch!
OP #6097085
Lesenswert?

Da muss ich meine Nase mal wieder in K&R stecken.

Steht noch hier, kam mal 64 DM ;-)
... und wurde in dem Fall auch gleich fündig, Seite 68: 'Da in C 
Vektoren bei Position 0 beginnen sind Indexwerte 0 oder positiv und ein 
negativer Wert wie -1 ist praktisch um Mißerfolg anzuzeigen.'
#6097145
Lesenswert?

Wilhelm M. schrieb:
> ssize_t find(const char* const* it, const char* pattern) {

Wenn du hier const so intensiv einsetzt, warum dann nicht konsequent?
1
ssize_t find(const char* const* const it, const char* const pattern) {

Nebenan sprichst du dich doch genau dafür aus.

Lutz S. schrieb:
> D.h. so lange ich commands nicht an eine Funktion übergebe besteht kein
> Problem durch die Berechnung?

Es besteht kein Problem, solange du die Längenberechnung auf das Array 
selbst anwendest und nicht auf einen Zeiger. Bei Übergabe an eine 
Funktion wird automatisch ein Zeiger draus, daher funktioniert es 
innerhalb der Funktion nicht.
#6097155
Lesenswert?

Rolf M. schrieb:
> Wilhelm M. schrieb:
>> ssize_t find(const char* const* it, const char* pattern) {
>
> Wenn du hier const so intensiv einsetzt, warum dann nicht konsequent?
>
>
1
> ssize_t find(const char* const* const it, const char* const pattern) {
2
>
>
> Nebenan sprichst du dich doch genau dafür aus.

Ja, absolut ...

> Lutz S. schrieb:
>> D.h. so lange ich commands nicht an eine Funktion übergebe besteht kein
>> Problem durch die Berechnung?
>
> Es besteht kein Problem, solange du die Längenberechnung auf das Array
> selbst anwendest und nicht auf einen Zeiger. Bei Übergabe an eine
> Funktion wird automatisch ein Zeiger draus, daher funktioniert es
> innerhalb der Funktion nicht.

Richtig.
Allerdings ist die Anzahl der Beiträge hier sehr hoch, wo das irgendwann 
mal zum Problem wird. Den oft fehlt das Verständnis dafür, was der 
Unterscheid zwischen einem Array-Bezeichner und einem Zeiger ist bzw. 
wann dieser Zerfall stattfindet. Aus diesem Grunde halte ich es für 
sinnvoll, diese wackelige Konstrukt ganz zu vermeiden.

BTW: der einzige Weg, das Zerfallen zu verhindern, ist mit templates zu 
arbeiten. Denn hier kann der Compiler die Länge als NTTP ableiten.
#6097229
Lesenswert?

Du solltest  auf eine kopfgesteuerte Schleife ausweichen, die 
funktioniert noch, auch wenn die Liste leer ist.

Hier sieht man es sofort, da die Liste direkt darüber definiert ist.

Aber so eine Funktion ist ja ganz praktisch.
Die kann man universeller machen, indem man (den Zeiger auf) die Liste 
mit übergibt.
OP #6097289
Lesenswert?

Ja, das Programm für das ich die Funktion geschrieben habe will die 
Kommandos von 1 bis x indiziert. 0 war ja für 'nicht gefunden' 
vorgesehen.

Falls man das von 0 will muss der Post-Inkrement weg und i eine Zeile 
tiefer erhöht werden.

Aber ansonsten gefällt mir das mittlerweile gut, viel gelernt von euch.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren