frage zu sizeof() in c

Gast #5909630
Lesenswert?

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?
#5909645
Lesenswert?

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.
Gast #5909706
Lesenswert?

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.
Gast #5909717
Lesenswert?

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.
Gast #5909734
Lesenswert?

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
  const unsigned char y[] = {"abc"};
2
  const unsigned char z[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
Gast #5909776
Lesenswert?

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?
Gast #5909949
Lesenswert?

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.
Persönliche Seite #5910091
Lesenswert?

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.
#5910095
Lesenswert?

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.
Moderator Persönliche Seite #5912500
Lesenswert?

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.
Gast #5912584
Lesenswert?

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.
Moderator Persönliche Seite #5912631
Lesenswert?

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.
Gast #5912646
Lesenswert?

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!
Gast #5912892
Lesenswert?

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]);
        }
}
Gast #5912923
Lesenswert?

chris schrieb:
>    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

Eine Pull Request mit sowas würde ich kommentarlos löschen.
Gast #5913009
Lesenswert?

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.
Gast #5913054
Lesenswert?

#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.
Gast #5913065
Lesenswert?

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

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