Discussion:
Einlesen, Rechnen und Ausgabe ohne undefiniertes Verhalten
(zu alt für eine Antwort)
Thomas Koenig
2015-06-27 11:03:44 UTC
Permalink
Raw Message
Hallo miteinander,

mal als Gedankenspiel: Wie müsste ein Programm aussehen, das zwei
Zahlen einliest, mit ihnen eine Rechenoperation durchführt und das
Ergebnis ausgibt, und zwar für verschiedene Standard-Datentypen
(int, float, double). Rechenoperation könnte Addition, Subtraktion,
Multiplikation oder Division sein. Das Ergebnis soll korrekt
sein.

Nebenbedingungen:

a) Die Eingabe soll beliebig sein.

b) Das Programm soll unter keinen Umständen "undefined behavior"
verwenden

c) Fehlermeldungen bei Überschreitung von Implementierungs-Limits
sind OK.

d) Das Programm soll die vorhandene Breite der Datentypen voll ausnutzen
(also z.B. nicht bei einer Multiplikation von zwei int-Werten eine
Fehlermeldung ausgeben, wenn eine der beiden Zahlen größer als 255
ist).

Deutlich erschwerend wäre noch eine Bedingung

e) Das Programm darf nur den Datentyp verwenden, mit dem auch
gerechnet werden soll.

Spannende Themen wären z.B:

Integer-Zahlen wie 12345678901234567890123456789012345678901234567890
sollten bei Eingabe eine Fehlermeldung erzeugen, wenn der Datentyp es nicht
packt. Was macht denn scanf() damit...?

Real-Zahlen wie 1.2e345678 bei der Eingabe (ebenso, wwas macht scanf damit)?

Subtraktion von 2147483640 - (-2147483648 )

Division von 1e30 durch 1e-30 bei typischen Float-Implementierungen

Multiplikation von 123456 mit 654321

usw.
Helmut Schellong
2015-06-27 12:23:35 UTC
Permalink
Raw Message
On 06/27/15 13:03, Thomas Koenig wrote:
[...]
Post by Thomas Koenig
Integer-Zahlen wie 12345678901234567890123456789012345678901234567890
sollten bei Eingabe eine Fehlermeldung erzeugen, wenn der Datentyp es nicht
packt. Was macht denn scanf() damit...?
Einerseits ist das gar kein Problem.
Man kann vielfältig prüfen und Signale abfangen.

Andererseits kann es keine korrekten Ergebnisse geben, wenn der
Input gar nicht (genau) verarbeitet werden kann.

Ich würde nicht einfach so Standard-Funktionen für sowas
verwenden. Ein wenig Mühe geben muß man sich schon.

Lies den Standard und die man-Page zu scanf() ...
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Stefan Ram
2015-06-27 14:17:04 UTC
Permalink
Raw Message
Post by Thomas Koenig
mal als Gedankenspiel: Wie müsste ein Programm aussehen, das zwei
Zahlen einliest, mit ihnen eine Rechenoperation durchführt und das
Ergebnis ausgibt
Ich beschränke mich zunächst einmal auf »int« und die Addition
zweier Zahlen.

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

int add( int const i, int const j )
{ if( ( ( i ^ j )|( ( ( i^( ~( i ^ j )& INT_MIN ))+ j )^ j ))>= 0 )
{ fprintf( stderr, "overflow.\n" ); return EXIT_FAILURE; } else
return printf( "%d\n", i+j ) > 0 ? EXIT_SUCCESS : EXIT_FAILURE; }

int main( void )
{ int i, j; if( 2 != scanf( "%d%d", &i, &j ))
{ fprintf( stderr, "input error.\n" ); return EXIT_FAILURE; }
else return add( i, j ); }

