Forum: PC-Programmierung Rückgabewert von strlen() unsigned, aber warum?


von zitter_ned_aso (Gast)


Lesenswert?

Hallo,

warum benutzt man bei dem Rückgabewert von strlen(...) einen unsigned 
Datentyp (size_t)? Warum kein signed Datentyp?

Wenn ich z.B. einen Satz / ein Wort rückwärts ausgeben will, dann mache 
ich es ja von der Position
1
strlen(my_string)-1
 bis 0 (inklusive).  Aber wie kann ich das mit einer Laufvariable
1
size_t i
 denn prüfen?

Man kommt bis 0 und bekommt dann einen Überlauf.

Ja, es gibt mehrere Ideen, die dieses Problem anders lösen. Aber wie 
könnte man dann mit size_t-Laufvariablen rechnen und alles in einer 
Schleife ausgeben (quasi Variante 2 + Variante 3: Man hat alles in einer 
Schleife und die Laufvariable hat den Typ size_t)?
1
void puts_rev(const char* str){
2
3
    /* 
4
    //Variante 1, mit Zeigern
5
    const char* p=str+strlen(str)-1;
6
7
    while(p>=str){
8
        putchar(*p--);
9
    }
10
    */
11
12
13
    /*
14
    //Variante 2, signed-Typ statt size_t nutzen
15
    int i = strlen(str) - 1;
16
    while (i >= 0)
17
      putchar(str[i--]);
18
    */
19
20
21
    //Variante 3, 
22
    size_t str_len=strlen(str);
23
    for(size_t i=str_len-1; i>0; i--)
24
        putchar(str[i]);
25
    putchar(str[0]);
26
}

von Sebastian S. (amateur)


Lesenswert?

Erst wenn Du es schaffst eine Zeichenkette mit negativer Länge zu 
schreiben, wirst Du auch den negativen Wertebereich brauchen.
Es gab mal Zeiten, da waren Variablen sehr "teuer" und es machte einen 
großen Unterschied, ob Du von 0 bis 255 Zählen konntest oder nur bis 127 
(wenn auch in beiden Richtungen).

von Hannes J. (Firma: _⌨_) (pnuebergang)


Lesenswert?

zitter_ned_aso schrieb:
> warum benutzt man bei dem Rückgabewert von strlen(...) einen unsigned
> Datentyp (size_t)? Warum kein signed Datentyp?

Ähm, weil ein String keine negative Länge haben kann?

> Man kommt bis 0 und bekommt dann einen Überlauf.

Unterlauf.

Daher, alle "Lösungen" die irgendwo "strlen(s) - 1" enthalten sind 
höchstwahrscheinlich falsch (nicht falsch kann es dann sein, wenn 
implizit oder explizit der Typ vor der Subtraktion konvertiert wird).
1
size_t len = strlen(s);
2
for(size_t ui = 0; ui < len; ui++) {
3
    putchar(s[len - ui]);
4
}
5
6
char *p = s + strlen(s);
7
while(p > s) {
8
  putchar *--p;
9
}

von mh (Gast)


Lesenswert?

1
size_t str_len = strlen(str);
2
for(size_t i=0; i<str_len; ++i) {
3
    putchar(str[str_len-i-1]);
4
}
5
putchar(str[0]);

von zitter_ned_aso (Gast)


Lesenswert?

zitter_ned_aso schrieb:
> Variante 2 + Variante 3: Man hat alles in einer
> Schleife und die Laufvariable hat den Typ size_t
1
                                                                   
2
for(size_t i=strlen(str)-1; BEDINGUNG; i--)                       
3
    putchar(str[i]);

Bedingung i>0 geht nicht. Wegen "unsigned". Das meinte ich.

Und dann habe ich mich gefragt, wie sowas bei Libs-Funktionen gelöst 
wird. Und dachte gleich an die strrchr(...)-Funktion. Denn ich würde da 
von hinten anfangen zu prüfen:
1
  const char* my_strrchr(const char* str, const char ch){               
2
                                                                        
3
      /*                                                                
4
      //das haette ich gern mit size_t statt int                        
5
      for(int i=strlen(str)-1; i>=0; i--)                               
6
          if(str[i]==ch)                                                
7
              return &str[i];                                           
8
      */                                                                
9
                                                                        
10
      for(const char* p=str+strlen(str)-1; p>=str; p--)                 
11
          if(*p==ch)                                                    
12
              return p;                                                 
13
                                                                        
14
      return NULL;                                                      
15
  }

Und da hätte ich wieder dieses Problem mit size_t.

Aber die Standardimplementierung aus glibc hat mir auch nicht 
weitergeholfen:

https://github.com/bminor/glibc/blob/master/string/strrchr.c#L33

Denn dort wird von vorne durch das Array iteriert. Und jedes mal die 
Funktion strchr(...) aufgerufen. Ist der Aufruf von strlen(...) wirklich 
so teuer? Warum macht man es so?

von Hannes J. (Firma: _⌨_) (pnuebergang)


Lesenswert?

