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_ti
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)?
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).
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).
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_ti=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
constchar*my_strrchr(constchar*str,constcharch){
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(constchar*p=str+strlen(str)-1;p>=str;p--)
11
if(*p==ch)
12
returnp;
13
14
returnNULL;
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?
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:
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/
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
intarr[]={1,2,3,4,5};
2
constsize_tarr_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.
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!
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.
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.
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.
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.
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.
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.
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
charstr[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!
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.
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.
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?
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.
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.
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.
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...
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.
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.
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.
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.
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.
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.
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)
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.
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.
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.
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.
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
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.
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.
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.
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()/.
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
.
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:
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.
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?
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.