Hallo,
ich habe ein verständnisproblem zu dem Befehl sizeof() in c.
z.B.
const uint8_t x[] = {0x00, 0x01, 0x02};
sizeof(x) ergibt 24. Um die Anzahl der elemente im array zu ermittlen,
mache ich sizeof(x)/(sizeof(uint8_t));
Jetzt habe ich zwei arrays
const char y[] = {"abc"};
const char z[3] = {"abc"};
sizeof(y) ergibt 4 und sizeof(z) ergibt 3. Warum bekomme ich eine größe
von 4, wenn ich die Länge des Arrays nicht direkt mit angebe?
Das sizeof(x) sollte 3 ergeben.
Die Anzahl der Elemente bekommst du besser mit sizeof(x)/sizeof(x[0])
Dann spielt der Typ keine Rolle mehr.
Mach das nicht bei Funktionsparametern.
Die Länge eines Strings bekommst du mit der Standardfunktion strlen().
Das funktioniert aber nicht bei deinem z, weil die Funktion die '\0'
sucht.
Dirk B. schrieb:> Das funktioniert aber nicht bei deinem z, weil die Funktion die '\0'> sucht.
???
strlen(y) und strlen(z) ergeben beide 3 bei mir.
Ist das jetzt nur Zufall?
@Jens
...und wenn Du Dich auf den Kopf stellst, sieht alles verkehrt herum
aus.
Oder anders ausgedrückt: Was haben strlen und sizeof miteinander zu tun?
Sebastian S. schrieb:> Oder anders ausgedrückt: Was haben strlen und sizeof miteinander zu tun?
nichts, aber mein Problem hat man ja in meinem ersten Beitrag
hoffentlich verstanden. Dann wurde mir gesagt, was ich falsch gemacht
habe. Das habe ihc jetzt verbessert. Aber es wurde auch gesagt, dass
strlen(z) nicht geht. Wenn ich das jedoch ausführe, kommt auch 3 heraus.
Das war jetzt eine neue frage warum. Oder sollte ich dafür extra einen
neuen Beitrag eröffnen? Ich denke nicht.
Es ist ein array of char ohne nullterminierung.
Strlen funktioniert nur zufällig weil hinter dem array z eine weitere
Variable ist welche auf null gesetzt ist.
Im realen Code ist dem dann nicht so und es kommt dann auf dem Zufall an
was zurückgeliefert wird.
Chris schrieb:> Es ist ein array of char ohne nullterminierung.> Strlen funktioniert nur zufällig weil hinter dem array z eine weitere> Variable ist welche auf null gesetzt ist.> Im realen Code ist dem dann nicht so und es kommt dann auf dem Zufall an> was zurückgeliefert wird.
Ahso,
daran wird es gelegen haben. Wenn ich nur
1
constunsignedchary[]={"abc"};
2
constunsignedcharz[3]={"abc"};
3
printf("%d %d",strlen(y),strlen(z))
mache, bekomme ich als ausgabe 3 und 18 heraus.
Vielen dank für den Hinweis
Jens schrieb:> const uint8_t x[] = {0x00, 0x01, 0x02};>> sizeof(x) ergibt 24
Eventuell habe ich das falsch in Erinnerung, aber liefert sizeof() nicht
die Größe in Bytes?
Das Array hat 3 Elemente die jeweils ein Byte groß sind, daher würde ich
behaupten sizeof(x)= 3.
Oder ist uint8_t 8 Bytes groß weil das ein 64 bit System ist und der
Compiler macht sich da keine Mühe irgendwas kleineres zu
verwenden/optimiert?
Soduko schrieb:> Oder ist uint8_t 8 Bytes groß weil das ein 64 bit System ist und der> Compiler macht sich da keine Mühe irgendwas kleineres zu> verwenden/optimiert?
Eine Größe ungleich drei ist niemals zulässig.
Jemand schrieb:> Soduko schrieb:>> Oder ist uint8_t 8 Bytes groß weil das ein 64 bit System ist und der>> Compiler macht sich da keine Mühe irgendwas kleineres zu>> verwenden/optimiert?>> Eine Größe ungleich drei ist niemals zulässig.https://www.youtube.com/watch?v=FFAbhi6xnCo
leo
Soduko schrieb:> Oder ist uint8_t 8 Bytes groß weil das ein 64 bit System ist und der> Compiler macht sich da keine Mühe irgendwas kleineres zu> verwenden/optimiert?
Ein Compiler, der keinen Typ für exakt 8 Bits hat, darf keinen Typ
uint8_t definieren, sondern nur uint_least8_t.
Jemand schrieb:> Eine Größe ungleich drei ist niemals zulässig.
Naja, der Code ist ja nur bruchstückhaft. Wer weiss, wer wie warum
uint8_t definiert hat.
Aber ja, wenn sizeof(char) ungleich sizeof(uint8_t) ist, dann läuft da
einiges schief.
A. S. schrieb:> Aber ja, wenn sizeof(char) ungleich sizeof(uint8_t) ist, dann läuft da> einiges schief
In der Tat: sizeof(char) ist per Definition immer 1, denn sizeof liefert
die Größe in Vielfachen von char, und char ist nunmal genau 1 char groß.
Außerdem ist char immer minimal 8bit groß. Einen kleineren Datentyp kann
es nicht geben. Wenn char also genau 8 bit ist, ist uint8_t ein Alias
dafür (bzw. für unsigned char). Ist char mehr als 8 bit, kann es kein
uint8_t geben. Somit ist sizeof (uint8_t) immer entweder ein Compiler
Fehler oder 1.
Jens schrieb:> daran wird es gelegen haben. Wenn ich nur>> const unsigned char y[] = {"abc"};> const unsigned char z[3] = {"abc"};> printf("%d %d", strlen(y), strlen(z))>> mache, bekomme ich als ausgabe 3 und 18 heraus.> Vielen dank für den Hinweis
Das Ergebnis von strlen(z) ist dem Zufall geschuldet.
strlen geht Zeichen für Zeichen den Speicher durch und hört erst bei der
ersten Null (nicht das Zeichen '0') auf zu zählen.
In Deinem Fall steht im Speicher hinter dem Array z irgendwelches
anderes Zeug, das halt irgendwo zufälligerweise Null ist.
Jens schrieb:> const unsigned char y[] = {"abc"};> const unsigned char z[3] = {"abc"};> printf("%d %d", strlen(y), strlen(z))>> mache, bekomme ich als ausgabe 3 und 18 heraus.
%d (bei printf) ist für int da.
Der Rückgabetyp von strlen() ist aber size_t.
Der richtige Formatspecifier ist %zu
- z für size_t
- u für unsigned
Die Größe von size_t ist so gewählt, das die Größe jedes Elements dort
abgelegt werden kann.
Auf einem 32-Bit System reicht dafür eine 32-Bit Variable aus. Das passt
meist mit dem unsigned int.
Auf einem 64-Bit System aber nicht mehr. Und ein int ist auch dort meist
32-Bit groß.
"meist", weil es auch anders sein kann.
Darauf musst du als Programmierer aber achten, sonst läuft dein Programm
irgendwann (z.B. nach System-/Compiler-/Versions-update) nicht mehr
richtig)
Der Compiler kann das erkennen und eine Warnung ausgeben. Wenn du ihn
läßt bzw. entsprechend einstellst.
Rufus Τ. F. schrieb:> In Deinem Fall steht im Speicher hinter dem Array z irgendwelches> anderes Zeug, das halt irgendwo zufälligerweise Null ist.
Es kann auch sein, das es in der Debug-Version immer richtig ist und in
der Release-Version dann daneben geht.
Jens schrieb:> const char y[] = {"abc"};> const char z[3] = {"abc"};
Der Grund, warum sizeof() hier unterschiedliche Größen liefert, ist
relativ einfach.
Bei der Initialisierung eines char-Arrays mit einem C-String wird immer
versucht, noch hinter dem String die '\0' als Terminierung anzubringen.
Aber nur, wenn es auch passt! Sonst wird auf die Terminierung
(stillschweigend) verzichtet.
Da bei y[] keine Größe angegeben ist, wird das Array ausreichend groß
mit Länge 4 angelegt und mit "abc" und anschließender Terminierung
initialisiert.
Bei z[3] ist die Länge 3 vorgegeben und es wird daher darauf verzichtet,
das Terminierungszeichen mit in das Array reinzuschreiben. Das ist kein
Fehler in C, sondern vom C-Standard so gewollt. Leider ist es aber
äußerst unschön: Wir erhalten hier einen unterminierten String, der so
in 99% aller Fälle bestimmt nicht erwünscht ist. Meines Erachtens wäre
eine Fehlermeldung hier angebrachter. Geändert kann das Verhalten aber
nicht mehr, denn es ist schon seit Jahrzehnten so Standard.
Es hat schon seinen Sinn.
Ein Beispiel eines Codes
#define match_(x) else if \
( id[0]==x[0]&&id[2]==x[2] \
&&strlen(x)==len \
&&id[len-2]==x[len-2] \
&&(__builtin_constant_p (x) ? !strcmp_P(id,x) : !strcmp(id,x)) \
)
#define match(x) match_(PSTR(#x))
#define Match(x) if(0); match(x)
Hier werden Ausschnitte aus dem String als byte oder eventuall auch als
word/dword (optimiert, nicht hier) verglichen.
Wenn es jetzt einen Error geben würde wegen des fehlenden Null oder weil
der ganze String nicht verwendet wird, sondern nur Ausschnitte, dann
wären viele Optimierungen in C nicht möglich. Im Prinzip wird hier das
String mit 4 Konstanten gecheckt bevor ein strcmp angewendet wird.
Bei einer Variablen ist es auch optimiert. Strings mit nur einem Zeichen
werden vorher abgefangen, deshalb fehlt dieser Check für len.
chris schrieb:> Ein Beispiel eines Codes
Darin sehe ich ein strlen. Das auf einen nicht-nullterminierten String
loszulassen ist ein eindeutiger Fehler.
Rufus Τ. F. schrieb:> Darin sehe ich ein strlen. Das auf einen nicht-nullterminierten String> loszulassen ist ein eindeutiger Fehler.
Wenn ich das Macro richtig verstehe, soll x ein nullterminierter String
und id[] dabei ein eventuell nicht-terminierstes Byte-Array sein.
Aber so ganz erschließt sich mir nicht, wo da eine Optimierung sein
soll. Erst Prüfung, ob erstes und drittes Zeichen gleich, dann strlen(),
dann am Schluss doch strcmp(). strlen() kostet auch jede Menge Zeit.
Aber egal, was hat der Beitrag von chris mit meinem Argument zu tun,
dass eine Initialisierung mit einem C-String "abc" besser immer mit
Terminierung durchgeführt werden sollte? Man würde auf jeden Fall viele
Fehler, die auch bestimmt deswegen in der Vergangenheit aufgetreten
sind, direkt durch eine ensprechende Compiler-Meldung erkennen.
const char z[3] = {"abc"};
Ein strlen() auf einen nicht nullterminierten String ist Ansatzpunkt
für Hacker. Geschickt ausgenutzt kann die Programmkontrolle übernommen
werden oder Zugriff auf eigentlich geschützte Daten ermöglicht werden.
Meine Meinung: Programmierer die das wiederholt machen, sollten eine
Kündigung bekommen!
Ein Match(setup), wobei setup zu "setup" verwandelt wird, und angenommen
es gibt so etwas wie diesen vereinfachten Code
ein Match(enable) wird in ein
if(0); else
if(id[0]=='e'&&id[2]=='a'&&6==len&&id[len-2]=='l'&&!strcmp_P(id,PSTR("en
able")))
umgewandelt. Das ist dann ziemlich optimiert, auch wenn es 200
Vergleiche sind.
wird hingegen match_(foo) verwendet wo foo ein char* ist, dann wird
if(0); else
if(id[0]==foo[0]&&id[2]==foo[2]&&strlen(foo)==len&&id[len-2]==foo[len-2]
&&!strcmp(id,foo))
Dies ist auch noch optimiert, ein strlen wird nur gemacht, wenn der
erste
und dritte Buchstabe gleich sind. Ein Stringvergleich wird erst gemacht
wenn noch der Vorletzte Buchstabe identisch ist. Minimum sind zwei
Buchstaben, in diesem Falle ist der Dritte Buchstabe '\0'.
Bei einem konstanten String wird strlen sowie die Stringzugriffe
wegoptimiert, eben wegen der Arrayzuweistung/definition von String[pos].
Viele Hash oder find Routinen von LinkListen oder HashList/Table machen
solche vergleiche, und da merkt man dann richtig den
Geschwindigkeitsunterschied, aber auch bei einem Microcontroller mit 50
Strings, der nebenbei andere Sachen macht.
parse_line(char*line) { unsigned char i,len; unsigned int j; char*id;
char* arg_s[4];
uint32_t arg_n[4];
assert(line); if(!line) return;
while(isspace(*line)) line++;
if(id=strchr(line,';')) *id='\0';
if(*line=='!') { comment(line); return; }
if(!*line) return; arg_f=0;
for(i=0;i<tblsize(arg_s);i++) { // parse line arguments
arg_n[i]=atol(arg_s[i]=args(line,&line));
if(isdigit(*arg_s[i])) arg_f|=1<<i;
} if(line) { args2x: serror("too many arguments"); return; }
for(i=0;i<tblsize(arg_s);i++) if(!*arg_s) { arg_n[0]=i-1; break; }
j=strlen(id=arg_s[0]); if(j>16) goto err; len=j;
if(!isalpha(*id)||(len=strlen(id))<2) serror(NULL); else
if(state==S_SETUP) {
Match(setup) state=S_SETUP;
match(help) goto help; else goto err;
Match(help); else goto command;
} else
if(state==S_ENABLE) {
Match(setup) state=S_SETUP;
match(help) goto help; else goto err;
} else
if(state==S_HIDDEN) { /* code */ } else
if(state==S_DEFAULT) {
Match(enable) { /* check password */ state=S_ENABLE; }
match(help) goto help; else goto err;
} else err:
serror("unrecognized command");
return;
help: /* help headers */
if(state==S_DEFAULT) { /* code */ }
if(state==S_SETUP) { /* code */ }
if(state==S_HIDDEN) { /* code */ }
if(state==S_ENABLE) { /* code */ }
return; command: // store command token to eeprom
i=arg_n[0];
arg_n[0]=hash(id);
#if tblsize(arg_s)!=4
#error "config must be updated for tblsize(arg_s)"
#endif
config(i|arg_f<<3); j=0;
for(i=0;i<len;i++) if(arg_n[i])<=0xff) j|=1<<i;
for(i=0;i<len;i++) if(arg_n[i])<=0xffff) j|=0x100<<i;
if(j>>8) { j|=0x80; config(j); config(j>>8); } else config(j);
while(i--) {
if(j&(0x100<<i) config(arg_n[i]>>16);
if(j&(0x1 <<i) config(arg_n[i]>> 8);
config(arg_n[i]);
}
}
Hier geht es aber darum dass "foobar"[3] ein oft verwendetes Konstrukt
in C
ist, natürlich versteckt von Preprozessoraufrufen wo eine Teilmenge von
einem konstanten C oder Unicode String in ein Array oder Strukt
umgewandelt wird und dabei teile des Strings verlorengehen. Ich habe
dazu ein Beispiel geliefert.
#define tblsize(tbl) (sizeof(tbl)/sizeof(tbl[0]))
args(line,&line) ist ähnlich strtod liefert String zurück, ähnlich
strtok
Ja, ein Kopierfehler.
for(i=0;i<tblsize(arg_s);i++) if(!*arg_s) { arg_n[0]=i-1; break;}
richtig ist:
for(i=0;i<tblsize(arg_s);i++) if(!*arg_s[i]) { arg_n[0]=i-1; break;}
es gibt ein
#define arg(x,y...) CONCAT(arg_,x)(y)
mit unterschiedlichen Macros für a, n, f, y usw.
Z.B wertet arg(y,2) aus ob das zweite Argument 1,up, yes, Y, T enthält
und
liefert dann 1 oder eben 0 zurück.
Auch wenn solche Makros die Leserlichkeit erhöhen, aus Übersichtsgründen
habe ich diese ersetzt, und da ist mir dieser Flüchtigkeitsfehler
untergelaufen.
chris schrieb:> Auch wenn solche Makros die Leserlichkeit erhöhen,
Du hast rein gar nichts verstanden. Zu +90% der Zeit wird ein Code
gelesen - nicht geschrieben, deiner ist unleserlich. Makros, die nur dir
bekannt sind, helfen nicht. Dieser write-once code sollte besser bei dir
in deinem Keller verbleiben.
leo
@chris
Kannst ein vollstaendiges kompilierbares minimal beispiel von deinem
parser posten?
Wuerde das gerne mal durch einen aktuellen gcc oder clang jagen
chris schrieb:> Auch wenn solche Makros die Leserlichkeit erhöhen
Erhöhen hätte nur [ c] und [/c] gekonnt (kennst Du die hier?). Bei mehr
als 2 Zeilen geht es nicht ohne.