mh schrieb:
>
1
> size_t str_len = strlen(str);
2
> for(size_t i=0; i<str_len; ++i) {
3
>     putchar(str[str_len-i-1]);
4
5
Ja, die -1 habe ich da vergessen. Ich wollte das mit etwas anders fixen:
6
7
[c]
8
for(size_t i = 1; i <= str_len; ++i) {
9
     putchar(str[str_len - i]);

von zitter_ned_aso (Gast)


Lesenswert?

mh schrieb:
> putchar(str[str_len-i-1]);

Ja, ist eine Möglichkeit. Und diese Rechnerei bräuchte man nicht, wenn 
die Laufvariable signed wäre.

von Jemand (Gast)


Lesenswert?

zitter_ned_aso schrieb:
> Warum kein signed Datentyp?

Was soll strlen dann für einen 3 GiB String auf einer 32 Bit Platform 
zurückgeben?

zitter_ned_aso schrieb:
> /*
>     //Variante 1, mit Zeigern
>     const char* p=str+strlen(str)-1;
>
>     while(p>=str){
>         putchar(*p--);
>     }
>     */

Undefiniertes Verhalten wenn p == str

Lösung? Trivial:
1
    size_t str_len=strlen(str);
2
    for (size_t i=str_len; i>0; i--)
3
    {
4
        putchar(str[i-1]);
5
    }

Gleichbedeutend:
1
    size_t str_len=strlen(str);
2
    for (size_t i=str_len-1; i!=SIZE_MAX; i--)
3
    {
4
        putchar(str[i]);
5
    }

von zitter_ned_aso (Gast)


Lesenswert?

Jemand schrieb:
> Undefiniertes Verhalten wenn p == str

wegen decrement am Ende?

von Jemand (Gast)


Lesenswert?

zitter_ned_aso schrieb:
> Jemand schrieb:
>> Undefiniertes Verhalten wenn p == str
>
> wegen decrement am Ende?

Ja, p darf nicht vor das Array dekrementiert werden. Zusätzlich ist der 
darauffolgende terminierende Vergleich p>=str ungültig, weil dieser 
Vergleich nur zulässig ist, wenn die Pointer auf dasselbe Array* zeigen.



[*] bzw. oder auch, der Vollständigkeit halber, /one past the last 
element of the array object/

von zitter_ned_aso (Gast)


Lesenswert?

Alles klar, danke. Dann muss man halt Indizes verschieben. Rückwärts 
kommt man auch mit Zeigern nicht weiter.

Beitrag #6237288 wurde vom Autor gelöscht.
von A. S. (Gast)


Lesenswert?

1
size_t l=strlen(str);
2
while(l) putchar(str[--l]);

von zitter_ned_aso (Gast)


Lesenswert?

A. S. schrieb:
> size_t l=strlen(str);
> while(l) putchar(str[--l]);

clever, hab' ich mir gemerkt ;-)

Jemand schrieb:
> Ja, p darf nicht vor das Array dekrementiert werden. Zusätzlich ist der
> darauffolgende terminierende Vergleich p>=str ungültig, weil dieser
> Vergleich nur zulässig ist, wenn die Pointer auf dasselbe Array* zeigen.

Ich möchte noch mal zu dieser Aussage zurückkehren. Denn ich sehe 
ständig solche Beispiele:

1
  int arr[] = {1, 2, 3, 4, 5};
2
  const size_t arr_len = get_array_length(arr);
3
4
  int *p = arr;
5
  int *p_end = p + arr_len; // außerhalb von Arraygrenzen!
6
7
  while (p < p_end)
8
    printf("%d\n", *p++);

Ich fliege doch bei solchen Sachen immer aus dem Array raus, bzw. 
vergleiche mit einer Adresse, die außerhalb meines Arrays liegt.

von mh (Gast)


Lesenswert?

Aus dem Standard
1
6.5.6 Additive operators
2
8) When an expression that has integer type is added to or subtracted
3
from a pointer, the result has the type of the pointer operand.
4
[...]
5
If both the pointer operand and the result point to elements of the same
6
array object, or one past the last element of the array object, the
7
evaluation shall not produce an overflow; otherwise, the behavior is
8
undefined. If the result points one past the last element of the array
9
object, it shall not be used as the operand of a unary * operator that
10
is evaluated.

von zitter_ned_aso (Gast)


Lesenswert?

mh schrieb:
> or one past the last element of the array object

Ach so.

mh schrieb:
> shall not produce an overflow

ich interpretiere es mal als "wird nicht" ;-)

Danke!

von Dirk B. (dirkb2)


Lesenswert?

zitter_ned_aso schrieb:
> Denn dort wird von vorne durch das Array iteriert. Und jedes mal die
> Funktion strchr(...) aufgerufen. Ist der Aufruf von strlen(...) wirklich
> so teuer? Warum macht man es so?

Wenn du von vorne durchgehst, musst du nur einmal durch.
Wenn du erst das Ende suchst und dann wieder zurück, musst du im Zweifel 
zweimal durch.

strlen() fasst sowieso schon jedes Zeichen an.

von Dirk B. (dirkb2)


Lesenswert?

zitter_ned_aso schrieb:
> Ich fliege doch bei solchen Sachen immer aus dem Array raus,

solange du nicht auf Element außerhalb vom Array zugreifst sondern nur

> vergleiche mit einer Adresse, die außerhalb meines Arrays liegt.

ist doch alles in Ordnung.

von zitter_ned_aso (Gast)


Lesenswert?

Dirk B. schrieb:
> strlen() fasst sowieso schon jedes Zeichen an.

ich habe mir noch musl-libc angeschaut. Dort wird bei strrchr(...) 
tatsächlich von hinten angefangen (so wie ich es mir eigentlich auch 
gedacht habe).

http://git.musl-libc.org/cgit/musl/tree/src/string/strrchr.c

http://git.musl-libc.org/cgit/musl/tree/src/string/memrchr.c

Keine Ahnung welche Variante nun die schnellste ist, aber wahrscheinlich 
kommt es darauf auch nicht wirklich an.

von zitter_ned_aso (Gast)


Lesenswert?

