Forum: PC-Programmierung Size von Vektor mit Zeigern auf char-arrays ermitteln


von Lutz S. (lutzs)


Angehängte Dateien:

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.

: Bearbeitet durch User
von leo (Gast)


Lesenswert?

Lutz S. schrieb:
> (sizeof commands)/(sizeof &commands[0])
>
> Das sieht nicht elegant aus,

... und ist auch flasch.
1
(sizeof commands)/(sizeof commands[0])

Wenn sich die Anzahl der commands aendert, wuerde ich auf einen 
Null-Pointer am Ende der Liste pruefen.

leo

von leo (Gast)


Lesenswert?

Lutz S. schrieb:
> Da sich die Anzahl der Kommandos ändern kann (im Original ca. 60)

Du kannst dir auch bsearch(3) anschauen.

leo

von zitter_ned_aso (Gast)


Lesenswert?

Lutz S. schrieb:
> if (strcmp(input,commands[i])==0) r = i;

im Prinzip kannst hier dann mit "break;" die for-Schleife abbrechen. Du 
hast ja einen Treffer gefunden. Alle weiteren Iterationen sind doch 
nicht mehr nötig oder?

von zitter_ned_aso (Gast)


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.

von Dirk B. (dirkb2)


Lesenswert?

Lutz S. schrieb:
> // gibt Integerwert für command zurück, 0 wenn nicht gefunden

Du kannst auch r+1 zurück geben, dann sparst du das "" oder nimmst -1 
für nicht gefunden

von g457 (Gast)


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.

von zitter_ned_aso (Gast)


Lesenswert?

Lutz S. schrieb:
> {"","on",

der leere String am Anfang wird ja nicht benutzt. Vielleicht wäre das 
ein möglicher Indikator für's Arrayende.

von Bernd B. (bbrand)


Lesenswert?

Lutz S. schrieb:
> Das sieht nicht elegant aus

Stimmt zwar, hat aber den Vorteil, dass es schon zur Kompilierzeit 
berechnet werden kann.

leo schrieb:
> ... und ist auch flasch.

Macht in dem Fall aber keinen Unterschied, da man in beiden Fällen die 
Größe eines Zeigers bekommt. Genausogut könnte man auch sizeof(char*) 
schreiben.

Gruß,
Bernd

von Dirk B. (dirkb2)


Lesenswert?

zitter_ned_aso schrieb:
> der leere String am Anfang wird ja nicht benutzt. Vielleicht wäre das
> ein möglicher Indikator für's Arrayende.

Dann müsste man ein strlen() machen. Ein NULL ist da eindeutiger 
(ähnlich dem '\0' beim String)

von Dirk B. (dirkb2)


Lesenswert?

Bernd B. schrieb:
> Genausogut könnte man auch sizeof(char*)

wenn man (sizeof arrayname)/(sizeof arrayname[0])
oder (sizeof arrayname)/(sizeof *arrayname)
nimmt, braucht man sich keine Gedanken um den Typ machen.
Egal ob int, char, char* oder struct. Das passt immer.

von Wilhelm M. (wimalopaan)


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.

: Bearbeitet durch User
von zitter_ned_aso (Gast)


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
}

von Wilhelm M. (wimalopaan)


Lesenswert?

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

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

zitter_ned_aso schrieb:
> command=42;

So ein Schwachsinn kann man wohl nur in C prodizieren ...

von Lutz S. (lutzs)


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
}

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


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.

von Lutz S. (lutzs)


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
}

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


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.

von Lutz S. (lutzs)


Lesenswert?

Das war mehr so eine Fingerübung auf dem PC. Im AVR würde ich die 
Variante oben mit der for-Schleife und der Berechnung nehmen.

Aber deine Anmerkungen sind natürlich richtig.

von Wilhelm M. (wimalopaan)


Lesenswert?

Lutz S. schrieb:
> Im AVR würde ich die
> Variante oben mit der for-Schleife und der Berechnung nehmen.

Ich hoffe, Du hast verstanden, warum das ggf. ein Problem darstellt 
(s.o.).

von Lutz S. (lutzs)


Lesenswert?

Meinst du das?

'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" ...'

von Wilhelm M. (wimalopaan)


Lesenswert?

Lutz S. schrieb:
> Meinst du das?
>
> '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" ...'

Ja.

Und aber auch:

Beitrag "Re: Size von Vektor mit Zeigern auf char-arrays ermitteln"

von Lutz S. (lutzs)


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
}

von Wilhelm M. (wimalopaan)


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!

von Lutz S. (lutzs)


Lesenswert?

Das wollte das aufrufende Programm so, kann man natürlich auf -1 
abändern.

Wo kann man solche Konventionen nachlesen?

Edit: z.B. hier: 
https://www.programming-idioms.org/about#about-block-language-coverage

: Bearbeitet durch User
von Dirk B. (dirkb2)


Lesenswert?

Lutz S. schrieb:
> Wo kann man solche Konventionen nachlesen?

Eigentlich im K&R.
Dort werden die Ideen aus erster Hand vermittelt.

Da 0 ein gültiger Index für ein Array ist, macht es Aufwand diese zu 
umgehen.

von Lutz S. (lutzs)


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.'

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Lutz S. schrieb:
> 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.'

s.a. POSIX-API

von Rolf M. (rmagnus)


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.

von Wilhelm M. (wimalopaan)


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.

von Wilhelm M. (wimalopaan)


Lesenswert?

Wilhelm M. schrieb:
>> ssize_t find(const char* const* const it, const char* const pattern) {
>> >
>> Nebenan sprichst du dich doch genau dafür aus.
>
> Ja, absolut ...

Große Unterlassungssünde meinerseits!
Bei den anderen Funktionen hatte ich das ja auch gemacht ...

von Dirk B. (dirkb2)


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.

von Lutz S. (lutzs)


Lesenswert?

Vor ein paar Minuten hatte ich das geändert ;-)
1
while (commands[i]!=NULL){
2
  if (strcmp(input,commands[i++])==0) return i;
3
}
4
return -1;

von Dirk B. (dirkb2)


Lesenswert?

Lutz S. schrieb:
> Vor ein paar Minuten hatte ich das geändert ;-)
>
>
1
> while (commands[i]!=NULL){
2
>   if (strcmp(input,commands[i++])==0) return i;
3
> }
4
> return -1;
5
>

Das gibt aber 1 für den ersten Eintrag.
0 wird nie erreicht.

von Wilhelm M. (wimalopaan)


Lesenswert?

Dirk B. schrieb:
> Du solltest  auf eine kopfgesteuerte Schleife ausweichen, die
> funktioniert noch, auch wenn die Liste leer ist.

Stand schon da:

Beitrag "Re: Size von Vektor mit Zeigern auf char-arrays ermitteln"

oder

Beitrag "Re: Size von Vektor mit Zeigern auf char-arrays ermitteln"

von Lutz S. (lutzs)


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.

von Dirk B. (dirkb2)


Lesenswert?

Wilhelm M. schrieb:
> Stand schon da:

Ja. Aber Lutz hatte die do-while Variante gewählt (gezeigt).
Ein Vorteil der for bzw. while-Schleife wurde dort auch nicht genannt.

von Lutz S. (lutzs)


Lesenswert?

Besonders an Wilhelm noch einmal vielen Dank für die geduldigen 
Erläuterungen.

Mir ist erst jetzt bei nochmaliger Durchsicht aufgegangen warum die 
Schleife

for(size_t i = 0; it[i]; ++i)

am  Ende der Liste terminiert.

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.