Hallo,
ich habe gerade wieder etwas gelernt. Im Nachhinein habe ich eine
Vermutung warum das Ergebnis bei a + b anders ist als bei c.
1 | /* add_char_overflow.c */
| 2 |
| 3 | #include <limits.h>
| 4 | #include <stdio.h>
| 5 |
| 6 | int
| 7 | main(void)
| 8 | {
| 9 | unsigned char a, b, c;
| 10 |
| 11 | for (a = 60; a < UCHAR_MAX - 40; a += 20)
| 12 | for (b = 30; b < UCHAR_MAX - 60; b += 30) {
| 13 | c = a + b;
| 14 | printf("%3d %3d %3d %3d\n", a, b, a+b, c);
| 15 | }
| 16 |
| 17 | return 0;
| 18 | }
| 19 |
| 20 | gcc -Wall -pedantic -ansi -o add_char_overflow add_char_overflow.c
|
1 | 60 30 90 90
| 2 | 60 60 120 120
| 3 | 60 90 150 150
| 4 | 60 120 180 180
| 5 | 60 150 210 210
| 6 | 60 180 240 240
| 7 | 80 30 110 110
| 8 | 80 60 140 140
| 9 | 80 90 170 170
| 10 | 80 120 200 200
| 11 | 80 150 230 230
| 12 | 80 180 260 4
| 13 | 100 30 130 130
| 14 | 100 60 160 160
| 15 | 100 90 190 190
| 16 | 100 120 220 220
| 17 | 100 150 250 250
| 18 | 100 180 280 24
| 19 | 120 30 150 150
| 20 | 120 60 180 180
| 21 | 120 90 210 210
| 22 | 120 120 240 240
| 23 | 120 150 270 14
| 24 | 120 180 300 44
| 25 | 140 30 170 170
| 26 | 140 60 200 200
| 27 | 140 90 230 230
| 28 | 140 120 260 4
| 29 | 140 150 290 34
| 30 | 140 180 320 64
| 31 | 160 30 190 190
| 32 | 160 60 220 220
| 33 | 160 90 250 250
| 34 | 160 120 280 24
| 35 | 160 150 310 54
| 36 | 160 180 340 84
| 37 | 180 30 210 210
| 38 | 180 60 240 240
| 39 | 180 90 270 14
| 40 | 180 120 300 44
| 41 | 180 150 330 74
| 42 | 180 180 360 104
| 43 | 200 30 230 230
| 44 | 200 60 260 4
| 45 | 200 90 290 34
| 46 | 200 120 320 64
| 47 | 200 150 350 94
| 48 | 200 180 380 124
|
Sollte in jedem C Buch stehen.
Klar ist die vierte Spalte (c). Erst nach kurzem Nachdenken ist mir auch
die dritte Spalte (a + b) klar.
1 | /* add_char_overflow.c */
| 2 |
| 3 | #include <limits.h>
| 4 | #include <stdio.h>
| 5 |
| 6 | int
| 7 | main(void)
| 8 | {
| 9 | unsigned char a, b, c;
| 10 |
| 11 | for (a = 60; a < UCHAR_MAX - 40; a += 20)
| 12 | for (b = 30; b < UCHAR_MAX - 60; b += 30) {
| 13 | c = a + b;
| 14 | printf("%3d %3d %3d %3d %s\n", a, b, a+b, c, (b < UCHAR_MAX - a) ? "Ok" : "Overflow");
| 15 | }
| 16 |
| 17 | return 0;
| 18 | }
|
1 | 60 30 90 90 Ok
| 2 | 60 60 120 120 Ok
| 3 | 60 90 150 150 Ok
| 4 | 60 120 180 180 Ok
| 5 | 60 150 210 210 Ok
| 6 | 60 180 240 240 Ok
| 7 | 80 30 110 110 Ok
| 8 | 80 60 140 140 Ok
| 9 | 80 90 170 170 Ok
| 10 | 80 120 200 200 Ok
| 11 | 80 150 230 230 Ok
| 12 | 80 180 260 4 Overflow
| 13 | 100 30 130 130 Ok
| 14 | 100 60 160 160 Ok
| 15 | 100 90 190 190 Ok
| 16 | 100 120 220 220 Ok
| 17 | 100 150 250 250 Ok
| 18 | 100 180 280 24 Overflow
| 19 | 120 30 150 150 Ok
| 20 | 120 60 180 180 Ok
| 21 | 120 90 210 210 Ok
| 22 | 120 120 240 240 Ok
| 23 | 120 150 270 14 Overflow
| 24 | 120 180 300 44 Overflow
| 25 | 140 30 170 170 Ok
| 26 | 140 60 200 200 Ok
| 27 | 140 90 230 230 Ok
| 28 | 140 120 260 4 Overflow
| 29 | 140 150 290 34 Overflow
| 30 | 140 180 320 64 Overflow
| 31 | 160 30 190 190 Ok
| 32 | 160 60 220 220 Ok
| 33 | 160 90 250 250 Ok
| 34 | 160 120 280 24 Overflow
| 35 | 160 150 310 54 Overflow
| 36 | 160 180 340 84 Overflow
| 37 | 180 30 210 210 Ok
| 38 | 180 60 240 240 Ok
| 39 | 180 90 270 14 Overflow
| 40 | 180 120 300 44 Overflow
| 41 | 180 150 330 74 Overflow
| 42 | 180 180 360 104 Overflow
| 43 | 200 30 230 230 Ok
| 44 | 200 60 260 4 Overflow
| 45 | 200 90 290 34 Overflow
| 46 | 200 120 320 64 Overflow
| 47 | 200 150 350 94 Overflow
| 48 | 200 180 380 124 Overflow
|
Alexander S. schrieb:
> gcc -Wall -pedantic -ansi -o add_char_overflow add_char_overflow.c
Auch wenn's jetzt in dem Fall keine Rolle spielt, solltest du mal deine
Optionen updaten. -ansi führt dazu, dass der Compiler auf das 35 Jahre
alte ANSI-C zurückschaltet.
Rolf M. schrieb:
> -ansi führt dazu, dass der Compiler auf das 35 Jahre alte ANSI-C
> zurückschaltet.
Wo ist da der Vorteil? Die Erklärung hast du offen gelassen.
900ss schrieb:
> Wo ist da der Vorteil?
Der Compiler würde auch meine Programme mögen ;) mit -ansi geht
garnichts:
- error: C++ style comments are not allowed in ISO C90
- error: 'for' loop initial declarations are only allowed in C99 or C11
mode
- warning: ISO C does not support '__FUNCTION__' predefined identifier
- warning: anonymous variadic macros were introduced in C99
- warning: comma at end of enumerator list
usw. Demnächst gibt's noch Binary Constants und falltgrough und noreturn
und und
C11: https://open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
C23: https://open-std.org/jtc1/sc22/wg14/www/docs/n3096.pdf
900ss schrieb:
> Rolf M. schrieb:
>> -ansi führt dazu, dass der Compiler auf das 35 Jahre alte ANSI-C
>> zurückschaltet.
>
> Wo ist da der Vorteil? Die Erklärung hast du offen gelassen.
Na dass eben alle Features, die der Sprache in den letzten 35 Jahren
hinzugefügt wurden, genutzt werden können. "Bauform" hat ja schon ein
paar davon gelistet. Wobei gcc manche auch mit -ansi fälschlicherweise
durchlässt, wie z.B. bool oder stdint.h, die es in C89 eigentlich nicht
gibt.
Alexander S. schrieb:
> Im Nachhinein habe ich eine
> Vermutung warum das Ergebnis bei a + b anders ist als bei c.
Da braucht man nichts zu vermuten.
Alle Berechnungen erfolgen immer so, als wären sie mindestens int. Und
auch printf(%d) erwartet int, d.h. bei Bedarf erfolgt eine Erweiterung
auf int.
Allein die Zuweisung auf c schneidet das höherwertige Byte wieder ab.
Rolf M. schrieb:
> 900ss schrieb:
>> Rolf M. schrieb:
>>> -ansi führt dazu, dass der Compiler auf das 35 Jahre alte ANSI-C
>>> zurückschaltet.
>>
>> Wo ist da der Vorteil? Die Erklärung hast du offen gelassen.
>
> Na dass eben alle Features, die der Sprache in den letzten 35 Jahren
> hinzugefügt wurden, genutzt werden können. "Bauform" hat ja schon ein
> paar davon gelistet. Wobei gcc manche auch mit -ansi fälschlicherweise
> durchlässt, wie z.B. bool oder stdint.h, die es in C89 eigentlich nicht
> gibt.
Ach shit, ich hatte das genau anders verstanden. Ich hatte es erst so
verstanden dass er -ansi nutzen soll und kratzte mich am Kopf und fragte
mich... äh? Dann geht ja "nichts" mehr :)
Aber eben habe ich gesehen, dass du eigentlich wolltest, dass er -ansi
aus seinen angegebenen Optionen streicht. Ich hatte die Option irgendwie
übersehen.
Bauform hat ja schöne Beispiele gebracht.
Ich nutze mal eine IDE im Job, die -ansi default setze wenn man ein
Projekt erzeugte. Das war das erste was ich gestrichen habe.
Hallo,
nur der Vollständigkeit halber hier die Version mit 'unsigned int' wo
für a+b und c immer das gleiche herauskommt.
1 | /* overflow_uint.c */
| 2 |
| 3 | #include <stdio.h>
| 4 | #include <limits.h>
| 5 |
| 6 | int
| 7 | main(void)
| 8 | {
| 9 | unsigned int a, b, c;
| 10 |
| 11 | printf("UINT_MAX = %u\n", UINT_MAX);
| 12 |
| 13 | for (a = UINT_MAX / 8; a < UINT_MAX - UINT_MAX / 4; a += UINT_MAX / 8)
| 14 | for (b = UINT_MAX / 8; b < UINT_MAX - UINT_MAX / 4; b += UINT_MAX / 8) {
| 15 | c = a + b;
| 16 | printf("%u %u %u %u %s\n", a, b, a + b, c, (b <= UINT_MAX - a) ? "Ok" : "Overflow");
| 17 | }
| 18 |
| 19 | a = UINT_MAX / 2;
| 20 | if (UINT_MAX % 2)
| 21 | b = a + 1;
| 22 | else
| 23 | b = a;
| 24 |
| 25 | c = a + b;
| 26 | printf("%u %u %u %u %s\n", a, b, a + b, c, (b <= UINT_MAX - a) ? "Ok" : "Overflow");
| 27 |
| 28 | return 0;
| 29 | }
|
1 | $ gcc -Wall -pedantic -ansi -o overflow_uint overflow_uint.c
| 2 |
| 3 | UINT_MAX = 4294967295
| 4 | 536870911 536870911 1073741822 1073741822 Ok
| 5 | 536870911 1073741822 1610612733 1610612733 Ok
| 6 | 536870911 1610612733 2147483644 2147483644 Ok
| 7 | 536870911 2147483644 2684354555 2684354555 Ok
| 8 | 536870911 2684354555 3221225466 3221225466 Ok
| 9 | 536870911 3221225466 3758096377 3758096377 Ok
| 10 | 1073741822 536870911 1610612733 1610612733 Ok
| 11 | 1073741822 1073741822 2147483644 2147483644 Ok
| 12 | 1073741822 1610612733 2684354555 2684354555 Ok
| 13 | 1073741822 2147483644 3221225466 3221225466 Ok
| 14 | 1073741822 2684354555 3758096377 3758096377 Ok
| 15 | 1073741822 3221225466 4294967288 4294967288 Ok
| 16 | 1610612733 536870911 2147483644 2147483644 Ok
| 17 | 1610612733 1073741822 2684354555 2684354555 Ok
| 18 | 1610612733 1610612733 3221225466 3221225466 Ok
| 19 | 1610612733 2147483644 3758096377 3758096377 Ok
| 20 | 1610612733 2684354555 4294967288 4294967288 Ok
| 21 | 1610612733 3221225466 536870903 536870903 Overflow
| 22 | 2147483644 536870911 2684354555 2684354555 Ok
| 23 | 2147483644 1073741822 3221225466 3221225466 Ok
| 24 | 2147483644 1610612733 3758096377 3758096377 Ok
| 25 | 2147483644 2147483644 4294967288 4294967288 Ok
| 26 | 2147483644 2684354555 536870903 536870903 Overflow
| 27 | 2147483644 3221225466 1073741814 1073741814 Overflow
| 28 | 2684354555 536870911 3221225466 3221225466 Ok
| 29 | 2684354555 1073741822 3758096377 3758096377 Ok
| 30 | 2684354555 1610612733 4294967288 4294967288 Ok
| 31 | 2684354555 2147483644 536870903 536870903 Overflow
| 32 | 2684354555 2684354555 1073741814 1073741814 Overflow
| 33 | 2684354555 3221225466 1610612725 1610612725 Overflow
| 34 | 3221225466 536870911 3758096377 3758096377 Ok
| 35 | 3221225466 1073741822 4294967288 4294967288 Ok
| 36 | 3221225466 1610612733 536870903 536870903 Overflow
| 37 | 3221225466 2147483644 1073741814 1073741814 Overflow
| 38 | 3221225466 2684354555 1610612725 1610612725 Overflow
| 39 | 3221225466 3221225466 2147483636 2147483636 Overflow
| 40 | 2147483647 2147483648 4294967295 4294967295 Ok
|
Alexander S. schrieb:
> nur der Vollständigkeit halber
Da fehlt dazwischen aber noch die Version mit uint16_t.
LG, Sebastian
Die Version mit uint16_t und 'unsigned long' darf jeder der möchte als
Hausaufgabe machen. :-)
Hallo,
einen habe ich noch. Nach einigen Antworten zuvor, ist mir aber klar,
dass das Ergebnis allen klar ist.
1 | #include <stdio.h>
| 2 | #include <limits.h>
| 3 |
| 4 | int
| 5 | main(int argc, char *argv[])
| 6 | {
| 7 | signed char c;
| 8 |
| 9 | c = SCHAR_MIN;
| 10 | printf("%d %d\n", c, c - 1);
| 11 | c = SCHAR_MIN;
| 12 | printf("%d %d\n", c, c - (signed char) 1);
| 13 | c = SCHAR_MIN;
| 14 | printf("%d ", c);
| 15 | --c;
| 16 | printf("%d\n", c);
| 17 |
| 18 |
| 19 | c = SCHAR_MAX;
| 20 | printf("%d %d\n", c, c + 1);
| 21 | c = SCHAR_MAX;
| 22 | printf("%d %d\n", c, c + (signed char) 1);
| 23 | c = SCHAR_MAX;
| 24 | printf("%d ", c);
| 25 | ++c;
| 26 | printf("%d\n", c);
| 27 |
| 28 | return 0;
| 29 | }
|
1 | $ gcc -Wall -pedantic -ansi -o overflow_signed_char overflow_signed_char.c
| 2 | $ ./overflow_signed_char
| 3 | -128 -129
| 4 | -128 -129
| 5 | -128 127
| 6 | 127 128
| 7 | 127 128
| 8 | 127 -128
|
Mir ist das Verhalten in so weit klar, als das bei C das Verhalten bei
underflow und overflow von signed char oder signed int, im Gegensatz zu
unsigned char oder unsigned int, nicht definiert ist.
Beitrag #7600790 wurde von einem Moderator gelöscht.
Alexander S. schrieb:
> Mir ist das Verhalten in so weit klar, als das bei C das Verhalten bei
> underflow und overflow von signed char oder signed int, im Gegensatz zu
> unsigned char oder unsigned int, nicht definiert ist.
Bis auf das
> --c;
und das
> ++c;
ist alles wohldefiniert.
Rolf M. schrieb:
> ist alles wohldefiniert.
Nicht für signed int.
https://en.wikipedia.org/wiki/Integer_overflow
Tabelle "Integer overflow handling in various programming languages"
C/C++: Signed integer, undefined behavior
http://cs.yale.edu/homes/aspnes/classes/223/notes.html#overflow-and-the-c-standards
P.S. Im Standard selber habe ich nicht nachgeschaut.
Alexander S. schrieb:
> Rolf M. schrieb:
>> ist alles wohldefiniert.
>
> Nicht für signed int.
Richtig, aber der Wertebereich von signed int ist auf jeden Fall groß
genug, um den von dir verwendeten Zahlenbereich abzudecken, daher gibt
es hier außer bei ++ und -- kein Problem.
Denn wie oben schon erwähnt wurde:
Peter D. schrieb:
> Alle Berechnungen erfolgen immer so, als wären sie mindestens int.
Das nennt sich "integer promotion". Das heißt, dass hier:
Alexander S. schrieb:
> c - 1
c erst mal auf int erweitert wird und dann die Berechnung mit dem Typ
durchgeführt wird. Das Ergebnis ist natürlich dann auch vom Typ int.
Somit kann da nix überlaufen, weil der Wertebereich von int groß genug
ist, um das Ergebnis aufzunehmen.
Alexander S. schrieb:
> c - (signed char) 1
Hier ist es das gleiche. Dein Cast ändert daran nichts. Es wird durch
ihn die 1 lediglich erst nach signed char konvertiert, bevor sie dann
sofort wieder (wie die Variable c) nach int konvertiert wird. Die
Berechnung erfolgt dann auch wieder mit Typ int.
Lediglich bei --c und ++c gibt es ein Problem, weil da das Ergebnis ja
wieder nach c zurückgeschrieben wird, das aber nicht groß genug dafür
ist.
Dann kannst Du sicher auch erklären, warum 1 | signed char c;
| 2 |
| 3 | c = c + 1;
|
und
unterschiedliche Ergebnisse liefern.
Weil c in beiden Fällen uninitialisiert ist und dein Code dadurch
undefiniertes Verhalten auslöst. Aber ich vermute, du wolltest hier c
mit SCHAR_MAX initialisieren. Dann hast du aber trotzdem undefiniertes
Verhalten, weil eben der Wertebereich von c nicht mehr ausreicht.
Laut C-Standard ist ++c äquivalent zu c+=1 ("The expression ++E is
equivalent to (E+=1), where the value 1 is of the appropriate type.")
und das wiederum ist (mit der Ausnahme dass c nur einmal evaluiert wird)
äquivalent zu c = c + 1. ("A compound assignment of the form E1 op= E2
is equivalent to the simple assignment expression E1 = E1 op (E2),
except that the lvalue E1 is evaluated only once, and with respect to an
indeterminately sequenced function call, the operation of a compound
assignment is a single evaluation")
Da das Verhalten in diesem Fall undefiniert ist, muss das Ergebnis aber
nicht gleich sein.
Hallo Rolf,
der entscheidende Punkt ist wohl, dass man zwischen 1 | signed char c;
| 2 | c = SCHAR_MAX;
| 3 | printf("%d", c + 1);
|
und 1 | signed char c;
| 2 | c = SCHAR_MAX;
| 3 | c = c + 1;
| 4 | printf("%d", c);
|
unterscheiden muss. Im ersten Fall ist der Wertebereich int und im
zweiten Fall signed char.
1 | #include <stdio.h>
| 2 | #include <limits.h>
| 3 |
| 4 | int
| 5 | main(int argc, char *argv[])
| 6 | {
| 7 | signed char c;
| 8 |
| 9 | c = SCHAR_MIN;
| 10 | printf("%d ", c);
| 11 | c = c - (signed char) 1;
| 12 | printf("%d\n", c);
| 13 | c = SCHAR_MIN;
| 14 | printf("%d ", c);
| 15 | --c;
| 16 | printf("%d\n", c);
| 17 |
| 18 |
| 19 | c = SCHAR_MAX;
| 20 | printf("%d ", c);
| 21 | c = c + (signed char) 1;
| 22 | printf("%d\n", c);
| 23 | c = SCHAR_MAX;
| 24 | printf("%d ", c);
| 25 | ++c;
| 26 | printf("%d\n", c);
| 27 |
| 28 | return 0;
| 29 | }
|
1 | $ ./overflow_signed_char
| 2 | -128 127
| 3 | -128 127
| 4 | 127 -128
| 5 | 127 -128
|
Alexander S. schrieb:
> der entscheidende Punkt ist wohl, dass man zwischensigned char c;
> c = SCHAR_MAX;
> printf("%d", c + 1);
>
> undsigned char c;
> c = SCHAR_MAX;
> c = c + 1;
> printf("%d", c);
>
> unterscheiden muss. Im ersten Fall ist der Wertebereich int und im
> zweiten Fall signed char.
Ja, weil du im zweiten Fall das Ergebnis nach c zurückschreibst, das vom
Typ signed char ist, in den es aber nicht reinpasst. Im ersten Fall
dagegen verarbeitest du das Ergebnis der Addition direkt als int weiter.
Somit läuft da nichts über. Würdest du schreiben:
dann wäre das Ergebnis das gleiche wie im ersten Fall.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
|