zitter_ned_aso schrieb:
> http://git.musl-libc.org/cgit/musl/tree/src/string/memrchr.c

ist das nicht falsch?
1
return __memrchr(s, c, strlen(s) + 1);

ich hätte die Eins abgezogen und nicht dazu addiert.

von zitter_ned_aso (Gast)


Lesenswert?

zitter_ned_aso schrieb:
> ich hätte die Eins abgezogen und nicht dazu addiert.

nein, weder abziehen noch dazu addieren.

von (prx) A. K. (prx)


Lesenswert?

Sebastian S. schrieb:
> Es gab mal Zeiten, da waren Variablen sehr "teuer" und es machte einen
> großen Unterschied, ob Du von 0 bis 255 Zählen konntest oder nur bis 127

"unsigned" hatte nie weniger als 16 Bits.

von mh (Gast)


Lesenswert?

Dirk B. schrieb:
> zitter_ned_aso schrieb:
>> Ich fliege doch bei solchen Sachen immer aus dem Array raus,
>
> solange du nicht auf Element außerhalb vom Array zugreifst sondern nur
>
>> vergleiche mit einer Adresse, die außerhalb meines Arrays liegt.
>
> ist doch alles in Ordnung.

Nein! Nur der Vergleich mit einem Pointer Ende+1 ist erlaubt.

von Dirk B. (dirkb2)


Lesenswert?

zitter_ned_aso schrieb:
> ich hätte die Eins abgezogen und nicht dazu addiert.

Du kannst so auch nach der '\0' suchen

von zitter_ned_aso (Gast)


Lesenswert?

Dirk B. schrieb:
> Du kannst so auch nach der '\0' suchen

ja, an sowas habe ich gar nicht gedacht! Man kann nach einem Stringende 
suchen.
1
    char* str="hallo";
2
    printf("%p\n", str+strlen(str));
3
    printf("%p\n", strrchr(str, '\0'));

strrchr(...) meckert nicht und gibt die gleiche Adresse aus.

von Rolf M. (rmagnus)


Lesenswert?

zitter_ned_aso schrieb:
> mh schrieb:
>> shall not produce an overflow
>
> ich interpretiere es mal als "wird nicht" ;-)

Es ist eine Anforderung an den Compiler, also ja - auf einem konformen 
Compiler wird kein Overflow stattfinden.

Dirk B. schrieb:
> zitter_ned_aso schrieb:
>> Ich fliege doch bei solchen Sachen immer aus dem Array raus,
>
> solange du nicht auf Element außerhalb vom Array zugreifst sondern nur
>
>> vergleiche mit einer Adresse, die außerhalb meines Arrays liegt.
>
> ist doch alles in Ordnung.

Es gilt nicht nur für den Zugriff, sondern auch für die 
Pointer-Arithmetik selbst.

A. K. schrieb:
> Sebastian S. schrieb:
>> Es gab mal Zeiten, da waren Variablen sehr "teuer" und es machte einen
>> großen Unterschied, ob Du von 0 bis 255 Zählen konntest oder nur bis 127
>
> "unsigned" hatte nie weniger als 16 Bits.

Wir sprechen zwar nicht von unsigned, sondern von size_t, aber auch da 
ist das Minimum bei 16 Bit. Aber auch bei einem size_t von 16 Bit Größe 
macht es einen Unterschied, ob das größte Objekt oder Array 32767 oder 
65535 Bytes groß sein kann.

zitter_ned_aso schrieb:
> zitter_ned_aso schrieb:
>> ich hätte die Eins abgezogen und nicht dazu addiert.
>
> nein, weder abziehen noch dazu addieren.

Nein, beides wäre falsch. Dazuaddieren ist korrekt.

von Sven B. (scummos)


Lesenswert?

Dass size_t unsigned ist ist einfach ein Fehler im Design der STL. Das 
sehe nicht nur ich so: 
https://github.com/ericniebler/stl2/issues/182#issuecomment-257000171
Das klang halt mal schlau, war es aber eigentlich nicht.

Die Begründung "aber der Index in einem Array ist immer positiv" klingt 
zwar irgendwie logisch, führt aber in der Praxis eher zu mehr Fehlern 
als zu weniger. Eigentlich muss ich mir gar keinen Text überlegen, der 
verlinkte Post hat das schon perfekt formuliert:

> Use of unsigned ints in interfaces isn't the boon many
> people think it is. If by accident a user passes a slightly
> negative number to the API, it suddenly becomes a huge positive
> number. Had the API taken the number as signed, then it can
> detect the situation by asserting the number is greater than
> or equal to zero.

von zitter_ned_aso (Gast)


Lesenswert?

Rolf M. schrieb:
> Nein, beides wäre falsch. Dazuaddieren ist korrekt.

Darüber kann man sich streiten ;-) Ich habe nicht daran gedacht, dass 
jemand nach einem '\0'-Zeichen suchen will.

Aber wenn man diese Option anbieten will, dann doch bitte richtig:
1
    char str[10];
2
    memset(str, 0, sizeof(str));
3
    strcpy(str,"hallo");
Das ist mein String. Und diese Anweisung
1
    printf("%p\n", strrchr(str, '\0'));

gibt die gleiche Adresse vom letzten '\0'-Zeichen wie diese hier
1
    printf("%p\n", str+strlen(str));

Und das ist falsch. Denn
1
   if(str[9]=='\0')
2
        puts("gleich");
3
    printf("und Adresse davon: %p\n", &str[9]);

Und die Adresse vom letzten '\0'-Zeichen ist eine andere!

