Forum: PC-Programmierung frage zu sizeof() in c


von Jens (Gast)


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?

von Dirk B. (dirkb2)


Lesenswert?

weil ein Stringliteral (der Text zwischen den ") mit einem '\0' beendet 
wird.

C-Grundlagen

von RNF (Gast)


Lesenswert?

Länge von "abc" ist 4. Strings werden in C mit Null '\0' terminiert.

von Dirk B. (dirkb2)


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.

von Jens (Gast)


Lesenswert?

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?

von Sebastian S. (amateur)


Lesenswert?

@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?

von Jens (Gast)


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.

von Chris (Gast)


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.

von Jens (Gast)


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

von Soduko (Gast)


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?

von Jemand (Gast)


Lesenswert?

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.

von leo (Gast)


Lesenswert?

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

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von A. S. (Gast)


Lesenswert?

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.

von Dr. Sommer (Gast)


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.

von Rufus Τ. F. (rufus) Benutzerseite


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.

von Dirk B. (dirkb2)


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.

: Bearbeitet durch User
von Dirk B. (dirkb2)


Lesenswert?

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.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

"Richtig" ist das auch in der Debugversion nicht. Es ist möglich, daß es 
dann irgendwelches Padding gibt, und der Zufall sich dann anders 
verhält.

von Frank M. (ukw) (Moderator) Benutzerseite


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.

: Bearbeitet durch Moderator
von chris (Gast)


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.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

chris schrieb:
> Ein Beispiel eines Codes

Darin sehe ich ein strlen. Das auf einen nicht-nullterminierten String 
loszulassen ist ein eindeutiger Fehler.

von Frank M. (ukw) (Moderator) Benutzerseite


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.

: Bearbeitet durch Moderator
von DickerFehler (Gast)


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!

von chris (Gast)


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]);
        }
}

von Jemand (Gast)


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.

von chris (Gast)


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.

von leo (Gast)


Lesenswert?

chris schrieb:
Viel Zeugs, wie ...

> for(i=0;i<tblsize(arg_s);i++) if(!*arg_s) { arg_n[0]=i-1; break; }

Was hast du genommen? Wo gibt's das?

leo

von chris (Gast)


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.

von leo (Gast)


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

von cppbert3 (Gast)


Lesenswert?

@chris

Kannst ein vollstaendiges kompilierbares minimal beispiel von deinem 
parser posten?

Wuerde das gerne mal durch einen aktuellen gcc oder clang jagen

von A. S. (Gast)


Lesenswert?

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.

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.