Der Rest für die anderen Typen und Operationen geht
teilweise entsprechend (Fleißarbeit), manchmal sind die
Anforderungen auch widersprüchlich, denn Gleitkommatypen
sind eben für Näherungen gedacht. Entweder man verwendet
»double« und akzeptiert die Näherungen oder man verwendet
einen eigenen Typ für rationale Zahlen.
Stefan Ram
2015-06-27 15:36:55 UTC
Permalink
Raw Message
Post by Stefan Ram
{ if( ( ( i ^ j )|( ( ( i^( ~( i ^ j )& INT_MIN ))+ j )^ j ))>= 0 )
Dieser Test ist so allerdings nur auf Maschinen mit
Zweierkomplementdarstellung möglich, allgemeiner ist:

if( i > 0 && j > 0 && i >( INT_MAX - j )|| i < 0 && j < 0 && i <( INT_MIN - j ))
Post by Stefan Ram
if( 2 != scanf( "%d%d", &i, &j ))
Leider hat »scanf« doch wohl in bestimmten Fällen
undefiniertes Verhalten:

»if the result of the conversion cannot be represented
in the object, the behavior is undefined«

Daher muß man die Ziffern dann wohl mit putchar lesen und
den Überlauf manuell erkennen. Die untenstehende Funktion
»read« wurde dafür soeben eigens von mir geschrieben und
enthält daher vielleicht noch Fehler.

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <ctype.h>

int add( int const i, int const j )
{ if
( i > 0 && j > 0 && i >( INT_MAX - j )||
i < 0 && j < 0 && i <( INT_MIN - j ))
{ fprintf( stderr, "overflow.\n" ); return EXIT_FAILURE; } else
return printf( "%d\n", i+j ) > 0 ? EXIT_SUCCESS : EXIT_FAILURE; }

int read( int * p )
{ int const ch = getchar();
if( ch < 0 )return 0;
if( ch == '-' )
fprintf( stderr, "minus sign not implemented yet.\n" ), exit( 99 );
if( isdigit(( unsigned char )ch ))
{ int value = ch - '0';
do
{ int const ch = getchar();
if( isdigit(( unsigned char )ch ))
{ int const oldvalue = value;
value *= 10;
if( value <= oldvalue || value / 10 != oldvalue )return 0;
int const oldvalue1 = value;
value += ch - '0';
if( value < oldvalue1 || value -( ch - '0' )!= oldvalue1 )
return 0;
continue; }
else break; }
while( 1 );
*p = value;
return 1; }
else return 0; }

int main( void )
{ int i, j; if( read( &i )&& read( &j ))return add( i, j );
fprintf( stderr, "input error.\n" ); return EXIT_FAILURE; }
Helmut Schellong
2015-06-27 16:10:11 UTC
Permalink
Raw Message
[...]
Post by Stefan Ram
value *= 10;
if( value <= oldvalue || value / 10 != oldvalue )return 0;
[...]

value*=10;
kann schon einen Überlauf produzieren.

Auch
if (ptr+b > bufend)
kann das.

Man muß tatsächlich
if (b > bufend-ptr)
programmieren, wenn man's perfekt machen will.
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Stefan Ram
2015-06-27 16:42:00 UTC
Permalink
Raw Message
Post by Helmut Schellong
value*=10;
kann schon einen Überlauf produzieren.
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <ctype.h>

int addint( int const i, int const j )
{ return
i > 0 && j > 0 && i >( INT_MAX - j )||
i < 0 && j < 0 && i <( INT_MIN - j ); }

int mulint( int const i, int const j )
{ if( i > 0 )
{ if( j > 0 ){ if( i > INT_MAX / j )return 1; }
else { if ( j < INT_MIN / i )return 2; }}
else
{ if( j > 0 ){ if ( i < INT_MIN / j )return 3; }
else if ( i != 0 && j < INT_MAX / i )return 4; }
return 0; }

int add( int const i, int const j )
{ if( addint( i, j ))
{ fprintf( stderr, "overflow.\n" ); return EXIT_FAILURE; } else
return printf( "%d\n", i+j ) > 0 ? EXIT_SUCCESS : EXIT_FAILURE; }

int read( int * p )
{ int const ch = getchar();
if( ch < 0 )return 0;
if( ch == '-' )
fprintf( stderr, "minus sign not implemented yet.\n" ), exit( 99 );
if( isdigit(( unsigned char )ch ))
{ int value = ch - '0';
do
{ int const ch = getchar();
if( isdigit(( unsigned char )ch ))
{ int const oldvalue = value;
if( mulint( value, 10 ))return 0;
value = value * 10;
int const oldvalue1 = value;
int const v = ch - '0';
if( addint( value, v ))return 0;
value = value + v;
if( value < oldvalue1 || value -( ch - '0' ) != oldvalue1 )
return 0;
continue; }
else break; }
while( 1 );
*p = value;
return 1; }
else return 0; }

int main( void )
{ int i, j; if( read( &i )&& read( &j ))return add( i, j );
fprintf( stderr, "input error.\n" ); return EXIT_FAILURE; }
Rainer Weikusat
2015-06-27 21:24:43 UTC
Permalink
Raw Message
Post by Stefan Ram
Post by Helmut Schellong
value*=10;
kann schon einen Überlauf produzieren.
[...]
Post by Stefan Ram
int addint( int const i, int const j )
{ return
i > 0 && j > 0 && i >( INT_MAX - j )||
i < 0 && j < 0 && i <( INT_MIN - j ); }
int mulint( int const i, int const j )
{ if( i > 0 )
{ if( j > 0 ){ if( i > INT_MAX / j )return 1; }
else { if ( j < INT_MIN / i )return 2; }}
else
{ if( j > 0 ){ if ( i < INT_MIN / j )return 3; }
else if ( i != 0 && j < INT_MAX / i )return 4; }
return 0; }
[...]
Post by Stefan Ram
int read( int * p )
{ int const ch = getchar();
if( ch < 0 )return 0;
if( ch == '-' )
fprintf( stderr, "minus sign not implemented yet.\n" ), exit( 99 );
if( isdigit(( unsigned char )ch ))
{ int value = ch - '0';
do
{ int const ch = getchar();
if( isdigit(( unsigned char )ch ))
{ int const oldvalue = value;
if( mulint( value, 10 ))return 0;
value = value * 10;
int const oldvalue1 = value;
int const v = ch - '0';
if( addint( value, v ))return 0;
value = value + v;
if( value < oldvalue1 || value -( ch - '0' ) != oldvalue1 )
return 0;
continue; }
else break; }
while( 1 );
*p = value;
return 1; }
else return 0; }
Die 'abstrakte Maschine', die von der C-Norm fuer die Beschreibung der
Semantik von C benutzt wird, zu programmieren, ist ein etwas
wunderliches Hobby, aber so wild ist sie dann doch nicht:

#define LIMIT (INT_MAX / 10)

int read( int * p )
{
unsigned ch = getchar();
int value;

if( ch == '-' )
fprintf( stderr, "minus sign not implemented yet.\n" ), exit( 99 );

ch -= '0';
if (ch > 9) return 0;
value = ch;

while (ch = getchar(), (ch -= '0') < 10) {
if (value > LIMIT) return 0;
value *= 10;

if (value > INT_MAX - ch) return 0;
value += ch;
}

*p = value;
return 1;
}

Am Rande: Wie steht eigentlich clang bezueglich der
gcc-Bloedsinnserfindung "undefiniertes Ueberlaufverhalten von
vorzeichenbehafteter Zweierkomplementarithmetik auf Maschinen, die sich
nicht um solche Ueberlaeufe kuemmern" ("Aber vor 10,000 Jahren habe ich
mal von einem Bueros oder so Computer gehoert, der Einerkomplement tatsaechlich
implementierte und bei Ueberlauf unter Absingen islamischer Hymnen
explodierte!").
Helmut Schellong
2015-06-28 07:01:40 UTC
Permalink
Raw Message
Post by Stefan Ram
Post by Helmut Schellong
value*=10;
kann schon einen Überlauf produzieren.
[...]
Post by Stefan Ram
int read( int * p )
[...]

ssize_t read(int fd, void *buf, size_t nbytes);

Ich würde 'read' nicht verwenden.
Mit 'static' vielleicht.
Post by Stefan Ram
Am Rande: Wie steht eigentlich clang bezueglich der
gcc-Bloedsinnserfindung "undefiniertes Ueberlaufverhalten von
vorzeichenbehafteter Zweierkomplementarithmetik auf Maschinen, die sich
nicht um solche Ueberlaeufe kuemmern" ("Aber vor 10,000 Jahren habe ich
mal von einem Bueros oder so Computer gehoert, der Einerkomplement tatsaechlich
implementierte und bei Ueberlauf unter Absingen islamischer Hymnen
explodierte!").
Meiner Erfahrung nach muß in C der Programmierer darauf achten, daß
kein Überlauf und ähnliches passiert.
Ein C-Compiler macht da (generell) keinerlei Absicherung.
Bei Pascal, mußte ich in der Praxis erfahren, ist eine ziemlich totale
Absicherung vorhanden.
Ein Pascal-Compiler akzeptiert einfach nichts, daß irgendwie nicht paßt.
32Bit-int mit 32Bit-unsigned operativ verknüpfen, geht gar nicht
(ohne explizite Umwandlung).
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Peter J. Holzer
2015-06-28 09:56:31 UTC
Permalink
Raw Message
Post by Rainer Weikusat
Am Rande: Wie steht eigentlich clang bezueglich der
gcc-Bloedsinnserfindung "undefiniertes Ueberlaufverhalten von
vorzeichenbehafteter Zweierkomplementarithmetik auf Maschinen, die
sich nicht um solche Ueberlaeufe kuemmern" ("Aber vor 10,000 Jahren
habe ich mal von einem Bueros oder so Computer gehoert, der
Einerkomplement tatsaechlich implementierte und bei Ueberlauf unter
Absingen islamischer Hymnen explodierte!").
Meines Wissens genauso.

Wenn in einer Funktion der Ausdruck »x + 5« vorkommt und x vom Typ int
ist, dann geht der Compiler davon aus, dass an dieser Stelle x <=
INT_MAX - 5 ist und optimiert entsprechend.

Das kann dazu führen, dass z.B. aus Code wie

int plus5(int x) {
if (x > INT_MAX - 5) {
printf("Warning: x too large");
}
return x + 5;
}

der ganze if-Block herausoptimiert wird und keine Warnung ausgegeben
wird (Die Versionen von gcc und clang, die ich hier habe, machen das bei
diesem Beispiel nicht, aber ich habe erst kürzlich einen sehr ähnlichen
Fall gesehen).

Zumindest die Autoren von clang sind sich dessen bewusst, dass das ein
Problem sein kann (es gab einige Blog-Einträge zu dem Thema in letzter
Zeit), aber das Problem ist nicht leicht zu lösen:

Einerseits will man natürlich Optimierungen durchführen können, und
gerade "undefiniertes Verhalten" ist eine der reichsten Quellen für
Optimierungsmöglichkeiten in C. (Oder umgekehrt gesehen: Die Autoren des
C-Standards haben vieles offen gelassen, um entsprechende Optimierungen
zu erlauben). In vielen Fällen ist sich der Programmierer auch dessen
bewusst und will nicht mit Warnungen genervt werden.

Andererseits ist undefiniertes Verhalten natürlich eine Quelle von Bugs,
und wenn der Compiler undefiniertes Verhalten entdeckt, sollte er den
Programmierer darauf aufmerksam machen und nicht stillschweigend
ausnützen, um Code zu erzeugen, der zwar schnell ist, aber nicht das
tut, was der Programmierer gemeint hat.

Zu erkennen, was der Programmierer gemeint hat, ist halt schwierig.

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Rainer Weikusat
2015-06-28 15:14:05 UTC
Permalink
Raw Message
Post by Peter J. Holzer
Post by Rainer Weikusat
Am Rande: Wie steht eigentlich clang bezueglich der
gcc-Bloedsinnserfindung "undefiniertes Ueberlaufverhalten von
vorzeichenbehafteter Zweierkomplementarithmetik auf Maschinen, die
sich nicht um solche Ueberlaeufe kuemmern" ("Aber vor 10,000 Jahren
habe ich mal von einem Bueros oder so Computer gehoert, der
Einerkomplement tatsaechlich implementierte und bei Ueberlauf unter
Absingen islamischer Hymnen explodierte!").
Meines Wissens genauso.
Wenn in einer Funktion der Ausdruck »x + 5« vorkommt und x vom Typ int
ist, dann geht der Compiler davon aus, dass an dieser Stelle x <=
INT_MAX - 5 ist und optimiert entsprechend.
Das kann dazu führen, dass z.B. aus Code wie
int plus5(int x) {
if (x > INT_MAX - 5) {
printf("Warning: x too large");
}
return x + 5;
}
der ganze if-Block herausoptimiert wird und keine Warnung ausgegeben
wird (Die Versionen von gcc und clang, die ich hier habe, machen das bei
diesem Beispiel nicht, aber ich habe erst kürzlich einen sehr ähnlichen
Fall gesehen).
[...]
Post by Peter J. Holzer
Einerseits will man natürlich Optimierungen durchführen können, und
gerade "undefiniertes Verhalten" ist eine der reichsten Quellen für
Optimierungsmöglichkeiten in C. (Oder umgekehrt gesehen: Die Autoren des
C-Standards haben vieles offen gelassen, um entsprechende Optimierungen
zu erlauben).
Diese Intention wird angenommen. Ich sehe das allerdings nicht so:
Meiner Ansicht nach sind die meisten Vagheiten in der C-Norm darauf
zurueckzufuehren, dass das Verhalten existierender Hardware
beruecksichtigt werden musste bzw dass man Beschraekungen moeglichen
Hardware-Verhaltens vermeiden wollte. Sign-and-magnitude und
Einerkomplement sind beide trotz ihrer Nachteile verwendet worden
deswegen kann die C-Norm nicht verlangen, dass INT_MAX + n ein
bestimmtes Ergebnis hat. Seien INT_MAX 65535 und n 65000 so ergibt sich
64999 als Result (0xfde7). Je nachdem welches der drei normierten
Verfahren verwendet wird, bedeutet das -537 (Zweierkomplement), -536
(Einerkomplement) oder -32239 (Sign-and-magnitude). Vollkommen andere
Resulate sind auch moeglich, zB 65535, und wie sich unschwer via Google
festellen laesst, gibt es auch immer noch Leute, die Entschieden(!)
davon ueberzeugt sind, das modulo-Arithmetik ueberhaupt keine
Existenzberechtigung haben duerfte,

http://blog.regehr.org/archives/1154

denn sie produziert ja "falsche Ergebnisse" (das ist natuerlich
bluehender Bloedsinn, denn die Operatoren verhalten sich exakt so, wie
sie definiert sind, lediglich ist dieses Verhalten nicht immer das
gewuenschte) und trotz fortwaehrendem kommerziellem Misserfolg kann man
nicht ausschliessen, das auch mal wieder neue Hardware "Explosion bei
Integerueberlauf" als Feature anbietet.

Das sind aber alles Dinge, mit denen ich als Programmierer von
Maschinen, deren anderes Verhalten ich fuer sinnvoll halte, nicht
belaestigt werden will und auch nicht belaestigt werden sollte. Erst
recht nicht von windigen, heuristischen Algorithmen, die zu erraten
versuchen, welcher Code besser nie geschrieben worde waere, um ihn
transparent unter den Tisch fallen zu lassen, ohne das der oder die
Verwantwortlichen zugeben muessen, dass hier Verbesserungsbedarf
bestanden haette.
Rainer Weikusat
2015-06-28 19:08:49 UTC
Permalink
Raw Message
Rainer Weikusat <***@mobileactivedefense.com> writes:

[...]
die C-Norm [kann] nicht verlangen, dass INT_MAX + n ein
bestimmtes Ergebnis hat. Seien INT_MAX 65535 und n 65000 so ergibt sich
64999 als Result (0xfde7). Je nachdem welches der drei normierten
Verfahren verwendet wird, bedeutet das -537 (Zweierkomplement), -536
(Einerkomplement) oder -32239 (Sign-and-magnitude).
Diese Beispiel ist Humbug: Fuer 16-bit Integers sollten es eher 32767
und 2 sein mit den moeglichen Intepretationsn als -32767, -32766 und -1.
Peter J. Holzer
2015-06-28 22:21:25 UTC
Permalink
Raw Message
Post by Rainer Weikusat
Post by Peter J. Holzer
Einerseits will man natürlich Optimierungen durchführen können, und
gerade "undefiniertes Verhalten" ist eine der reichsten Quellen für
Optimierungsmöglichkeiten in C. (Oder umgekehrt gesehen: Die Autoren des
C-Standards haben vieles offen gelassen, um entsprechende Optimierungen
zu erlauben).
Meiner Ansicht nach sind die meisten Vagheiten in der C-Norm darauf
zurueckzufuehren, dass das Verhalten existierender Hardware
beruecksichtigt werden musste
Niemand *muss* das Verhalten existierender Hardware beim Festlegen der
Semantik einer Programmiersprache berücksichtigen. Man kann immer ein
bestimmtes Verhalten festlegen. Die Implementierung wird halt dann mehr
oder weniger effizient.
Post by Rainer Weikusat
bzw dass man Beschraekungen moeglichen Hardware-Verhaltens vermeiden
wollte. Sign-and-magnitude und Einerkomplement sind beide trotz ihrer
Nachteile verwendet worden deswegen kann die C-Norm nicht verlangen,
dass INT_MAX + n ein bestimmtes Ergebnis hat.
Natürlich könnte sie das. Tut sie aber nicht. Und der Grund hier war
eindeutig, dass man auf jeder Plattform die effizienteste Implementation
zulassen wollte, um den Preis der Inkompatibilität (bzw. musste sich der
Programmierer selbst darum kümmern, seinen Code portabel zu gestalten).
Ein weiterer Grund wäre "existing code matters", aber das scheint mir
hier keine Rolle gespielt zu haben - denn da Overflow nun undefined war,
konnte sich (hypothetischer) existierender Code, der z.B. das Verhalten
einer bestimmten Sign-Magnitude-Plattform erwartet hat, auch nicht
darauf verlassen (immerhin durfte der Compiler das bisherige Verhalten
beibehalten und sich ANSI-C-konform nennen, was bei einer
Standardisierung auf ein bestimmtes Verfahren nicht der Fall gewesen
wäre).
Post by Rainer Weikusat
Seien INT_MAX 65535 und n 65000 so ergibt sich
64999 als Result (0xfde7).
Ich glaube, da ist Dir was durcheinandergekommen.
Post by Rainer Weikusat
gibt es auch immer noch Leute, die Entschieden(!) davon ueberzeugt
sind, das modulo-Arithmetik ueberhaupt keine Existenzberechtigung
haben duerfte,
http://blog.regehr.org/archives/1154
Der schreibt nicht, dass Modulo-Arithmetik überhaupt keine
Existenzberechtigung hat, sondern dass Arithmetik mit Overflow der
default sein sollte.
Post by Rainer Weikusat
denn sie produziert ja "falsche Ergebnisse" (das ist natuerlich
bluehender Bloedsinn, denn die Operatoren verhalten sich exakt so, wie
sie definiert sind, lediglich ist dieses Verhalten nicht immer das
gewuenschte)
Für ein richtiges Ergebnis reicht es nicht, wenn der Operator sich so
verhält wie er definiert ist, man muss ihn auch richtig verwenden.

Und für die allermeisten Probleme ist modulo-Arithmetik einfach das
falsche Werkzeug. Wenn ich die Fläche eines Rechtecks berechnen will und
zwei unsigned Werte miteinander multipliziere, erhalte ich ein
wohldefiniertes Ergebnis - aber für genügend große Werte ist es einfach
falsch.
Post by Rainer Weikusat
und trotz fortwaehrendem kommerziellem Misserfolg kann man
nicht ausschliessen, das auch mal wieder neue Hardware "Explosion bei
Integerueberlauf" als Feature anbietet.
AMD/Intel haben ja im 64-Bit-Modus INTO gestanzt. Aber so schwierig ist
das auch ohne diese Instruktion für einen Compiler nicht umzusetzen.
Clang z.B. erzeugt mit -ftrapv (diese Option kennt übrigens auch gcc)
einfach ein JO nach jeder Operation.

Dass zumindest zwei weitverbreitete Compiler eine Option zum Abfangen
von int-Overflows haben, könnte natürlich dazu führen, dass diese in
Zukunft mehr verwendet wird und sich Intel, AMD und ARM darüber Gedanken
machen, wie man das möglichst effizient machen kann (wobei das
vielleicht eh schon recht effizient ist - da das Overflow-Flag im
Normalfall nicht gesetzt ist, ist das im wesentlichen ein Nop - stellt
sich nur die Frage, ob man das ohne Bubble in der Pipeline hinbekommt).
Post by Rainer Weikusat
Das sind aber alles Dinge, mit denen ich als Programmierer von
Maschinen, deren anderes Verhalten ich fuer sinnvoll halte, nicht
belaestigt werden will und auch nicht belaestigt werden sollte. Erst
recht nicht von windigen, heuristischen Algorithmen, die zu erraten
versuchen, welcher Code besser nie geschrieben worde waere, um ihn
transparent unter den Tisch fallen zu lassen, ohne das der oder die
Verwantwortlichen zugeben muessen, dass hier Verbesserungsbedarf
bestanden haette.
Wenn Du Code schreibst, der in C undefiniertes Verhalten hat, und gerne
definiertes Verhalten hättest, solltest Du vielleicht eine andere
Programmiersprache wählen. C ist nun mal die "the compiler will get its
revenge"-Sprache.

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Thomas Koenig
2015-06-29 06:34:35 UTC
Permalink
Raw Message
[F'up]
Post by Peter J. Holzer
Wenn Du Code schreibst, der in C undefiniertes Verhalten hat, und gerne
definiertes Verhalten hättest, solltest Du vielleicht eine andere
Programmiersprache wählen. C ist nun mal die "the compiler will get its
revenge"-Sprache.
Da kann ich dir nur zustimmen. Es gibt genügend Fallen,
in die man mit C (und C++) reintappen kann, die in anderen
Programmiersprachen so nicht vorkommen können. CVE ist voll
davon, integer overflow ist nur eine Möglichkeit, use after
free was anderes.

Es ist schon ein bisschen komisch. In der Zeit, als die
grundlegenden Konzepte der Informatik entwickelt wurden,
waren die Computer viele Größenordnungen langsamer als heute.
Damals kann man verstehen, dass Geschwindigkeit vor Sicherheit
ging - die Kosten pro CPU-Zyklus waren einfach zu hoch. (Ob das
betriebswirtscahftlich wirklich richtig war, so lange nach
eigentlich vermeidbaren Fehlern zu suchen, sei mal dahingestellt).

Und was macht man mit dieser immensen Recchenpower? In
runtime-checks investieren, die die Programme sicherer machen?
Nein, man verbrät das mit Code Bloat.

Heutzutage wartet man locker so viele Taktzyklen auf das Starten
von Excel, wie eine PDP-11 in einem Jahr hatte. Das juckt keinen,
aber ein paar Zyklen für Laufzeit-Tests zu verbraten... das geht
ja gar nicht.

Ada existiert, sogar mit einem freien Compiler. Wer will, kann
problemlos eine deutlich sicherere Sprache wählen. Nur macht
es kaum jemand. Wieso eigentlich?
Stefan Ram
2015-06-29 13:47:49 UTC
Permalink
Raw Message
Newsgroups: de.comp.lang.c,de.comp.lang.misc
Followup-To: de.comp.lang.misc
Post by Thomas Koenig
Und was macht man mit dieser immensen Recchenpower? In
runtime-checks investieren, die die Programme sicherer machen?
Nein, man verbrät das mit Code Bloat.
Du beschwertest Dich hier ja wohl über C. Dies ist aber /die/
Sprache, die laut language shootout am schnellsten ist.
Sogar noch schneller als C++! Also gerade die Sprache
mit dem geringsten Code Bloat (wenn man nicht in Assembler
programmieren will).

Assembler wäre für Dich vielleicht interessant, weil es
dort keinen Code Bloat gibt und sogar weniger undefiniertes
Verhalten als in C, solange Du nur dokumentierte Op-Codes
verwendest.

Dank Emulatoren ist Assembler heute nicht mehr ganz
unportabel: Assembler-Programme, die einst für den C64 oder
Amiga geschrieben wurden,j laufen damit heute unter Windows
10 (und vielleicht auch unter Linux oder Macintosh), und
sogar viel schneller als die Originale!

Newsgroups: de.comp.lang.c,de.comp.lang.misc
Followup-To: de.comp.lang.misc
Rainer Weikusat
2015-06-29 15:12:22 UTC
Permalink
Raw Message
Post by Thomas Koenig
[F'up]
dclm ist als Umgebung fuer Diskussionen nicht zumutbar.
Post by Thomas Koenig
Post by Peter J. Holzer
Wenn Du Code schreibst, der in C undefiniertes Verhalten hat, und gerne
definiertes Verhalten hättest, solltest Du vielleicht eine andere
Programmiersprache wählen. C ist nun mal die "the compiler will get its
revenge"-Sprache.
Da kann ich dir nur zustimmen. Es gibt genügend Fallen,
in die man mit C (und C++) reintappen kann, die in anderen
Programmiersprachen so nicht vorkommen können. CVE ist voll
davon, integer overflow ist nur eine Möglichkeit, use after
free was anderes.
Es ist schon ein bisschen komisch. In der Zeit, als die
grundlegenden Konzepte der Informatik entwickelt wurden,
waren die Computer viele Größenordnungen langsamer als heute.
Damals kann man verstehen, dass Geschwindigkeit vor Sicherheit
ging - die Kosten pro CPU-Zyklus waren einfach zu hoch. (Ob das
betriebswirtscahftlich wirklich richtig war, so lange nach
eigentlich vermeidbaren Fehlern zu suchen, sei mal dahingestellt).
Und was macht man mit dieser immensen Recchenpower?
[...]
Post by Thomas Koenig
Ada existiert, sogar mit einem freien Compiler. Wer will, kann
problemlos eine deutlich sicherere Sprache wählen. Nur macht
es kaum jemand. Wieso eigentlich?
C gilt sogar in relativ 'emporgehobenen' Umgebungen, zB
Linux-Kernelentwicklung, als zu kompliziert, um seine sprachlichen
Moeglichkeiten zu nutzen bzw zu dulden, dass das getan wird. Und
verglichen mit Ada ist C eine sehr simple Sprache.
Rainer Weikusat
2015-06-29 10:20:30 UTC
Permalink
Raw Message
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Peter J. Holzer
Einerseits will man natürlich Optimierungen durchführen können, und
gerade "undefiniertes Verhalten" ist eine der reichsten Quellen für
Optimierungsmöglichkeiten in C. (Oder umgekehrt gesehen: Die Autoren des
C-Standards haben vieles offen gelassen, um entsprechende Optimierungen
zu erlauben).
Meiner Ansicht nach sind die meisten Vagheiten in der C-Norm darauf
zurueckzufuehren, dass das Verhalten existierender Hardware
beruecksichtigt werden musste
Niemand *muss* das Verhalten existierender Hardware beim Festlegen der
Semantik einer Programmiersprache berücksichtigen. Man kann immer ein
bestimmtes Verhalten festlegen. Die Implementierung wird halt dann mehr
oder weniger effizient.
Oder mehr oder weniger nicht-existent, jedenfalls fuer praktische
Zwecke: Niemand 'muss' eine Spezifikation so schreiben, dass sie sowohl
existierende Implementierungen als auch die kommerziellen Interessen
groesserer Hersteller beruecksichtigt aber fuer Industrienormen, die
letzten Endes von Angestellen (im weiteren Sinne) genau dieser
Hersteller geschrieben werden, duerfte ein solcher Fall nicht
tatsaechlich vorkommen.
Post by Peter J. Holzer
Post by Rainer Weikusat
bzw dass man Beschraekungen moeglichen Hardware-Verhaltens vermeiden
wollte. Sign-and-magnitude und Einerkomplement sind beide trotz ihrer
Nachteile verwendet worden deswegen kann die C-Norm nicht verlangen,
dass INT_MAX + n ein bestimmtes Ergebnis hat.
Natürlich könnte sie das. Tut sie aber nicht. Und der Grund hier war
eindeutig, dass man auf jeder Plattform die effizienteste Implementation
zulassen wollte, um den Preis der Inkompatibilität (bzw. musste sich der
Programmierer selbst darum kümmern, seinen Code portabel zu
gestalten).
Das ist keineswegs eindeutig weil nirgends so ausgedrueckt und sogar
wenn dem anders waere, wuerde das immer noch nicht zwangslaeufig
heissen, das das, was oeffentlich gesagt wurde, auch mit dem, was man
vorher hinter verschlossenen Tueren verhandelt hatte, uebereinstimmt.
Post by Peter J. Holzer
Ein weiterer Grund wäre "existing code matters", aber das scheint mir
hier keine Rolle gespielt zu haben - denn da Overflow nun undefined war,
Das tatsaechlich Verhalten war vor der C-Norm unter keinen Umstaenden
in irgendeinem konkreten 'undefiniert' und sogar dass die C-Norm es als
'undefiniert' bezeichnet, ist das Ergebnis einer kreativen
Interpretation des Textes seitens der Leute, die es gerne so haben
wollten. Der Abschnitt ueber 'signed integer conversions' konstatiert

Otherwise, the new type is signed and the value cannot be
represented in it; either the result is implementation-defined
or an implementation-defined signal is raised.

Die 'undefiniert'-Theorie bezieht sich auf eine andere Stelle,

If an exceptional condition occurs during the evaluation of an
expression (that is, if the result is not mathematically defined
or not in the range of representable values for its type), the
behavior is undefined.

Jetzt ist der parenthetische Text aber eine Erlaeuterung dh eigentlich
eine Fussnote, 'exceptional condition' wird im vollstaendigen Rest des
Textes nicht in der Bedeutung von 'Situation die irgendjemand fuer
aussergewoehnlich haelt' benutzt sondern als terminus technicus fuer
'hardware trap', vgl

The conversion of a floating constant shall not raise an
exceptional condition

und 'es ist undefiniert' steht in offensichtlichem Widerspruch zu 'it is
implementation defined (or ....)'.
Post by Peter J. Holzer
Post by Rainer Weikusat
Seien INT_MAX 65535 und n 65000 so ergibt sich
64999 als Result (0xfde7).
Ich glaube, da ist Dir was durcheinandergekommen.
Post by Rainer Weikusat
gibt es auch immer noch Leute, die Entschieden(!) davon ueberzeugt
sind, das modulo-Arithmetik ueberhaupt keine Existenzberechtigung
haben duerfte,
http://blog.regehr.org/archives/1154
denn sie produziert ja "falsche Ergebnisse"
Der schreibt nicht, dass Modulo-Arithmetik überhaupt keine
Existenzberechtigung hat, sondern dass Arithmetik mit Overflow der
default sein sollte.
Woertlich:

Systems programming languages, such as Rust, Go, and D, have
default integer types that wrap. This is bad because unexpected
wrapping causes programs to produce incorrect results

Hier ist allerdings der Code falsch und nicht das Verhalten der
Maschine.

[...]
Post by Peter J. Holzer
Post by Rainer Weikusat
Das sind aber alles Dinge, mit denen ich als Programmierer von
Maschinen, deren anderes Verhalten ich fuer sinnvoll halte, nicht
belaestigt werden will und auch nicht belaestigt werden sollte. Erst
recht nicht von windigen, heuristischen Algorithmen, die zu erraten
versuchen, welcher Code besser nie geschrieben worde waere, um ihn
transparent unter den Tisch fallen zu lassen, ohne das der oder die
Verwantwortlichen zugeben muessen, dass hier Verbesserungsbedarf
bestanden haette.
Wenn Du Code schreibst, der in C undefiniertes Verhalten hat, und gerne
definiertes Verhalten hättest, solltest Du vielleicht eine andere
Programmiersprache wählen. C ist nun mal die "the compiler will get its
revenge"-Sprache.
Was Du ueber micht glauben oder schreiben moechtest ist an dieser Stelle
ohne Belang: Es ist meiner Ansicht nach nicht Aufgabe eines
Compilers, Programmierer daran zu hindern, unportablen Code zu
schreiben, falls sie das aus irgendeinem Grund moechten. Er sollte das
im Gegensatz dazu ermoeglichen denn das erhoeht seinen Nutzwert.
Peter J. Holzer
2015-06-29 13:37:59 UTC
Permalink
Raw Message
Post by Rainer Weikusat
Post by Peter J. Holzer
Ein weiterer Grund wäre "existing code matters", aber das scheint mir
hier keine Rolle gespielt zu haben - denn da Overflow nun undefined war,
Das tatsaechlich Verhalten war vor der C-Norm unter keinen Umstaenden
in irgendeinem konkreten 'undefiniert' und sogar dass die C-Norm es als
'undefiniert' bezeichnet, ist das Ergebnis einer kreativen
Interpretation des Textes seitens der Leute, die es gerne so haben
wollten.
So kreativ muss man da nicht sein. Das wird explizit im Standard als
Beispiel angeführt:

| 3.4.3 undefined behavior
[...]
| EXAMPLE An example of undefined behavior is the behavior on integer
| overflow.

Nun sind Beispiele nicht normativ, und es könnte sein, dass sich
herausstellt, dass der normative Text des Standards das Verhalten bei
Integer-Overflow doch definiert. Das ist aber einigermaßen
unwahrscheinlich. C11 ist mittlerweile die 3. Version des Standards
(Errata nicht mitgerechnet) und das wäre auch schon anderen Leuten als
dem Herrn Weikusat aufgefallen.
Post by Rainer Weikusat
Der Abschnitt ueber 'signed integer conversions' konstatiert
Otherwise, the new type is signed and the value cannot be
represented in it; either the result is implementation-defined
or an implementation-defined signal is raised.
Wir sprechen hier aber nicht über eine "conversion", sondern über
arithmetische Operationen.
Post by Rainer Weikusat
Die 'undefiniert'-Theorie bezieht sich auf eine andere Stelle,
If an exceptional condition occurs during the evaluation of an
expression (that is, if the result is not mathematically defined
or not in the range of representable values for its type), the
behavior is undefined.
Genau.
Post by Rainer Weikusat
Jetzt ist der parenthetische Text aber eine Erlaeuterung dh eigentlich
eine Fussnote,
Das halte ich jetzt für "eine kreative Interpretation des Textes seitens
einer Person, die es so haben will".
Post by Rainer Weikusat
'exceptional condition' wird im vollstaendigen Rest des Textes nicht
in der Bedeutung von 'Situation die irgendjemand fuer
aussergewoehnlich haelt'
Das ist eine typische Weikusat-Unterstellung.
Post by Rainer Weikusat
benutzt sondern als terminus technicus fuer 'hardware trap', vgl
The conversion of a floating constant shall not raise an
exceptional condition
Eher umgekehrt: Der Hardware-Trap ist eine mögliche Folge der
exceptional condition.
Post by Rainer Weikusat
und 'es ist undefiniert' steht in offensichtlichem Widerspruch zu 'it is
implementation defined (or ....)'.
Es wäre ein Widerspruch, wenn es sich auf den gleichen Sachverhalt
beziehen würde, ja.
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
gibt es auch immer noch Leute, die Entschieden(!) davon ueberzeugt
sind, das modulo-Arithmetik ueberhaupt keine Existenzberechtigung
haben duerfte,
http://blog.regehr.org/archives/1154
denn sie produziert ja "falsche Ergebnisse"
Der schreibt nicht, dass Modulo-Arithmetik überhaupt keine
Existenzberechtigung hat, sondern dass Arithmetik mit Overflow der
default sein sollte.
Systems programming languages, such as Rust, Go, and D, have
default integer types that wrap. This is bad because unexpected
wrapping causes programs to produce incorrect results
Hier ist allerdings der Code falsch und nicht das Verhalten der
Maschine.
Das ist eine unsinnige Unterscheidung (gerade von Dir, der Du Dich
gerade darüber aufregst, dass Compiler nicht den Code erzeugen, den Du
von Ihnen erwartest). Das Programm produziert nicht den gewünschten
Output, also ist der Output falsch. Natürlich ist das (abgesehen von
Compiler-Bugs) primär das Problem des Programmierers, der halt die
Semantik seiner Programmiersprache nicht ausreichend verstanden bzw.
berücksichtigt hat. Wenn aber eine Programmiersprache eine Semantik hat,
die nicht zum Problemen passt, dann kann man durchaus sagen, dass diese
Programmiersprache für den gewählten Anwendungszweck "falsch" ist.

Er argumentiert, dass modulo-Arithmetik für int-Typen nur für wenige
Anwendungszwecke passend ist und daher der dafault in einer
Programmiersprache Trap-on-overflow sein sollte, was in weiterer Folge
heißt, dass die Hardware einen Weg zur Verfügung stellen sollte, das
effizient zu implementieren.

Ich bin etwas skeptisch bezüglich seiner Annahme, dass Overflow-Checks
teuer sind *und* durch eine Hardware-Implementation wesentlich billiger
würden. Benchmarks mit -ftrapv würden ersteres beantworten (bitte mit
clang, nicht mit gcc - die gcc-Implementation schaut sehr ineffizient
aus), für letzteres bräuchte man natürlich Silizium. x86 im 386er-Modus
hat noch die INTO-Instruktion. Ist »add; into« signifikant schneller als
»add; jo«? Wenn nein, ist into nur deshalb langsam, weil es keiner
verwendet, oder gibt es technische Gründe warum das langsam sein muss?
Post by Rainer Weikusat
Post by Peter J. Holzer
Wenn Du Code schreibst, der in C undefiniertes Verhalten hat, und gerne
definiertes Verhalten hättest, solltest Du vielleicht eine andere
Programmiersprache wählen. C ist nun mal die "the compiler will get its
revenge"-Sprache.
Was Du ueber micht glauben oder schreiben moechtest ist an dieser Stelle
ohne Belang: Es ist meiner Ansicht nach nicht Aufgabe eines
Compilers, Programmierer daran zu hindern, unportablen Code zu
schreiben, falls sie das aus irgendeinem Grund moechten.
Sie hindern dich eh nicht daran. Du musst halt dann nur damit leben,
dass Dein unportabler Code nicht auf allen Plattformen wie gewünscht
funktioniert - wenn Du Pech hast, auch nicht auf den Plattformen, an
denen Du interessiert bist.

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Rainer Weikusat
2015-06-29 15:01:28 UTC
Permalink
Raw Message
[...]
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Peter J. Holzer
Wenn Du Code schreibst, der in C undefiniertes Verhalten hat, und gerne
definiertes Verhalten hättest, solltest Du vielleicht eine andere
Programmiersprache wählen. C ist nun mal die "the compiler will get its
revenge"-Sprache.
Was Du ueber micht glauben oder schreiben moechtest ist an dieser Stelle
ohne Belang: Es ist meiner Ansicht nach nicht Aufgabe eines
Compilers, Programmierer daran zu hindern, unportablen Code zu
schreiben, falls sie das aus irgendeinem Grund moechten.
Sie hindern dich eh nicht daran. Du musst halt dann nur damit leben,
dass Dein unportabler Code nicht auf allen Plattformen wie gewünscht
funktioniert
Was Du ueber micht glauben oder schreiben moechtest ist an dieser Stelle
ohne Belang: Es ist meiner Ansicht nach nicht Aufgabe eines
Compilers, Programmierer daran zu hindern, unportablen Code zu
schreiben, falls sie das aus irgendeinem Grund moechten. Er sollte es im
Gegenteil ermoeglichen, denn das erhoeht seinen Nutzwert.

Falls Dir dazu eine sinnvollere Entgegnung als von keinerlei Fakten
gestuetzen Spekulation ueber Dinge, die ich tue oder nicht tue,
einfaellt, waere ich sehr an ihr interessiert.
Rainer Weikusat
2015-06-30 12:09:00 UTC
Permalink
Raw Message
Post by Rainer Weikusat
[...]
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Peter J. Holzer
Wenn Du Code schreibst, der in C undefiniertes Verhalten hat, und gerne
definiertes Verhalten hättest, solltest Du vielleicht eine andere
Programmiersprache wählen. C ist nun mal die "the compiler will get its
revenge"-Sprache.
Was Du ueber micht glauben oder schreiben moechtest ist an dieser Stelle
ohne Belang: Es ist meiner Ansicht nach nicht Aufgabe eines
Compilers, Programmierer daran zu hindern, unportablen Code zu
schreiben, falls sie das aus irgendeinem Grund moechten.
Sie hindern dich eh nicht daran. Du musst halt dann nur damit leben,
dass Dein unportabler Code nicht auf allen Plattformen wie gewünscht
funktioniert
Was Du ueber micht glauben oder schreiben moechtest ist an dieser Stelle
ohne Belang: Es ist meiner Ansicht nach nicht Aufgabe eines
Compilers, Programmierer daran zu hindern, unportablen Code zu
schreiben, falls sie das aus irgendeinem Grund moechten. Er sollte es im
Gegenteil ermoeglichen, denn das erhoeht seinen Nutzwert.
JFTR: Waehrend mich 'undefiniertes Verhalten' von manchen Konstruktionen
ueberhaupt nicht stoert, weil man aufgrund von reichlich existierender
Praxis davon ausgehen kann das 'ploetzliche Aenderung des Verhaltens
nach 15 Jahren' nicht stattfinden wird, naemlich fuer C-Code, der seine
eigene Speicherverwaltung unter Annahme eines 'flachen' Speichermodells
macht, wuerde ich jetzt nicht auf den Gedanken kommen, mich auf ein
bestimmtes Verhalten bei 'arithmetischen Bereichsueberschreitungen' zu
verlassen, weil ich weiss, dass das fuer viele Leute ein Reizthema ist.

Um hier mal etwas Kontext zu haben, erlaube ich mir mal, eine
"Beispieloptimierung" aus der gcc 4.4 Dokumentation anzufuehren:

"Vereinfache x + 1 > x zu 1", dh generell x + n > x zu 1. Das ist aber
nun bedauerlicherweise genau die Methode, wie jemand Naives (der einen
Compiler nicht fuer ein Rachewerkzeug von mit der technischen
Entwicklung von Hardware Unzufriedenen haelt) eine "Ueberlaufpruefung"
implementieren wuerde. Hierbei handelt es sich auch nicht um
'Compiler-Optimierung' im klassischen Sinne, dh darum, einen in einer
Hochsprache ausgedrueckten Algorithmus moeglichst effizient in eine
bestimmte Maschinensprache zu uebersetzen, sondern um Versuche, den
Quellcode so umzuschreiben, wie ihn der Programmierer eigentlich haette
schreiben sollen, basierend auf der Annahme, das dieser entweder zu
dumm oder zu denkfaul war, um sich konkreter auszudruecken. Der
moegliche Gewinn besteht darin, zwei oder drei Instruktionen
einzusparen, der moegliche Schaden ist ziemlich unbegrenzt, so ungefaehr
alles, was angesichts des Y2K-Problems an Befuerchtungen durchgekaut
wurde. Angesichts dessen halte ich das fuer eine unverwantwortlich
waghalsige Annahme.

Und dann gibt es natuerlich auch noch Schmankerln wie

`-fwrapv'
This option instructs the compiler to assume that signed
arithmetic overflow of addition, subtraction and
multiplication wraps around using twos-complement
representation. This flag enables some optimizations and
disables others. This option is enabled by default for the
Java front-end, as required by the Java language
specification.

Leider haben sich die Leute, die Java definiert haben, ueberhaupt nicht
ueber 'hitzige Meinungsverschiedenheiten aus der Mitte des letzten
Jahrhunderts" und Kompatibilitaet mit diversen Mainframe-Computern, die
man heutzutage mit Glueck noch in einem Museum findet, gekuemmert,
sondern einfach das faktische Resultat dieser historischen Diskussion in
ihre Spezifikation geschrieben und 'herumoptimieren' kann man daren nicht
mehr ...
Rainer Weikusat
2015-06-30 16:06:11 UTC
Permalink
Raw Message
[...]
Post by Rainer Weikusat
"Vereinfache x + 1 > x zu 1", dh generell x + n > x zu 1. Das ist aber
nun bedauerlicherweise genau die Methode, wie jemand Naives
[...]
Post by Rainer Weikusat
eine "Ueberlaufpruefung" implementieren wuerde.
Heute aus anderem Grund in Perl 5.14.2 gefunden (dieser Code hat noch
eine Menge anderer Fehler):

if (GIMME == G_ARRAY && PL_op->op_private & OPpREPEAT_DOLIST) {
dMARK;
static const char oom_list_extend[] = "Out of memory during list extend";
const I32 items = SP - MARK;
const I32 max = items * count;

MEM_WRAP_CHECK_1(max, SV*, oom_list_extend);
/* Did the max computation overflow? */
if (items > 0 && max > 0 && (max < items || max < count))
Perl_croak(aTHX_ oom_list_extend);

[pp.c/ pp_repeat]

Das ist natuerlich genau so einen Situation, in der man

max < items || max < count

zu

0

'vereinfachen' koennte, weil aus

items > 0
max = items * count
max > 0

count > 0 folgt und max folglicherweise nach der Berechnung weder
kleiner items noch kleiner count sein kann wenn man annimmt, dass
32-Bit-Ganzahlen "in Wirklichkeit" einen unbeschraenkten Wertebereich
haben. Gefixt wurde das am 19.03.2015,

http://perl5.git.perl.org/perl.git/commitdiff/b3b27d017bd3d61f6cbc3eaf8465b8d98d86a024?hp=b9a07097bdb32335cd4963591c6f2d29bc6302a8
Thomas Koenig
2015-06-30 22:16:05 UTC
Permalink
Raw Message
Post by Rainer Weikusat
"Vereinfache x + 1 > x zu 1", dh generell x + n > x zu 1.
Nach der C-Norm erlaubt.
Post by Rainer Weikusat
Das ist aber
nun bedauerlicherweise genau die Methode, wie jemand Naives (der einen
Compiler nicht fuer ein Rachewerkzeug von mit der technischen
Entwicklung von Hardware Unzufriedenen haelt) eine "Ueberlaufpruefung"
implementieren wuerde.
Das ist einer der Nachteile von C, dass die Sprachdefinition diese Art
von Transformation erlaubt.

Nun ist es schwierig, neben einer offiziellen Norm auch noch
inoffizielle Vereinbarungen zu treffen, was denn jetzt an
Verbesserungen legal sein soll und was nicht. Ein Problem
damit ist, dass jeder seine eigene Vorstellung hat, was denn
jetzt vernünftig ist und was nicht. Auch das müsste man
daher in irgendeiner Form normieren, und das kann man praktisch
nur in der eigentlichen Norm

Die Adressaten deiner (durchaus berechtigten) Kritik sollten
daher primär nicht die die Compilerautoren sein, sondern
JTC1/SC22/WG14. Schreibe die Leute an und schlage eine entsprechende
Änderung der Norm vor. Eine Optimierung zu verbieten, sollte
verhältnismäßig einfach sein, weil kein existierender Code
dadurch ungültig wird.

Soweit ich weiss, sitzt in der WG14 auch jemand von CERT, der sollte
ein offenes Ohr für das Stopfen von Sicherheitslücken haben...

Es kann natürlich sein, dass im Ergebnis die Mitglieder von WG14
deine Einschätzung
Post by Rainer Weikusat
Der
moegliche Gewinn besteht darin, zwei oder drei Instruktionen
einzusparen, der moegliche Schaden ist ziemlich unbegrenzt,
nicht teilen und du damit nicht durchkommst. Ein Versuch wäre es auf
jeden Fall mal wert.
Rainer Weikusat
2015-07-01 11:45:32 UTC
Permalink
Raw Message
Post by Thomas Koenig
Post by Rainer Weikusat
"Vereinfache x + 1 > x zu 1", dh generell x + n > x zu 1.
Nach der C-Norm erlaubt.
Sogar wenn man von 'undefiniertem Verhalten' in diesem Fall ausgeht, und
ich halte den Text weitherhin fuer wenigstens widerspruechlich, ist das
nicht ganz korrekt: Dann sagt die C-Norm nichts ueber diesen Fall
('behaviour upon which this international standard imposes no
requirements'), es ist also jedem, der eine entsprechende
Implementierung schreibt, freigestellt, ihn so zu handhaben oder zu
ignorieren wie es ihm passt, ohne deswegen den Bereich der
Standard-Konformitaet zu verlassen.

ZB koennte, insofern es die C-Norm angeht, auch ein mit einer Axt
bewaffneter Roboter aktiviert werden, der die Wohnung des
verantwortlichen Code-Schreibers aufsucht, jedem, den er dort antrifft,
die Fuesse abhackt, und danach sicherheitshalber noch den ganzen Block
niederbrennt. Das 'erlaubt' die C-Norm zwar genausowenig ausdruecklich,
aber verbieten tut sie es ganz sicher auch nicht.
Post by Thomas Koenig
Post by Rainer Weikusat
Das ist aber nun bedauerlicherweise genau die Methode, wie jemand
Naives (der einen Compiler nicht fuer ein Rachewerkzeug von mit der
technischen Entwicklung von Hardware Unzufriedenen haelt) eine
"Ueberlaufpruefung" implementieren wuerde.
Das ist einer der Nachteile von C, dass die Sprachdefinition diese Art
von Transformation erlaubt.
Falls man es mit jemandem zu tun hat, der Aenderungen an einer
Implementierung, die bekanntermassen ausgiebig genutzte Konstruktionen
wie die obige, ploetzlich kaputt uebersetzt, weil er absolut ueberzeugt
ist, das ihm etwas viel besseres eingefallen ist, kann man es einen der
Nachteile der C Norm nennen, dass dieses aufgrund von ganz anderen
Ruecksichten nicht ausdruecklich untersagt wurde ...
Post by Thomas Koenig
Nun ist es schwierig, neben einer offiziellen Norm auch noch
inoffizielle Vereinbarungen zu treffen, was denn jetzt an
Verbesserungen legal sein soll und was nicht. Ein Problem
damit ist, dass jeder seine eigene Vorstellung hat, was denn jetzt
vernünftig ist und was nicht. Auch das müsste man daher in irgendeiner
Form normieren, und das kann man praktisch nur in der eigentlichen
Norm
... allerdings stimme ich dem nur begrenzt zu: Jeder ausreichend
komplexe Text kann in vielfaeltiger Weise interpretiert werden, dh
Interpretationen, die manche Leute fuer sinnvoll und andere fuer
schaedlich halten, wird man so nicht verhindern koennen.
Rainer Weikusat
2015-07-01 11:47:20 UTC
Permalink
Raw Message
Post by Thomas Koenig
Post by Rainer Weikusat
"Vereinfache x + 1 > x zu 1", dh generell x + n > x zu 1.
Nach der C-Norm erlaubt.
Sogar wenn man von 'undefiniertem Verhalten' in diesem Fall ausgeht, und
ich halte den Text weitherhin fuer wenigstens widerspruechlich, ist das
nicht ganz korrekt: Dann sagt die C-Norm nichts ueber diesen Fall
('behaviour upon which this international standard imposes no
requirements'), es ist also jedem, der eine entsprechende
Implementierung schreibt, freigestellt, ihn so zu handhaben oder zu
ignorieren wie es ihm passt, ohne deswegen den Bereich der
Standard-Konformitaet zu verlassen.

ZB koennte, insofern es die C-Norm angeht, auch ein mit einer Axt
bewaffneter Roboter aktiviert werden, der die Wohnung des
verantwortlichen Code-Schreibers aufsucht, jedem, den er dort antrifft,
die Fuesse abhackt, und danach sicherheitshalber noch den ganzen Block
niederbrennt. Das 'erlaubt' die C-Norm zwar genausowenig ausdruecklich,
aber verbieten tut sie es ganz sicher auch nicht.
Post by Thomas Koenig
Post by Rainer Weikusat
Das ist aber nun bedauerlicherweise genau die Methode, wie jemand
Naives (der einen Compiler nicht fuer ein Rachewerkzeug von mit der
technischen Entwicklung von Hardware Unzufriedenen haelt) eine
"Ueberlaufpruefung" implementieren wuerde.
Das ist einer der Nachteile von C, dass die Sprachdefinition diese Art
von Transformation erlaubt.
Falls man es mit jemandem zu tun hat, der Aenderungen an einer
Implementierung, die bekanntermassen ausgiebig genutzte Konstruktionen
wie die obige, dannd ploetzlich kaputt uebersetzt, weil er absolut
ueberzeugt ist, das ihm etwas viel besseres eingefallen ist, fuer
sinnvoll haelt, kann man es einen der Nachteile der C Norm nennen, dass
dieses aufgrund von ganz anderen Ruecksichten nicht ausdruecklich
untersagt wurde ...
Post by Thomas Koenig
Nun ist es schwierig, neben einer offiziellen Norm auch noch
inoffizielle Vereinbarungen zu treffen, was denn jetzt an
Verbesserungen legal sein soll und was nicht. Ein Problem
damit ist, dass jeder seine eigene Vorstellung hat, was denn jetzt
vernünftig ist und was nicht. Auch das müsste man daher in irgendeiner
Form normieren, und das kann man praktisch nur in der eigentlichen
Norm
... allerdings stimme ich dem nur begrenzt zu: Jeder ausreichend
komplexe Text kann in vielfaeltiger Weise interpretiert werden, dh
Interpretationen, die manche Leute fuer sinnvoll und andere fuer
schaedlich halten, wird man so nicht verhindern koennen.
Thomas Koenig
2015-07-01 19:25:14 UTC
Permalink
Raw Message
Post by Rainer Weikusat
Post by Thomas Koenig
Post by Rainer Weikusat
"Vereinfache x + 1 > x zu 1", dh generell x + n > x zu 1.
Nach der C-Norm erlaubt.
Sogar wenn man von 'undefiniertem Verhalten' in diesem Fall ausgeht, und
ich halte den Text weitherhin fuer wenigstens widerspruechlich,
Wenn das deine ernsthafte Meinung ist und du auch bereit bist, etwas
Arbeit zu investieren, um daran etwas zu ändern, dann sorge dafür,
dass ein entsprechender "Defect Report" geschrieben wird.

In Deutschland kontaktiert man dafür am besten das DIN-Gremium
mit der etwas sperrigen Bezeichnung NA 043-01-22 AA,
"Programmiersprachen".

Einen Link findest du unter

http://www.nia.din.de/gremien/NA+043-01-22+AA/de/54770215.html

wo du auch einen Ansprechpartner und ein Kontaktformular findest.

Das Usenet ist für solche Bemerkungen leider ein ziemlich
wirkungsfreier Raum (mit der möglichen Ausnahme von comp.lang.fortran,
wo ein signifikanter Teil des Normungsgremiums sich aktiv beteiligt,
aber Fortran ist ja hier nicht das Thema).
Post by Rainer Weikusat
ist das
nicht ganz korrekt: Dann sagt die C-Norm nichts ueber diesen Fall
('behaviour upon which this international standard imposes no
requirements'),
Damit wäre die Vereinfachung, die gcc macht, auf jeden Fall OK, weil
undefiniertes Verhalten halt auch Auswertung einer Konstante sein kann.
Post by Rainer Weikusat
Post by Thomas Koenig
Das ist einer der Nachteile von C, dass die Sprachdefinition diese Art
von Transformation erlaubt.
Falls man es mit jemandem zu tun hat, der Aenderungen an einer
Implementierung, die bekanntermassen ausgiebig genutzte Konstruktionen
wie die obige, dannd ploetzlich kaputt uebersetzt, weil er absolut
ueberzeugt ist, das ihm etwas viel besseres eingefallen ist, fuer
sinnvoll haelt, kann man es einen der Nachteile der C Norm nennen, dass
dieses aufgrund von ganz anderen Ruecksichten nicht ausdruecklich
untersagt wurde ...
Ja, das ist ein Nachteil der Sprachnorm. Es ist nie eine gute Idee,
sich auf Dinge zu verlassen, die in der Sprachnorm nicht explizit
geregelt sind, aus genau diesem Grund. "All the world's a VAX"
und so...
Post by Rainer Weikusat
... allerdings stimme ich dem nur begrenzt zu: Jeder ausreichend
komplexe Text kann in vielfaeltiger Weise interpretiert werden, dh
Interpretationen, die manche Leute fuer sinnvoll und andere fuer
schaedlich halten, wird man so nicht verhindern koennen.
Genau dann wid es Zeit für einen Defect Report.
Rainer Weikusat
2015-07-02 17:40:14 UTC
Permalink
Raw Message
Post by Thomas Koenig
Post by Rainer Weikusat
Post by Thomas Koenig
Post by Rainer Weikusat
"Vereinfache x + 1 > x zu 1", dh generell x + n > x zu 1.
Nach der C-Norm erlaubt.
Sogar wenn man von 'undefiniertem Verhalten' in diesem Fall ausgeht, und
ich halte den Text weitherhin fuer wenigstens widerspruechlich,
Wenn das deine ernsthafte Meinung ist und du auch bereit bist, etwas
Arbeit zu investieren, um daran etwas zu ändern, dann sorge dafür,
dass ein entsprechender "Defect Report" geschrieben wird.
Das ist meine momentane Ansicht ueber die gesammelten Aussagen zum Thema
'Bereichsueberschreitungen bei Ganzahlberechnungen', die die C-Norm
macht. Allerdings ist mein Wissen um solche Aussagen moeglicherweise/
wahrscheinlich lueckenhaft und/oder es koennte bessere Argument fuer
'undefiniertes Verhalten' geben als die mir bislang bekannten. Weil ich
C beruflich benutze, bin ich daran interessiert, das Thema zu
diskutieren, um mein Verstaendnis zu vertiefen und weil ich
grundsaetzlich immer auch an anderen Ansichten interessiert bin
(insofern das ohne Angriffe gegen direkt an einer Diskussion beteiligte
Personen vor sich geht).

Irgendwelche Normungsgremien von der Richtigkeit meiner Ansicht
zu ueberzeugen interessiert mich hingegen ueberhaupt nicht.

[...]
Post by Thomas Koenig
Post by Rainer Weikusat
ist das nicht ganz korrekt: Dann sagt die C-Norm nichts ueber diesen
Fall ('behaviour upon which this international standard imposes no
requirements'),
Damit wäre die Vereinfachung, die gcc macht, auf jeden Fall OK, weil
undefiniertes Verhalten halt auch Auswertung einer Konstante sein kann.
Damit waere das konkrete Verhalten von gcc in diesen Fall irrelevant fuer
seine Norm-Konformitaet. Sogar dann wenn es, wie hier, offensichtlich
nicht mit der C-Norm in Einklang zu bringen ist: Ein int
(stellvertretend fuer alle anderen vorzeichenbehafteten Ganzzahltypen
gebraucht) hat in jedem Fall eine endliche Menge an Wertbits also ist
sein Wertebereich auch endlich. Die Ungleichung

x + 1 > x

ist nur dann wahr, falls x < INT_MAX oder, anders ausgedrueckt, sei

f(x) = x + 1

so ist der Definitionsbereich dieser Funktion in diesem Fall INT_MIN -
INT_MAX - 1. Insofern der Compiler nicht beweisen kann, dass x < INT_MAX
gilt, kann er auch keine Aussagen ueber das Resultat machen, schon gar
nicht es sei INT_MAX + 1.
Peter J. Holzer
2015-07-02 21:23:02 UTC
Permalink
Raw Message
Post by Rainer Weikusat
Post by Thomas Koenig
Post by Rainer Weikusat
ist das nicht ganz korrekt: Dann sagt die C-Norm nichts ueber diesen
Fall ('behaviour upon which this international standard imposes no
requirements'),
Damit wäre die Vereinfachung, die gcc macht, auf jeden Fall OK, weil
undefiniertes Verhalten halt auch Auswertung einer Konstante sein kann.
Damit waere das konkrete Verhalten von gcc in diesen Fall irrelevant fuer
seine Norm-Konformitaet.
So ist es. Genau das ist mit "behavior ... for which this International
Standard imposes no requirements" gemeint. Der Compiler kann machen was
er will, jedes Verhalten ist norm-konform.
Post by Rainer Weikusat
Sogar dann wenn es, wie hier, offensichtlich nicht mit der C-Norm in
Einklang zu bringen ist: Ein int (stellvertretend fuer alle anderen
vorzeichenbehafteten Ganzzahltypen gebraucht) hat in jedem Fall eine
endliche Menge an Wertbits also ist sein Wertebereich auch endlich.
Die Ungleichung
x + 1 > x
ist nur dann wahr, falls x < INT_MAX
Nein. Du argumentierst hier auf Basis einer bestimmten Arithmetik, aber
der Standard schreibt diese Arithmetik nicht vor. Da die Voraussetzung
falsch ist, ist auch die Schlussfolgerung falsch.
Post by Rainer Weikusat
oder, anders ausgedrueckt, sei
f(x) = x + 1
so ist der Definitionsbereich dieser Funktion in diesem Fall INT_MIN -
INT_MAX - 1.
Das ist hingegen richtig. Für x = INT_MAX ist das einfach nicht
definiert.
Post by Rainer Weikusat
Insofern der Compiler nicht beweisen kann, dass x < INT_MAX gilt, kann
er auch keine Aussagen ueber das Resultat machen, schon gar nicht es
sei INT_MAX + 1.
Es muss es nicht beweisen, das ist genau der Punkt. Im Fall von
undefiniertem Verhalten kann er machen, was er will. Die Vorschrift
"wenn x <= INT_MAX ist, ist das Ergebnis 1, wenn x == INT_MAX ist, kann
das Ergebnis beliebig sein (als auch 1)" kann man zu "das Ergebis ist 1"
vereinfachen.

(oder anders ausgedrückt: Der Compiler kann annehmen, dass x in diesem
Ausdruck nie den Wert INT_MAX hat, weil das zu undefiniertem Verhalten
führen würde und das nicht sein darf).

Ein anderes Beispiel (das insbesondere deshalb interessant ist, weil es
bis inklusive C99 definiert war, aber vermutlich kein Compiler für
Intel-Prozessoren (und etliche andere Architekturen) je standard-konform
war, weshalb es in C11 nun undefined ist):

int m(int a, int b) { return a % b }

int c = m(INT_MIN, -1);

INT_MIN % -1 ist arithmetisch eindeutig 0, und bis C99 war das das
vorgeschriebene Ergebnis.

Aber

movl INT_MIN, %eax
cltd
idivl -1, %eax

liefert eine Exception, weil das Ergebnis der Division INT_MIN/-1 nicht
in %eax Platz hat (dass das ohnehin nicht verwendet wird sondern nur das
parallel berechnete INT_MIN%-1 in %edx weiß der Prozessor ja nicht).

Ein standardkonformer Compiler hätte also m ungefähr so kompilieren
müssen:

movl 4(%esp), %eax
movl 8(%esp), %edx
cmpl %eax, INT_MIN
jne .1
cmpl %edx, -1
jne .1
movl 0, %eax
j .2
.1:
idivl %edx
movl %edx %eax
.2:
ret

um den Fall (INT_MIN, -1) korrekt zu behandeln (es gibt sicher auch
andere Lösungen, aber die sind auch nicht einfacher).

Dass dieser (in der Praxis wohl reichlich uninteressante) Spezialfall
jetzt undefiniert ist, gibt dem Compiler die Freiheit, deutlich
einfacheren Code zu erzeugen, der in allen anderen Fällen korrekt ist.

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Helmut Schellong
2015-07-03 08:21:57 UTC
Permalink
Raw Message
[...]
Post by Peter J. Holzer
Post by Rainer Weikusat
Die Ungleichung
x + 1 > x
ist nur dann wahr, falls x < INT_MAX
Nein. Du argumentierst hier auf Basis einer bestimmten Arithmetik, aber
der Standard schreibt diese Arithmetik nicht vor. Da die Voraussetzung
falsch ist, ist auch die Schlussfolgerung falsch.
Ich bin ein überzeugter Praktiker und prüfe vor einer Aktion, die
potentiell UB bewirken könnte:

if (a>=0 && INT_MAX-x >= a) x += a;

Ebenso:
if (b > pend-p) me= pend;
else me= p+b;

Statt:
if (p+b > pend) me= pend;
else me= p+b;

b= ~0u; kann nämlich vorliegen.
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Rainer Weikusat
2015-07-04 14:35:34 UTC
Permalink
Raw Message
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Thomas Koenig
Post by Rainer Weikusat
ist das nicht ganz korrekt: Dann sagt die C-Norm nichts ueber diesen
Fall ('behaviour upon which this international standard imposes no
requirements'),
Damit wäre die Vereinfachung, die gcc macht, auf jeden Fall OK, weil
undefiniertes Verhalten halt auch Auswertung einer Konstante sein kann.
Damit waere das konkrete Verhalten von gcc in diesen Fall irrelevant fuer
seine Norm-Konformitaet.
[...]
Post by Peter J. Holzer
Post by Rainer Weikusat
Einklang zu bringen ist: Ein int (stellvertretend fuer alle anderen
vorzeichenbehafteten Ganzzahltypen gebraucht) hat in jedem Fall eine
endliche Menge an Wertbits also ist sein Wertebereich auch endlich.
Die Ungleichung
x + 1 > x
ist nur dann wahr, falls x < INT_MAX
Nein. Du argumentierst hier auf Basis einer bestimmten Arithmetik, aber
der Standard schreibt diese Arithmetik nicht vor. Da die Voraussetzung
falsch ist, ist auch die Schlussfolgerung falsch.
Der C-Ausdruck x + 1 > 1 hat nur dann den Wert 1 falls x < INT_MAX
gilt. Andernfalls kann man wohl behaupten, dass der Wert auch dann 1
sei, aber dazu muss man sich auf etwas anderes stuetzen, als die
C-Norm.

[...]
Post by Peter J. Holzer
Post by Rainer Weikusat
Insofern der Compiler nicht beweisen kann, dass x < INT_MAX gilt, kann
er auch keine Aussagen ueber das Resultat machen, schon gar nicht es
sei INT_MAX + 1.
Es muss es nicht beweisen, das ist genau der Punkt. Im Fall von
undefiniertem Verhalten kann er machen, was er will. Die Vorschrift
"wenn x <= INT_MAX ist, ist das Ergebnis 1, wenn x == INT_MAX ist, kann
das Ergebnis beliebig sein (als auch 1)" kann man zu "das Ergebis ist 1"
vereinfachen.
Die Aussage ist aber nicht, dass das Ergebnis beliebig sein kann,
sondern "hierueber macht die C-Norm keine Aussage". Insbesondere macht
sie auch keine Aussage darueber, welche Bedeutung x, +, < und 1 in
diesem Fall haben.
Post by Peter J. Holzer
(oder anders ausgedrückt: Der Compiler kann annehmen, dass x in diesem
Ausdruck nie den Wert INT_MAX hat, weil das zu undefiniertem Verhalten
führen würde und das nicht sein darf).
Das kann der Compiler nicht annehmen, weil INT_MAX jedenfalls ein
gueltiger Wert fuer int ist. Jetzt stellt sich allerdings die Frage, wie
dieser Fall zu handhaben ist, weil die C-Norm darueber nichts
aussagt. Da gibt es eine traditionelle Methode, die noch auch B
zurueckgeht, und die Dennis Ritchie in 'The Development of the C
Language' (mit etwas Muehe immer noch zu finden) wie folgt beschrieben
hat:

The + operator, for example, simply adds its operands using the
machine's integer add instruction

In diesem Fall bleibt es der Hardware ueberlassen, die
Bereichsueberschreitung so zu handhaben oder zu ignorieren, wie es
denjenigen, die sie entworfen haben, sinnvoll erschien. Das
bedeutet fuer aller praktischen Zwecke dasselbe, was man auch in der
Java-Spezifikation findet, naemlich 'Zweierkomplementarithmetik modulo
2 hoch Typgroesse'.

Alternativ kann ein Compilerschreiber, der sich an dem Wortlaut der
C-Norm aber nicht unbedingt an 'real existierendes C' gebunden fuehlt,
sich auch seine eigene Semantik ausdenken. Also zum Beispiel die
Operatoren + und > so zu interpretieren, als ob sie auf der
mathematischen, unendlichen, abzaehlbaren Menge der ganzen Zahlen
definiert waeren, obwohl die Maschine das nicht unterstuetzt und auch
gar nicht unterstuetzen koennte und obwohl die C-Norm dem ausdruecklich
einen Riegel vorschiebt: Jeder C Integertyp hat einen endlichen
Wertebereich.

Im konkreten Fall ist natuerlich derjenige/ sind diejenigen, die das
fuer gcc implementiert haben, der Ansicht, dass ihre 'Arithmetik mit
hypothetischen Zahlen' sinnvoller ist, als die konkrete Arithmetik der
Maschine und das es doch "ganz klar" ist, das

x + 1 > x

nicht Operatoren der Sprache C benutzt, sondern diejenigen, die sie zu
benutzen vorziehen.

Aus meiner Sicht ist das aber bloss eine vollkommen willkuerliche
"Geschmacksentscheidung" und ob man hier Siebtklaesslermathematik oder
das Rezitiern von 'One Direction'-Text bevorzugt, vollkommen gleich:
Beides gefaellt irgendjemandem und verbaut die Moeglichkeit, vorhandene
Hardwarefeatures zu benutzen.
Rainer Weikusat
2015-07-04 14:41:09 UTC
Permalink
Raw Message
Rainer Weikusat <***@mobileactivedefense.com> writes:

[Text ueber Ganzahlbereichueberschreitungen]

Hier sei noch ausdruecklich angefuegt, dass ich beleibe nicht behaupten
moechte, das "ich" hier "Recht haette" und "die" "im Unrecht
seien". 'Wir' (wenn ich das mal so benutzen darf) haben einfach zwei
inkompatible Ansichten ueber denselben Sachverhalt.
Thomas Koenig
2015-07-05 14:53:46 UTC
Permalink
Raw Message
Post by Rainer Weikusat
Post by Peter J. Holzer
Es muss es nicht beweisen, das ist genau der Punkt. Im Fall von
undefiniertem Verhalten kann er machen, was er will. Die Vorschrift
"wenn x <= INT_MAX ist, ist das Ergebnis 1, wenn x == INT_MAX ist, kann
das Ergebnis beliebig sein (als auch 1)" kann man zu "das Ergebis ist 1"
vereinfachen.
Die Aussage ist aber nicht, dass das Ergebnis beliebig sein kann,
sondern "hierueber macht die C-Norm keine Aussage". Insbesondere macht
sie auch keine Aussage darueber, welche Bedeutung x, +, < und 1 in
diesem Fall haben.
Die feine Unterscheidung ist in diesem Fall, wer was garantiert.

Was Peter geschrieben hat, bezieht sich auf den Compiler - der darf
das Ergebnis vereinfachen. Er dürfte auch was anderes machen, z.B.
einen "Trap on Overflow".

Was Rainer geschrieben hat, ist das, was der Programmierer annehmen
darf, nämlich gar nichts (außer das sein Programm falsch ist).
Sowohl Vereinfachung als auch "Trap on Overflow" als auch weitere
Dinge liegen im Bereich des Möglichen (das Auslösen des dritten
Weltkrieges wäre sicher nach der C-Norm erlaubt, würde ich aber
als schweren Qualitätsfehler des Compilers sehen).
Juergen Ilse
2015-07-05 18:59:32 UTC
Permalink
Raw Message
Hallo,
Post by Thomas Koenig
Post by Rainer Weikusat
Post by Peter J. Holzer
Es muss es nicht beweisen, das ist genau der Punkt. Im Fall von
undefiniertem Verhalten kann er machen, was er will. Die Vorschrift
"wenn x <= INT_MAX ist, ist das Ergebnis 1, wenn x == INT_MAX ist, kann
das Ergebnis beliebig sein (als auch 1)" kann man zu "das Ergebis ist 1"
vereinfachen.
Die Aussage ist aber nicht, dass das Ergebnis beliebig sein kann,
sondern "hierueber macht die C-Norm keine Aussage".
... was gleichbedeutend ist mit *JEDES* *BELIEBIGE* Ergebnis waere in
dem Fall, fuer den der Standard keine Aussage macht, standardkonform,
denn alle Vorgaben des Standards waeren ja erfuellt. Genaugenommen
heisst es also doch, dass das Ergebnis beliebig sein kann (immer INT_MAX
oder Zufallszahlen oder ...). Da der Standard in dem Fall keine Vorgaben
macht, ist die Optimierung voellig korrekt, solange der Compilerhersteller
fuer seinen Compiler nicht ein Verhalten dokumentiert, dass die Optimie-
rung ausschliessen wuerde. AFAIK haben weder die Autoren von gcc noch die
Autoren von clang diesen undokumentierten Fall fuer ihren Compiler
dokumentiert.
Post by Thomas Koenig
Post by Rainer Weikusat
Insbesondere macht sie auch keine Aussage darueber, welche Bedeutung
x, +, < und 1 in diesem Fall haben.
Jede Implementierung der Operatorfunktionen, die sich in den im
Standard dokumentierten Faellen genauso verhaelt, wie vom Standard
gefordert, waere standardkonform, voellig unabhaengig davon, wie
das Verhalten in allen anderen Faellen waere (auch egal, wie un-
sinnig dir die Implementierung auch erscheinen mag).
Post by Thomas Koenig
Die feine Unterscheidung ist in diesem Fall, wer was garantiert.
Was Peter geschrieben hat, bezieht sich auf den Compiler - der darf
das Ergebnis vereinfachen. Er dürfte auch was anderes machen, z.B.
einen "Trap on Overflow".
Was Rainer geschrieben hat, ist das, was der Programmierer annehmen
darf, nämlich gar nichts (außer das sein Programm falsch ist).
Sowohl Vereinfachung als auch "Trap on Overflow" als auch weitere
Dinge liegen im Bereich des Möglichen (das Auslösen des dritten
Weltkrieges wäre sicher nach der C-Norm erlaubt, würde ich aber
als schweren Qualitätsfehler des Compilers sehen).
Letzterer Aussage wuerde ich zustimmen, auch wenn ich nisher glueck-
licherweise nie mit einem Compiler zu tun hatte, der bei Programm-
fehlern den dritten Weltkrieg ausloesen koennte ...
;-)

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
--
Ein Domainname ist nur ein Name, nicht mehr und nicht weniger.
Wer mehr hineininterpretiert, hat das Domain-Name-System nicht
verstanden.
Rainer Weikusat
2015-07-05 21:19:52 UTC
Permalink
Raw Message
[...]
Post by Juergen Ilse
Post by Rainer Weikusat
Insbesondere macht sie auch keine Aussage darueber, welche Bedeutung
x, +, < und 1 in diesem Fall haben.
Jede Implementierung der Operatorfunktionen, die sich in den im
Standard dokumentierten Faellen genauso verhaelt, wie vom Standard
gefordert, waere standardkonform,
Hier ging es um die Begruendung warum diese Aenderung sinnvoll und
'logisch' sein soll: Diese fliesst aus einer bestimmten Vorstellung von
"richtigem Rechnen" die die Verwantwortlichen mehr oder minder zufaellig
haben und die ausdruecklich nichts mit der Sprache C zu tun hat (ausser
einer gewissen, optischen Aehnlichkeit).
Rainer Weikusat
2015-07-05 21:16:41 UTC
Permalink
Raw Message
Post by Thomas Koenig
Post by Rainer Weikusat
Post by Peter J. Holzer
Es muss es nicht beweisen, das ist genau der Punkt. Im Fall von
undefiniertem Verhalten kann er machen, was er will. Die Vorschrift
"wenn x <= INT_MAX ist, ist das Ergebnis 1, wenn x == INT_MAX ist, kann
das Ergebnis beliebig sein (als auch 1)" kann man zu "das Ergebis ist 1"
vereinfachen.
Die Aussage ist aber nicht, dass das Ergebnis beliebig sein kann,
sondern "hierueber macht die C-Norm keine Aussage". Insbesondere macht
sie auch keine Aussage darueber, welche Bedeutung x, +, < und 1 in
diesem Fall haben.
Die feine Unterscheidung ist in diesem Fall, wer was garantiert.
Niemand 'garantiert' hier irgendetwas: Die C-Norm ist eine
Anforderungsdefinition fuer C-Implmentierungen, an die man sich
freiwillig halten kann oder auch nicht.
Post by Thomas Koenig
Was Peter geschrieben hat, bezieht sich auf den Compiler - der darf
das Ergebnis vereinfachen. Er dürfte auch was anderes machen, z.B.
einen "Trap on Overflow".
Wenn man 'Bereichsueberschreitungen bei vorzeichenbehafteter
Ganzzahl-Arithmetik' fuer undefiniert haelt, so folgt daraus, dass
eine C-Implementierung vollkommen unabhaengig von ihrem Verhalten in
diesem Fall als standardkonform angesehen werden kann. Weiter nichts.

Was jemand, der eine C-Implementierung schreibt (oder aendert) "tun
darf" haengt, haengt dagegen von allgemeinen Gesetze des Landes, dessen
Gesetzen er unterliegt, ab. Das ist fuer den gegebenen Fall nicht nur
vollkommen belanglos, sondern geradezu ein absurdes Kriterium.
Rainer Weikusat
2015-07-05 21:20:29 UTC
Permalink
Raw Message
Post by Thomas Koenig
Post by Rainer Weikusat
Post by Peter J. Holzer
Es muss es nicht beweisen, das ist genau der Punkt. Im Fall von
undefiniertem Verhalten kann er machen, was er will. Die Vorschrift
"wenn x <= INT_MAX ist, ist das Ergebnis 1, wenn x == INT_MAX ist, kann
das Ergebnis beliebig sein (als auch 1)" kann man zu "das Ergebis ist 1"
vereinfachen.
Die Aussage ist aber nicht, dass das Ergebnis beliebig sein kann,
sondern "hierueber macht die C-Norm keine Aussage". Insbesondere macht
sie auch keine Aussage darueber, welche Bedeutung x, +, < und 1 in
diesem Fall haben.
Die feine Unterscheidung ist in diesem Fall, wer was garantiert.
Niemand 'garantiert' hier irgendetwas: Die C-Norm ist eine
Anforderungsdefinition fuer C-Implmentierungen, an die man sich
freiwillig halten kann oder auch nicht.
Post by Thomas Koenig
Was Peter geschrieben hat, bezieht sich auf den Compiler - der darf
das Ergebnis vereinfachen. Er dürfte auch was anderes machen, z.B.
einen "Trap on Overflow".
Wenn man 'Bereichsueberschreitungen bei vorzeichenbehafteter
Ganzzahl-Arithmetik' fuer undefiniert haelt, so folgt daraus, dass
eine C-Implementierung vollkommen unabhaengig von ihrem Verhalten in
diesem Fall als standardkonform angesehen werden kann. Weiter nichts.

Was jemand, der eine C-Implementierung schreibt (oder aendert) "tun
darf", haengt dagegen von allgemeinen Gesetze des Landes, dessen
Gesetzen er unterliegt, ab. Das ist fuer den gegebenen Fall nicht nur
vollkommen belanglos, sondern geradezu ein absurdes Kriterium.
Thomas Jahns
2015-07-07 16:54:23 UTC
Permalink
Raw Message
Post by Rainer Weikusat
Wenn man 'Bereichsueberschreitungen bei vorzeichenbehafteter
Ganzzahl-Arithmetik' fuer undefiniert haelt, so folgt daraus, dass
eine C-Implementierung vollkommen unabhaengig von ihrem Verhalten in
diesem Fall als standardkonform angesehen werden kann. Weiter nichts.
Das ganze wäre nicht so traurig, wenn man von den "anderen" Compilern innerhalb
der gcc nicht noch Warnungen aus dem generierten Code bekäme (auf den der
Benutzer von z.B. gfortran[*] keinerlei Einfluss hat), die eindeutig zeigen,
dass auch Compiler-Schreiber von dem stetigen Vermeiden undefinierter overflows
überfordert sind.

Thomas

[*] Fortran kennt per Sprachstandard nur vorzeichenbehaftete Typen und alle
Datenfelder müssen mit diesen beschreibbar/benutzbar/iterierbar sein.
G.B.
2015-07-02 12:23:53 UTC
Permalink
Raw Message
Post by Peter J. Holzer
Das kann dazu führen, dass z.B. aus Code wie
int plus5(int x) {
if (x > INT_MAX - 5) {
printf("Warning: x too large");
}
return x + 5;
}
der ganze if-Block herausoptimiert wird und keine Warnung ausgegeben
wird
Wie sieht's denn hiermit aus:


volatile int do_print;

int plus5(int x) {
if (x > INT_MAX - 5) {
do_print = printf("Warning: x too large");
}
return x + 5;
}
Peter J. Holzer
2015-07-02 17:48:02 UTC
Permalink
Raw Message
Post by G.B.
Post by Peter J. Holzer
Das kann dazu führen, dass z.B. aus Code wie
int plus5(int x) {
if (x > INT_MAX - 5) {
printf("Warning: x too large");
}
return x + 5;
}
der ganze if-Block herausoptimiert wird und keine Warnung ausgegeben
wird
volatile int do_print;
int plus5(int x) {
if (x > INT_MAX - 5) {
do_print = printf("Warning: x too large");
}
return x + 5;
}
Kein Unterschied.

Entweder x ist <= INT_MAX - 5, dann ist das Verhalten definiert und der
if-Zweig wird nicht ausgeführt. Oder x ist größer, dann ist das
Verhalten undefiniert. Und "undefiniert" heißt undefiniert - Punkt.

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Georg Bauhaus
2015-07-03 07:28:02 UTC
Permalink
Raw Message
Post by Peter J. Holzer
Post by G.B.
Post by Peter J. Holzer
Das kann dazu führen, dass z.B. aus Code wie
int plus5(int x) {
if (x > INT_MAX - 5) {
printf("Warning: x too large");
}
return x + 5;
}
der ganze if-Block herausoptimiert wird und keine Warnung ausgegeben
wird
volatile int do_print;
int plus5(int x) {
if (x > INT_MAX - 5) {
do_print = printf("Warning: x too large");
}
return x + 5;
}
Kein Unterschied.
Entweder x ist <= INT_MAX - 5, dann ist das Verhalten definiert und der
if-Zweig wird nicht ausgeführt. Oder x ist größer, dann ist das
Verhalten undefiniert. Und "undefiniert" heißt undefiniert - Punkt.
O.K.

Einige Norm-Nutzer schrieben auch was:
http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html#signed_overflow
Ralf Damaschke
2015-07-03 11:03:58 UTC
Permalink
Raw Message
Post by Peter J. Holzer
Post by G.B.
Post by Peter J. Holzer
der ganze if-Block herausoptimiert wird und keine Warnung ausgegeben
wird
volatile int do_print;
int plus5(int x) {
if (x > INT_MAX - 5) {
do_print = printf("Warning: x too large");
}
return x + 5;
}
Entweder x ist <= INT_MAX - 5, dann ist das Verhalten definiert und der
if-Zweig wird nicht ausgeführt. Oder x ist größer, dann ist das
Verhalten undefiniert. Und "undefiniert" heißt undefiniert - Punkt.
Das Verhalten ist aber erst undefiniert mit der Ausführung der
return-Anweisung. Solange es möglich ist, dass diese nicht stattfindet,
ist das Verhalten bis dahin noch definiert.

Beispiel:
int plus5_1(int x) {
if (x > INT_MAX - 5) {
handle_x_too_large(x);
}
return x + 5;
}

Die if-Anweisung darf nicht entfallen.

Bei einer anderen Reihenfolge darf dagegen die if-Anweisung
wegoptimiert werden:

int plus5_1(int x) {
int zwerg = x + 5;
if (x > INT_MAX - 5) {
handle_x_too_large(x);
}
return zwerg;
}
Peter J. Holzer
2015-07-03 11:23:55 UTC
Permalink
Raw Message
Post by Ralf Damaschke
Post by Peter J. Holzer
Post by G.B.
Post by Peter J. Holzer
der ganze if-Block herausoptimiert wird und keine Warnung ausgegeben
wird
volatile int do_print;
int plus5(int x) {
if (x > INT_MAX - 5) {
do_print = printf("Warning: x too large");
}
return x + 5;
}
Entweder x ist <= INT_MAX - 5, dann ist das Verhalten definiert und der
if-Zweig wird nicht ausgeführt. Oder x ist größer, dann ist das
Verhalten undefiniert. Und "undefiniert" heißt undefiniert - Punkt.
Das Verhalten ist aber erst undefiniert mit der Ausführung der
return-Anweisung.
Jein. Das Verhalten ist nur dann undefiniert, wenn die x + 5 tatsächlich
berechnet wird, da hast Du recht. Aber es gibt keine zeitliche
Abhängigkeit, das undefinierte Verhalten kann auch Code betreffen, der
vorher ausgeführt wird. Genau das soll dieses Beispiel demonstrieren.

Der Inhalt des if-Zweigs ändert nichts am Wert von x und er kann auch
nicht zu einem Abbruch des Programms führen. Wenn also x nach dem if <=
INT_MAX - 5 ist, dann ist es auch vorher <= INT_MAX - 5. Da nur in
diesem Fall das Verhalten definiert ist, muss bei der Evaluation von if
(x > INT_MAX - 5) nur dieser Fall berücksichtigt werden und somit kann
der Compiler annehmen, dass die Bedingung immer falsch ist.

Schaut aus wie eine Zeitreise, ist aber keine - nur Flow-Analyse.
Post by Ralf Damaschke
Solange es möglich ist, dass diese nicht stattfindet,
ist das Verhalten bis dahin noch definiert.
int plus5_1(int x) {
if (x > INT_MAX - 5) {
handle_x_too_large(x);
}
return x + 5;
}
Die if-Anweisung darf nicht entfallen.
Sie darf dann nicht entfallen, wenn es möglich ist, dass
handle_x_too_large(x) das Programm beendet (oder mittels longjmp den
Rest von plus5_1 überspringt). Wenn der Compiler beweisen kann, dass das
nicht möglich ist (weil der Code von handle_x_too_large bekannt ist und
kein exit, longjmp, assert, raise, ... enthält) und daher x+5 auf jeden
Fall berechnet wird, kann sie entfallen.
Post by Ralf Damaschke
Bei einer anderen Reihenfolge darf dagegen die if-Anweisung
int plus5_1(int x) {
int zwerg = x + 5;
if (x > INT_MAX - 5) {
handle_x_too_large(x);
}
return zwerg;
}
Korrekt. Hier gibt es keine Möglichkeit, die Berechnung von x + 5 zu
verhindern.

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Juergen Ilse
2015-07-03 12:23:55 UTC
Permalink
Raw Message
Hallo,
Post by Peter J. Holzer
Post by Ralf Damaschke
Das Verhalten ist aber erst undefiniert mit der Ausführung der
return-Anweisung.
... weil erst ion der return-Anweisung wirklich die Berechnung ausgefuehrt
wird.
Post by Peter J. Holzer
Jein. Das Verhalten ist nur dann undefiniert, wenn die x + 5 tatsächlich
berechnet wird, da hast Du recht. Aber es gibt keine zeitliche
Abhängigkeit, das undefinierte Verhalten kann auch Code betreffen, der
vorher ausgeführt wird. Genau das soll dieses Beispiel demonstrieren.
Korrekt. Allerdings kann der Compiler, sofern er den Quelltext der "handle"
Funktion fuer "too_large" nicht kennt, gar nicht wissen, ob die Addition
spaeter ausgefuehrt wird, also undefifiertes Verhalten vorliegt, die die
handle-Funktion koennte ja auch (z.B. mittels longjump()) die Funktion
verlassen, bevor die Addition ausgefuehrt wird. Daher duerfte in Ralfs
erstem Beispiel IMHO der if-Block nicht wegoptimiert werden. Im zweiten
Beispiel hingegen schon, weil die Addition auf jeden Fall ausgefuehrt wird
und damit das Verhalten auf jeden Fall (unabhaengig davon, wie der Rest der
Funktion durchlaufen wird) undefiniert ist.
Post by Peter J. Holzer
Post by Ralf Damaschke
Solange es möglich ist, dass diese nicht stattfindet,
ist das Verhalten bis dahin noch definiert.
s/bis dahin//
Post by Peter J. Holzer
Post by Ralf Damaschke
int plus5_1(int x) {
if (x > INT_MAX - 5) {
handle_x_too_large(x);
}
return x + 5;
}
Die if-Anweisung darf nicht entfallen.
Sie darf dann nicht entfallen, wenn es möglich ist, dass
handle_x_too_large(x) das Programm beendet (oder mittels longjmp den
Rest von plus5_1 überspringt). Wenn der Compiler beweisen kann, dass das
nicht möglich ist (weil der Code von handle_x_too_large bekannt ist und
kein exit, longjmp, assert, raise, ... enthält) und daher x+5 auf jeden
Fall berechnet wird, kann sie entfallen.
Korrekt. Dann sind wir uns ja einig.

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
--
Ein Domainname ist nur ein Name, nicht mehr und nicht weniger.
Wer mehr hineininterpretiert, hat das Domain-Name-System nicht
verstanden.
Peter J. Holzer
2015-07-03 15:29:28 UTC
Permalink
Raw Message
Post by Juergen Ilse
Hallo,
Post by Peter J. Holzer
Post by Ralf Damaschke
Das Verhalten ist aber erst undefiniert mit der Ausführung der
return-Anweisung.
... weil erst ion der return-Anweisung wirklich die Berechnung ausgefuehrt
wird.
Post by Peter J. Holzer
Jein. Das Verhalten ist nur dann undefiniert, wenn die x + 5 tatsächlich
berechnet wird, da hast Du recht. Aber es gibt keine zeitliche
Abhängigkeit, das undefinierte Verhalten kann auch Code betreffen, der
vorher ausgeführt wird. Genau das soll dieses Beispiel demonstrieren.
Korrekt. Allerdings kann der Compiler, sofern er den Quelltext der "handle"
Funktion fuer "too_large" nicht kennt, gar nicht wissen, ob die Addition
spaeter ausgefuehrt wird, also undefifiertes Verhalten vorliegt,
In dem Beispiel, auf das sich das bezog, kam handle_x_too_large() gar
nicht vor, sondern printf(). Was printf() macht, ist aber vom Standard
definiert und daher kann der Compiler das wissen. Mein Vorposter (Georg,
nicht Ralf) wollte hier mittels einer Zuweisung zu einer
volatile-Variable eine Reihenfolge erzwingen, aber das macht m.M.n.
keinen Unterschied.

Auf das Beispiel mit handle_x_too_large() bin ich weiter unten
eingegangen und ...
Post by Juergen Ilse
die die handle-Funktion koennte ja auch (z.B. mittels longjump()) die
Funktion verlassen, bevor die Addition ausgefuehrt wird.
... da habe ich longjmp sogar explizit erwähnt (du zitierst das ja
unten).
Post by Juergen Ilse
Daher duerfte in Ralfs erstem Beispiel IMHO der if-Block nicht
wegoptimiert werden. Im zweiten Beispiel hingegen schon, weil die
Addition auf jeden Fall ausgefuehrt wird und damit das Verhalten auf
jeden Fall (unabhaengig davon, wie der Rest der Funktion durchlaufen
wird) undefiniert ist.
Ja, wir sind uns einig.

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Stefan Reuther
2015-07-03 16:32:56 UTC
Permalink
Raw Message
Post by Thomas Koenig
Integer-Zahlen wie 12345678901234567890123456789012345678901234567890
sollten bei Eingabe eine Fehlermeldung erzeugen, wenn der Datentyp es nicht
packt. Was macht denn scanf() damit...?
Real-Zahlen wie 1.2e345678 bei der Eingabe (ebenso, wwas macht scanf damit)?
Für sowas empfehlen sich statt 'scanf' eher 'strtol', 'strtod' und Freunde.


Stefan
Loading...