von zitter_ned_aso (Gast)


Lesenswert?

Sven B. schrieb:
> Die Begründung "aber der Index in einem Array ist immer positiv" klingt
> zwar irgendwie logisch

Die Begründung war, dass es keinen Text mit negativer Länge gibt. Daher 
muss der Rückgabewert von strlen(...) immer positiv sein.

Aber ich kenne auch keine negativen Buchstaben, trotzdem gibt getchar() 
einen "signed int"-Wert zurück.

von Dirk B. (dirkb2)


Lesenswert?

Ein C-String ist bei der ersten '\0' zuende. Und das ist auch die 
letzte.

Wenn du ein Array durchsuchen möchtest, musst du mem-Funktionen nehmen.

von Dirk B. (dirkb2)


Lesenswert?

zitter_ned_aso schrieb:
> Aber ich kenne auch keine negativen Buchstaben, trotzdem gibt getchar()
> einen "signed int"-Wert zurück.

getchar liest ja nicht nur Buchstaben ein.
und neben dem Wertebereich von char wollte man noch EOF erkennen können.

von zitter_ned_aso (Gast)


Lesenswert?

Dirk B. schrieb:
> Ein C-String ist bei der ersten '\0' zuende. Und das ist auch die
> letzte.

Das ist ein Argument.

und warum wird dann bei strncpy(...) alles mit Nullen aufgefüllt, wenn 
die Länge von source kleiner als num ist? Warum reicht nicht ein 
'\0'-Zeichen?
1
char * strncpy ( char * destination, const char * source, size_t num );

von zitter_ned_aso (Gast)


Lesenswert?

zitter_ned_aso schrieb:
> und warum wird dann bei strncpy(...) alles mit Nullen aufgefüllt,

ach so, weil sie dort großzügig die memset-Funktion einsetzen.

https://github.com/bminor/glibc/blob/master/string/strncpy.c

von Sven B. (scummos)


Lesenswert?

zitter_ned_aso schrieb:
> Die Begründung war, dass es keinen Text mit negativer Länge gibt. Daher
> muss der Rückgabewert von strlen(...) immer positiv sein.

Die Erklärung ist historisch korrekt, aber eine gute faktische 
Begründung für dieses Design ist das nicht.

von mh (Gast)


Lesenswert?

Sven B. schrieb:
> Die Begründung "aber der Index in einem Array ist immer positiv" klingt
> zwar irgendwie logisch, führt aber in der Praxis eher zu mehr Fehlern
> als zu weniger. Eigentlich muss ich mir gar keinen Text überlegen, der
> verlinkte Post hat das schon perfekt formuliert:
>
>> Use of unsigned ints in interfaces isn't the boon many
>> people think it is. If by accident a user passes a slightly
>> negative number to the API, it suddenly becomes a huge positive
>> number. Had the API taken the number as signed, then it can
>> detect the situation by asserting the number is greater than
>> or equal to zero.

Wenn der user "by accident" die Warnungen des Compilers ignoriert...
Es gibt genug gute Argumente für/gegen signed/unsinged als Typ für 
Zählwerte, das hier zitierte, ist nen mieses Argument.

Mir ist eigentlich egal ob size_t singed oder unsigned ist, beides hat 
Vor- und Nachteile. Was ich hasse ist, wenn es jede API anders macht und 
überall static_cast nötig ist, damit es läuft.

von xyz (Gast)


Lesenswert?

Wenn man nicht faul ist, und seine Strings in "Handarbeit" bearbeitet,
faellt ein strlen() ohnehin nicht an.
Ein '\0' hat da immer noch gereicht und zu sehen wann man fertig ist.

von mh (Gast)


Lesenswert?

xyz schrieb:
> Wenn man nicht faul ist, und seine Strings in "Handarbeit" bearbeitet,
> faellt ein strlen() ohnehin nicht an.
> Ein '\0' hat da immer noch gereicht und zu sehen wann man fertig ist.

Bis man irgendwann mal auf Strings trifft, die man nicht selbst erzeugt 
hat. Es soll irgendwo Software geben, die externe Daten verarbeitet...

von A. S. (Gast)


Lesenswert?

mh schrieb:
> Es gibt genug gute Argumente für/gegen signed/unsinged als Typ für
> Zählwerte, das hier zitierte, ist nen mieses Argument.

mit dem UB bei Signed-Überlauf und "-3 ist > 0u" ist die richtige Wahl 
von signed/unsigned für mich eines der frustrierensten Dinge. Wie mans 
macht, macht mans verkehrt. Braucht explizite inline-Funktionen oder 
verliert mit unendlichen casts die eigentliche Typprüfung.

von xyz (Gast)


Lesenswert?

> Bis man irgendwann mal auf Strings trifft, die man nicht selbst erzeugt
> hat.

Fuettern und streicheln verboten!

von Rolf M. (rmagnus)


Lesenswert?

zitter_ned_aso schrieb:
> Aber ich kenne auch keine negativen Buchstaben, trotzdem gibt getchar()
> einen "signed int"-Wert zurück.

Ich kenne auch keine positiven Buchstaben. Also wäre in der Hinsicht 
unsigned auch nicht besser. Geschickter wäre es gewesen, wenn C einen 
eigenen Typ für Text hätte, mit dem man gar nicht rechnen kann, so wie 
es das bei vielen anderen Sprachen auch gibt.

zitter_ned_aso schrieb:
> Dirk B. schrieb:
>> Ein C-String ist bei der ersten '\0' zuende. Und das ist auch die
>> letzte.
>
> Das ist ein Argument.

So ist es auch für die Funktion strrchr definiert. Das erste '\0' (und 
nur das) muss als Teil des Strings angesehen werden, kann also auch 
damit gesucht werden.

> und warum wird dann bei strncpy(...) alles mit Nullen aufgefüllt, wenn
> die Länge von source kleiner als num ist?

Weil strncpy Schrott ist. Wenn die Länge von source größer als num ist, 
wird gar kein '\0' angehängt, und destination ist kaputt, weil kein 
gültiger C-String.

> Warum reicht nicht ein '\0'-Zeichen?

Eigentlich reicht es.

Sven B. schrieb:
> zitter_ned_aso schrieb:
>> Die Begründung war, dass es keinen Text mit negativer Länge gibt. Daher
>> muss der Rückgabewert von strlen(...) immer positiv sein.
>
> Die Erklärung ist historisch korrekt, aber eine gute faktische
> Begründung für dieses Design ist das nicht.

Warum nicht? Warum soll man einen Typ mit einem Vorzeichen verwenden für 
etwas, das nicht negativ sein kann? size_t wird auch an vielen anderen 
Stellen für die Größe von Objekten verwendet, wie z.B. sizeof, malloc 
u.s.w.
Auch hier hat man sich entschlossen, für Objektgrößen einen 
vorzeichenlosen Typ zu verwenden, da Objekte niemals eine negative Größe 
haben können.
Und wenn size_t voreichenbehaftet wäre, dann würde das die maximale 
Größe von Objekten halbieren. Also könnten auf einem System, wo size_t 
16 Bit breit ist, Objekte/Arrays/Strings nur noch maximal 32767 statt 
65535 Bytes lang sein, weil man die Hälfte des Wertebereichs des Typs 
verschenkt, um nie vorkommende negative Zahlen darstellen zu können.

: Bearbeitet durch User
von Sven B. (scummos)


Lesenswert?

Rolf M. schrieb:
> Warum nicht? Warum soll man einen Typ mit einem Vorzeichen verwenden für
> etwas, das nicht negativ sein kann?

Weil man mit dem Größen rechnen will, und in den abgeleiteten Rechnungen 
machen negative Zahlen oft Sinn, und die Konvertierung zwischen signed 
und unsigned die ganze Zeit verursacht dann viel mehr Probleme als sie 
löst.

> size_t wird auch an vielen anderen
> Stellen für die Größe von Objekten verwendet, wie z.B. sizeof, malloc
> u.s.w.
> Auch hier hat man sich entschlossen, für Objektgrößen einen
> vorzeichenlosen Typ zu verwenden, da Objekte niemals eine negative Größe
> haben können.

Ja, und es ist an jeder dieser Stellen ein Fehler. Sagt inzwischen auch 
das Konsortium, was das designt hat, ziemlich einstimmig. ;)

> Und wenn size_t voreichenbehaftet wäre, dann würde das die maximale
> Größe von Objekten halbieren. Also könnten auf einem System, wo size_t
> 16 Bit breit ist, Objekte/Arrays/Strings nur noch maximal 32767 statt
> 65535 Bytes lang sein, weil man die Hälfte des Wertebereichs des Typs
> verschenkt, um nie vorkommende negative Zahlen darstellen zu können.

Das spielt quasi nie eine Rolle. Dieses Problem tritt nur dann auf, wenn 
du mehr als halb so viele einzelne Bytes in einem Array hast wie deine 
Plattform insgesamt an virtuellem Speicher addressieren kann. Wann kommt 
das bitte vor?
In diesem Supersonderspezialfall auf irgendeiner obskuren 
16-Bit-Plattform könntest du dir die nötige Funktion dann mit größerem 
Rückgabetyp einfach selber schreiben.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Sven B. schrieb:
> Rolf M. schrieb:
>> Warum nicht? Warum soll man einen Typ mit einem Vorzeichen verwenden für
>> etwas, das nicht negativ sein kann?
>
> Weil man mit dem Größen rechnen will, und in den abgeleiteten Rechnungen
> machen negative Zahlen oft Sinn, und die Konvertierung zwischen signed
> und unsigned die ganze Zeit verursacht dann viel mehr Probleme als sie
> löst.

Warum "die ganze Zeit"? Wenn du mit dem Ergebnis deines strlen() 
vorzeichenbehaftet rechnen willst, konvertierst du es einfach genau 
einmal in was vorzeichenbehaftetes, und fertig.

> Dieses Problem tritt nur dann auf, wenn du mehr als halb so viele einzelne
> Bytes in einem Array hast wie deine Plattform insgesamt an virtuellem Speicher
> addressieren kann.

Nein. C sagt nicht, dass man mit einem size_t den gesamten Speicher 
erreichen können muss, sondern nur den Speicher eines Objekts. Beispiel: 
x86 im Real Mode. Der hat 1 MB Adressraum und 64k große Segmente. size_t 
ist entsprechend 16 Bit breit, und damit kann ein Objekt ein komplettes 
Segment, also 64k groß werden. Wäre es vorzeichenbehaftet, gingen nur 
32k, und das nur, um auch negative Größen darstellen zu können.

> In diesem Supersonderspezialfall auf irgendeiner obskuren
> 16-Bit-Plattform könntest du dir die nötige Funktion dann mit größerem
> Rückgabetyp einfach selber schreiben.

So supersonderspeziell waren PCs mit DOS eigentlich nicht.

: Bearbeitet durch User
von Sven B. (scummos)


Lesenswert?

Rolf M. schrieb:
>> Weil man mit dem Größen rechnen will, und in den abgeleiteten Rechnungen
>> machen negative Zahlen oft Sinn, und die Konvertierung zwischen signed
>> und unsigned die ganze Zeit verursacht dann viel mehr Probleme als sie
>> löst.
>
> Warum "die ganze Zeit"? Wenn du mit dem Ergebnis deines strlen()
> vorzeichenbehaftet rechnen willst, konvertierst du es einfach genau
> einmal in was vorzeichenbehaftetes, und fertig.

Und dann konvertiere ich es genau einmal wieder zurück, um es wieder als 
Index verwenden zu können. Und vergesse dabei nicht, vorher zu prüfen, 
ob das Ergebnis kleiner null ist. Damit habe ich genau dieselben 
möglichen Fehlerklassen wie mit Signed, plus zwei nutzlose 
Konvertierungen. Plus die neue Fehlerklasse, dass ich die Konvertierung 
vergesse und einen Overflow produziere!

> Nein. C sagt nicht, dass man mit einem size_t den gesamten Speicher
> erreichen können muss, sondern nur den Speicher eines Objekts. Beispiel:
> x86 im Real Mode. Der hat 1 MB Adressraum und 64k große Segmente. size_t
> ist entsprechend 16 Bit breit, und damit kann ein Objekt ein komplettes
> Segment, also 64k groß werden. Wäre es vorzeichenbehaftet, gingen nur
> 32k, und das nur, um auch negative Größen darstellen zu können.

Ja, aber x86 im Real Mode verwendet halt seit 25 Jahren keiner mehr, 
außer ein Bios oder vergleichbares. Wenn das sein eigenes strlen 
implementieren muss, damit hunderte Millionen Entwickler ein besseres 
Design ihrer Hochsprache haben, finde ich das ok.

> So supersonderspeziell waren PCs mit DOS eigentlich nicht.

Aber heutzutage sind sie es, insbesondere als Ziel-Plattform neuer 
Software mit neuen Sprachstandards und neuen Tools. Deshalb ist das 
kein gutes Argument dafür, wie es jetzt sein sollte.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Sven B. schrieb:
> Ja, aber x86 im Real Mode verwendet halt seit 25 Jahren keiner mehr,
> außer ein Bios oder vergleichbares.

Nur ist C - und auch dessen Vorgabe, dass size_t vorzeichenlos ist - 
deutlich älter als das.

> Wenn das sein eigenes strlen implementieren muss, damit hunderte Millionen
> Entwickler ein besseres Design ihrer Hochsprache haben, finde ich das ok.

Ich nicht, denn ich zweifle an, dass der Vorteil so arg groß ist (und 
dass es "hunderte Millionen" von C-Entwicklern überhaupt gibt). Und es 
müsste nicht nur strlen sein, sondern nebenbei z.B. auch das komplette 
dynamische Speichermanagement. Und da man bei den Compilern in der Regel 
per Kommandozeilenoption wählen kann, welche Version des Sprachstandards 
umgesetzt werden soll, müsste das dann immer so ausgelegt sein, dass es 
mit beiden Varianten in sämtlichen Fällen problemlos funktioniert.

>> So supersonderspeziell waren PCs mit DOS eigentlich nicht.
>
> Aber heutzutage sind sie es, insbesondere als Ziel-Plattform neuer
> Software mit neuen Sprachstandards und neuen Tools.

Neue Versionen des C-Standards werden in der Regel so weit wie möglich 
kompatibel zu den alten gehalten, damit bestehender Code, der auf bisher 
zugesichertem Verhalten beruht, nicht ungültig wird. Da wird sehr viel 
Wert drauf gelegt.
Und wenn ich mir jetzt vorstelle, dass dem gcc im Modus des aktuellsten 
Standards plötzlich vorgeschrieben wird, eine andere Signedness für 
size_t zu verwenden als im Modus einer vorherigen Standard-Version, 
schreit das auch nach Ärger.

> Deshalb ist das kein gutes Argument dafür, wie es jetzt sein sollte.

Doch, ist es.

von A. S. (Gast)


Lesenswert?

Sven B. schrieb:
> Und dann konvertiere ich es genau einmal wieder zurück, um es wieder als
> Index verwenden zu können. Und vergesse dabei nicht, vorher zu prüfen,
> ob das Ergebnis kleiner null ist. Damit habe ich genau dieselben
> möglichen Fehlerklassen wie mit Signed, plus zwei nutzlose
> Konvertierungen. Plus die neue Fehlerklasse, dass ich die Konvertierung
> vergesse und einen Overflow produziere!

Du kannst doch mit unsigned genauso rechnen. Dafür ist es ja bei Über- 
und Unterlauf definiert und macht genau das, was Du willst.

Und statt zwei Checks (idx>=0 && idx<len) kannst Du bei unsigned auf den 
ersten verzichten.

Falls Du das nicht so siehst, konstruiere doch gerne ein Beispiel, wo 
signed auch nur eine operation spart gegenüber unsigned.

(ja, es gibt einige Beispiele, wo bei gleichem Arbeitsbereich <0 
intuitiver ist als >INT_MAX oder ein beliebiger Maximalwert. Aber sparen 
tut man letztendlich nichts)

von (prx) A. K. (prx)


Lesenswert?

Rolf M. schrieb:
> So supersonderspeziell waren PCs mit DOS eigentlich nicht.

Nicht im Small und Medium Model von x86 C. Aber die Pointer vom Large 
Model lagen quer zur damaligen C Tradition, die von 
sizeof(int)==sizeof(int *) ausging.

: Bearbeitet durch User
von Sven B. (scummos)


Lesenswert?

A. S. schrieb:
> Du kannst doch mit unsigned genauso rechnen. Dafür ist es ja bei Über-
> und Unterlauf definiert und macht genau das, was Du willst.

Ne, ist halt einfach nicht so. Der Überlauf ist total sinnlos für jede 
reale Rechnung. In welchem Fall möchte ich denn irgendwas rechnen, wo 12 
- 17 = 4294967291 ist? Im Kontext von "Index in einer Liste"?

Was ich hingegen häufig will ist dass ich irgendeinen Vektor mit Werten 
habe, dann rechne ich aus, an welchem Index der Wert, den ich brauche, 
stehen müsste, und schaue dann, ob der in der gültigen Spanne enthalten 
ist: ist er < 0 oder >= len? Das geht mit unsigned nicht und man macht 
im Gegenteil mit unsigned hier mehr Fehler als mit signed.

> Falls Du das nicht so siehst, konstruiere doch gerne ein Beispiel, wo
> signed auch nur eine operation spart gegenüber unsigned.

Ich habe 50 Objekte die ich auf den Bildschirm malen will, mit 
x-Koordinate in einem virtuellen Koordinatensystem. Ich habe einen 
(vereinfacht in y nur 1 hohen) Buffer mit Pixeln, und eine Funktion int 
x_to_pixel(float xvirt). Mit signed sizes kann ich dies tun:
1
int x_to_pixel(float xvirt) {
2
  return static_cast<int>((xvirt + xzero) * zoom);
3
}
4
5
...
6
7
for (auto const& object: objects) {
8
  int const pos = x_to_pixel(object.xvirt);
9
  if (pos < 0 || pos >= buffer.size())
10
    continue;
11
  paint(object, buffer, pos);
12
}

Wie mache ich das äquivalent ähnlich einfach mit unsigned sizes?

Ich sollte noch hinzufügen dass ich natürlich verstehe, dass man das 
jetzt für C++ nicht mehr ändern kann ...  ist halt Pech. Man kann ja 
aber trotzdem daraus lernen, dass es Quatsch ist.

: Bearbeitet durch User
von Vincent H. (vinci)


Lesenswert?

A. S. schrieb:
> Falls Du das nicht so siehst, konstruiere doch gerne ein Beispiel, wo
> signed auch nur eine operation spart gegenüber unsigned.

Das ist eigentlich ziemlich simpel. Es reicht etwa mit 32bit Werten 
etwas zu indexieren. Folgendes Snippet etwa ist aus dem bzip Source 
abgeleitet (© Chandler Carruth):
https://godbolt.org/z/qzb5nC

Im originalen Talk (siehe hier https://youtu.be/yG1OZ69H_-o) ist der 
Unterschied sogar noch größer und liegt laut Carruth auch noch in einem 
Hotpath.

von Jemand (Gast)


Lesenswert?

Sven B. schrieb:
> Wie mache ich das äquivalent ähnlich einfach mit unsigned sizes?

Da der /mixed-signedness/-Vergleich nur problematisch ist, wenn der 
int negativ ist, funktioniert das genau so wie es da steht, weil der 
Fall bereits vorher behandelt wird.

von mh (Gast)


Lesenswert?

Vincent H. schrieb:
> Das ist eigentlich ziemlich simpel. Es reicht etwa mit 32bit Werten
> etwas zu indexieren.

Das hat nichts mit dem Indizieren zu tun, sondern mit dem 
Inkrementieren. In der int Variante sagst du dem Compiler, dass es 
keinen Überlauf geben kann. Beim uint muss der Compiler den Fall 
behandeln.

Benutzt man keinen uint32_t, sondern size_t ist die unsigned Variante 
plötzlich besser.

von Vincent H. (vinci)


Lesenswert?

mh schrieb:
> Vincent H. schrieb:
>> Das ist eigentlich ziemlich simpel. Es reicht etwa mit 32bit Werten
>> etwas zu indexieren.
>
> Das hat nichts mit dem Indizieren zu tun, sondern mit dem
> Inkrementieren. In der int Variante sagst du dem Compiler, dass es
> keinen Überlauf geben kann. Beim uint muss der Compiler den Fall
> behandeln.

Ja schon klar dass inkrement das "Problem" ist.

> Benutzt man keinen uint32_t, sondern size_t ist die unsigned Variante
> plötzlich besser.

Ich vermute der Code stammt aus einer Zeit in der size_t ebenfalls noch 
32bit breit war. Er erfüllt trotzdem die Fragestellung und ist in dieser 
Form immer noch im aktuellen bzip2 Source zu finden!

/edit
In blocksort.c
1
Bool mainGtU ( UInt32  i1, 
2
               UInt32  i2,
3
               UChar*  block, 
4
               UInt16* quadrant,
5
               UInt32  nblock,
6
               Int32*  budget )

: Bearbeitet durch User
von mh (Gast)


Lesenswert?

Vincent H. schrieb:
> Ich vermute der Code stammt aus einer Zeit in der size_t ebenfalls noch
> 32bit breit war.

Wenn size_t 32 Bit breit war, war es vermutlich ne 32 Bit CPU und damit 
gab es das Problem nicht.

von Sven B. (scummos)


Lesenswert?

Jemand schrieb:
> Sven B. schrieb:
>> Wie mache ich das äquivalent ähnlich einfach mit unsigned sizes?
>
> Da der /mixed-signedness/-Vergleich nur problematisch ist, wenn der
> int negativ ist, funktioniert das genau so wie es da steht, weil der
> Fall bereits vorher behandelt wird.

Verstehe ich nicht.

von mh (Gast)


Lesenswert?

Sven B. schrieb:
> Verstehe ich nicht.

Für jeden Wert von pos, bei dem
1
pos < 0 == true
wäre auch
1
static_cast<unsigned>(pos) >= buffer.size() == true
Damit reicht der >= buffer.size() Vergleich aus.

von Sven B. (scummos)


Lesenswert?

mh schrieb:
> Damit reicht der >= buffer.size() Vergleich aus.

Jetzt muss sich die pro-unsigned-Fraktion aber entscheiden: Dieses 
Argument oder das mit dem doppelt so großen addressierbarer Speicher? 
Beides geht nämlich nicht und verursacht auch super schwer zu findende 
Bugs.

von Jemand (Gast)


Lesenswert?

mh schrieb:
> Sven B. schrieb:
>> Verstehe ich nicht.
>
> Für jeden Wert von pos, bei dempos < 0 == true
> wäre auchstatic_cast<unsigned>(pos) >= buffer.size() == true
> Damit reicht der >= buffer.size() Vergleich aus.

Da muss ich Widersprechen: Wenn ein int gegen den negativen Grenzwert 
strebt, strebt der Wert nach der Umwandlung zu unsigned zum mittleren 
Wertebereich von unsigned int und ist somit möglicherweise kleiner als 
/buffer.size()/.

von A. S. (Gast)


Lesenswert?

Sven B. schrieb:
> Jetzt muss sich die pro-unsigned-Fraktion aber entscheiden: Dieses
> Argument oder das mit dem doppelt so großen addressierbarer Speicher?
> Beides geht nämlich nicht und verursacht auch super schwer zu findende
> Bugs.

Der Zahlenraum wird nicht größer. Wenn Du negative Ergebnisse hast, die 
unterlaufen, geht es in beiden Fällen nicht. 65k Array und -1000 gehen 
mit 16 Bit nicht.

In der Regel hast Du keine negativen Werte. Und die Aussage war ja, dass 
unsigned immer mindestens genauso gut ist, nicht, dass es immer besser 
ist

.

von mh (Gast)


Lesenswert?

Sven B. schrieb:
> Jetzt muss sich die pro-unsigned-Fraktion aber entscheiden: Dieses
> Argument oder das mit dem doppelt so großen addressierbarer Speicher?

Du hast gefragt, wie es ähnlich einfach geht mit unsigned. Jetzt passt 
es dir nicht, wenn die Antwort nicht "besser" ist? Niemand hat gesagt, 
dass unsigend immer die bessere Lösung ist.

Ich würd es eh so regeln:
1
float x_to_pixel(float xvirt) {
2
  return (xvirt + xzero) * zoom;
3
}
4
for (auto const& object: objects) {
5
  float const pos = x_to_pixel(object.xvirt);
6
  if (pos < 0 || pos >= buffer.size())
7
    continue;
8
  paint(object, buffer, static_cast<unsigned/int>(pos));
9
}
Ja, dann gibt es Pixelbruchteile. Die fallen für mich in den gleichen 
Abstraktionsbereich wie negative Pixel.

von mh (Gast)


Lesenswert?

Jemand schrieb:
> Da muss ich Widersprechen: Wenn ein int gegen den negativen Grenzwert
> strebt, strebt der Wert nach der Umwandlung zu unsigned zum mittleren
> Wertebereich von unsigned int und ist somit möglicherweise kleiner als
> /buffer.size()/.

Klar, aber dann knallt es auch an der gleichen Stelle bei der signed 
Variante.

von Rolf M. (rmagnus)


Lesenswert?

Sven B. schrieb:
> mh schrieb:
>> Damit reicht der >= buffer.size() Vergleich aus.
>
> Jetzt muss sich die pro-unsigned-Fraktion aber entscheiden: Dieses
> Argument oder das mit dem doppelt so großen addressierbarer Speicher?
> Beides geht nämlich nicht

Stimmt, es geht immer nur eins von beiden. Wäre die Variable signed, 
ginge keins von beiden.

> und verursacht auch super schwer zu findende Bugs.

Und bei signed nicht?

von Sven B. (scummos)


Lesenswert?

mh schrieb:
> Du hast gefragt, wie es ähnlich einfach geht mit unsigned. Jetzt passt
> es dir nicht, wenn die Antwort nicht "besser" ist?

Ehrlich gesagt passt es mir nicht, weil ich die Verwendung des signed 
overflows an der Stelle für Murks halte, der ohne Kommentar nicht als 
Absicht zu erkennen ist. Und weil man durch Schreiben dieses Codes die 
in den letzten 10 Posts als großen Vorteil für unsigned verkaufte 
Eigenschaft, dass man den doppelten Speicher addressieren kann, in einen 
subtilen Bug konvertiert: denkt sich der Benutzer der API "naja, die 
Größe ist unsigned, da kann ich 4G Elemente reintun", macht die Funktion 
u.U. Mist, wenn sie den negativen Wertebereich zum Rechnen benötigt; 
wäre die Größe signed, wäre klar, dass nur 2G reinpassen.

mh schrieb:
> Ich würd es eh so regeln:

Naja, ok. Die Idee war ja schon, dass der Input (der Pixel) irgendwie 
auch als signed vs unsigned daherkommt. Die "x_to_pixel"-Funktion war 
das konstruierte Beispiel für das Äquivalent zum std::vector::size oder 
sowas. Wenn man da die Signatur ändert, wird das Beispiel witzlos. Hatte 
ich aber zugegebenermaßen nicht besonders klar formuliert.

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.