Discussion:
Ist ein Zeiger ausserhalb eines Feldes?
(zu alt für eine Antwort)
Rainer Weikusat
2018-08-01 15:32:32 UTC
Permalink
Das mag sich wie eine einfach zu beantwortende Frage anhoeren, aber 'in
C' (wie definiert) kann diese Frage nur entschieden werden, falls beide
Zeiger denselben Typ haben und nur indem man den Zeiger der Reihe nach
mit Zeigern auf alle Feldelemente vergleicht.

Folgendes sollte allerdings auch 'funktionieren', wenigstens insofern
die technischen Bedingungen gegeben sind und dass man nicht befuerchten
muss, dass sich das ein Compiler-Entwickler wegen "Das ist undefinert
!!1 Wenn wirs nicht machen funktionierts schneller nicht !!2" als
"Optimierung" selbst zum Geburtstag schenkt:

int on_wheel(struct timer *t)
{
return (uintptr_t)t->me - (uintptr_t)timers.wheel < TIMER_WHEEL_SZ;
}
Helmut Schellong
2018-08-02 09:22:54 UTC
Permalink
On 08/01/2018 17:32, Rainer Weikusat wrote:
[...]
Post by Rainer Weikusat
Folgendes sollte allerdings auch 'funktionieren', wenigstens insofern
die technischen Bedingungen gegeben sind und dass man nicht befuerchten
muss, dass sich das ein Compiler-Entwickler wegen "Das ist undefinert
!!1 Wenn wirs nicht machen funktionierts schneller nicht !!2" als
int on_wheel(struct timer *t)
{
return (uintptr_t)t->me - (uintptr_t)timers.wheel < TIMER_WHEEL_SZ;
}
Also, ich habe das schon 1995 in modifizierter Form angewandt:
greater_equal Startadresse && less_equal Adresse_letztes_Element.

Was passiert, wenn oben der Subtrahend größer als der Minuend ist?
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Claus Reibenstein
2018-08-02 13:01:30 UTC
Permalink
Post by Helmut Schellong
Folgendes sollte allerdings auch 'funktionieren', [...]
int on_wheel(struct timer *t)
{
return (uintptr_t)t->me - (uintptr_t)timers.wheel < TIMER_WHEEL_SZ;
}
greater_equal Startadresse && less_equal Adresse_letztes_Element.
Das reicht nicht. Damit stellst Du bestenfalls fest, ob die Adresse
innerhalb des Feldes liegt, weißt aber immer noch nicht, ob sie auf ein
gültiges Element dieses Feldes zeigt.

Aus diesem Grund halte ich schon Rainers Ansatz ebenfalls für falsch.

Gruß
Claus
Helmut Schellong
2018-08-03 09:13:00 UTC
Permalink
Post by Claus Reibenstein
Post by Helmut Schellong
Folgendes sollte allerdings auch 'funktionieren', [...]
int on_wheel(struct timer *t)
{
return (uintptr_t)t->me - (uintptr_t)timers.wheel < TIMER_WHEEL_SZ;
}
greater_equal Startadresse && less_equal Adresse_letztes_Element.
Das reicht nicht. Damit stellst Du bestenfalls fest, ob die Adresse
innerhalb des Feldes liegt, weißt aber immer noch nicht, ob sie auf ein
gültiges Element dieses Feldes zeigt.
Das ist hier zunächst gar keine Forderung.
Man kann das anschließend mittels sizeof(element_typ) feststellen.
--
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
2018-08-05 12:15:22 UTC
Permalink
Post by Claus Reibenstein
Post by Helmut Schellong
Folgendes sollte allerdings auch 'funktionieren', [...]
int on_wheel(struct timer *t)
{
return (uintptr_t)t->me - (uintptr_t)timers.wheel < TIMER_WHEEL_SZ;
}
greater_equal Startadresse && less_equal Adresse_letztes_Element.
Das reicht nicht. Damit stellst Du bestenfalls fest, ob die Adresse
innerhalb des Feldes liegt, weißt aber immer noch nicht, ob sie auf ein
gültiges Element dieses Feldes zeigt.
Aufgrund von Helmuts semantischem Exkurs habe ich das jetzt auch
verstanden (Juchu!). Die Frage selber moechte ich nicht diskutieren,
hier aber anmerken, dass ich es mit genau zwei moeglichen Faellen zu tun
habe:

1. Ein gueltiger struct timer ** zeigt auf ein Feldelement.

2. Ein gueltiger struct timer ** zeigt auf einen Zeiger, der garantiert
kein Feldelement ist (naemlich auf den Nachfolgerzeiger des
Vorgaengers).
Rainer Weikusat
2018-08-02 17:22:36 UTC
Permalink
Post by Helmut Schellong
[...]
Post by Rainer Weikusat
Folgendes sollte allerdings auch 'funktionieren', wenigstens insofern
die technischen Bedingungen gegeben sind und dass man nicht befuerchten
muss, dass sich das ein Compiler-Entwickler wegen "Das ist undefinert
!!1 Wenn wirs nicht machen funktionierts schneller nicht !!2" als
int on_wheel(struct timer *t)
{
return (uintptr_t)t->me - (uintptr_t)timers.wheel < TIMER_WHEEL_SZ;
}
greater_equal Startadresse && less_equal Adresse_letztes_Element.
Fuer den allgemeinen Fall hat das undefiniertes Verhalten (6.5.8|5): Es
gibt nur dann eine Ordungsrelation zweier Zeiger, falls beide auf
Mitglieder derselben Struktur oder desselben Feldes zeigen, mit 'Feld
der Groesse n beginnend an Addresse a' durch (a, a+n) begrenzt.
Post by Helmut Schellong
Was passiert, wenn oben der Subtrahend größer als der Minuend ist?
Das, was bei einer Subtraktion in eine Ring passiert: Denkt man sich die
Mitglieder des Rings als tatsaechlichen Ring angeordnet, bedeutet
'Subtraktion' 'Bewegung im Gegenuhrzeigersinn'. Man kommt also irgendwo
hinter dem Ende des Feldes auf dessen anderer Seite an.
Helmut Schellong
2018-08-03 10:01:06 UTC
Permalink
Post by Rainer Weikusat
Post by Helmut Schellong
[...]
Post by Rainer Weikusat
int on_wheel(struct timer *t)
{
return (uintptr_t)t->me - (uintptr_t)timers.wheel < TIMER_WHEEL_SZ;
}
greater_equal Startadresse && less_equal Adresse_letztes_Element.
Fuer den allgemeinen Fall hat das undefiniertes Verhalten (6.5.8|5): Es
gibt nur dann eine Ordungsrelation zweier Zeiger, falls beide auf
Mitglieder derselben Struktur oder desselben Feldes zeigen, mit 'Feld
der Groesse n beginnend an Addresse a' durch (a, a+n) begrenzt.
Ich argumentiere jetzt nicht gegen undef. Verhalten.

Ich kann da Text aus dem Standard zitieren, der schon vor
langer Zeit klärend war:

Two pointers compare equal if and only if both are null pointers, both
are pointers to the same object (including a pointer to an object
and a subobject at its beginning) or function,
both are pointers to one past the last element of the same array object,
or one is a pointer to one past the end of one array object and the other
is a pointer to the start of a different array object that happens
to immediately follow the first array object in the address space. 109)

109) Two objects may be adjacent in memory because they are
adjacent elements of a larger array or
adjacent members of a structure with no padding between them,
or because the implementation chose to place them so, even though
they are unrelated.
If prior invalid pointer operations (such as accesses outside array bounds)
produced undefined behavior, subsequent comparisons also produce undefined
behavior.

Die Elemente eines Arrays sind folglich ohne Zwischenraum
aneinanderstoßend.
Die (int)Adressen springen um sizeof(element_typ).

Die reale Praxis (64bit-Pointer) deutet stark darauf hin, daß
Pointer aus der selben Speicherklasse jeweils aus einem
linearen Feld stammen.

Ich hatte mal nach Änderung einer realloc-Adresse alle Pointer
aus dem Bereich umändern (neu generieren) müssen, passend
zur neuen Adresse.
Dazu brauchte ich old_BasePointer und new_BasePointer.
Das funktioniert einwandfrei.
Das ist aber nicht das, was ich ganz oben nannte.
--
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
2018-08-03 15:50:52 UTC
Permalink
Post by Helmut Schellong
Post by Rainer Weikusat
Post by Helmut Schellong
[...]
Post by Rainer Weikusat
int on_wheel(struct timer *t)
{
return (uintptr_t)t->me - (uintptr_t)timers.wheel < TIMER_WHEEL_SZ;
}
greater_equal Startadresse && less_equal Adresse_letztes_Element.
Fuer den allgemeinen Fall hat das undefiniertes Verhalten (6.5.8|5): Es
gibt nur dann eine Ordungsrelation zweier Zeiger, falls beide auf
Mitglieder derselben Struktur oder desselben Feldes zeigen, mit 'Feld
der Groesse n beginnend an Addresse a' durch (a, a+n) begrenzt.
Ich argumentiere jetzt nicht gegen undef. Verhalten.
Ich kann da Text aus dem Standard zitieren, der schon vor
Two pointers compare equal if and only if
[...]
Post by Helmut Schellong
109) Two objects may be adjacent in memory
[...]

Verstehe ich jetzt nicht ganz, was das miteinander zu tun haben soll. <>
fuer Zeiger wird in 6.5.8|5 definiert: Falls zwei Zeiger auf Mitglieder
derselben Struktur zeigen, gilt derjenige als kleiner, der auf das
frueher deklarierte Mitgleid zeigt. Zeigen zwei Zeiger in dasselbe Feld,
gilt derjenige, der auf das Feldelement mit dem kleineren Index zeigt,
als kleiner. Sei a ein Feld der Groesse n, dann sind alle Zeiger auf
Feldelemente kleiner als der Zeiger a + n.

Ferner gelten zwei Zeiger auf dasselbe Objekt als gleich, zwei Zeiger
mit Wert a + n sind gleich und alle Zeiger auf Mitglieder von unions.

"In all other cases, the behaviour is undefined".

Dh ein Vergleich p < a, p Zeiger, a wie oben, hat im allgemeinen Fall
undefiniertes Verhalten (aus den obigen Regeln lassen sich Ausnahmen
herleiten).
Helmut Schellong
2018-08-03 19:31:28 UTC
Permalink
Post by Rainer Weikusat
Post by Helmut Schellong
Ich kann da Text aus dem Standard zitieren, der schon vor
Two pointers compare equal if and only if
[...]
Post by Helmut Schellong
109) Two objects may be adjacent in memory
[...]
Verstehe ich jetzt nicht ganz, was das miteinander zu tun haben soll. <>
fuer Zeiger wird in 6.5.8|5 definiert: [...]
Es gibt eine Verwandtschaft.
Der Standard garantiert dadurch, daß Arrays keinen Zwischenraum
zwischen ihren Elementen haben.
Auch nicht, wenn die Elemente Subarrays sind.
Dies wurde mal vor langer Zeit (hartnäckig) angezweifelt.
--
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
2018-08-03 20:03:43 UTC
Permalink
Post by Helmut Schellong
Post by Rainer Weikusat
Post by Helmut Schellong
Ich kann da Text aus dem Standard zitieren, der schon vor
Two pointers compare equal if and only if
[...]
Post by Helmut Schellong
109) Two objects may be adjacent in memory
[...]
Verstehe ich jetzt nicht ganz, was das miteinander zu tun haben soll. <>
fuer Zeiger wird in 6.5.8|5 definiert: [...]
Es gibt eine Verwandtschaft.
Der Standard garantiert dadurch, daß Arrays keinen Zwischenraum
zwischen ihren Elementen haben.
Auch nicht, wenn die Elemente Subarrays sind.
Dies wurde mal vor langer Zeit (hartnäckig) angezweifelt.
Sehe ich nicht so: Die Frage, ob ein Zeiger ausserhalb eines Feldes ist,
hat man der Frage, ob es zwischen Feldelementen Zwischenraeume geben
kann (bizarrer Einfall, BTW) nichts zu tun.
Helmut Schellong
2018-08-04 11:48:57 UTC
Permalink
[...]
Post by Rainer Weikusat
Post by Helmut Schellong
Es gibt eine Verwandtschaft.
Der Standard garantiert dadurch, daß Arrays keinen Zwischenraum
zwischen ihren Elementen haben.
Auch nicht, wenn die Elemente Subarrays sind.
Dies wurde mal vor langer Zeit (hartnäckig) angezweifelt.
Sehe ich nicht so: Die Frage, ob ein Zeiger ausserhalb eines Feldes ist,
hat man der Frage, ob es zwischen Feldelementen Zwischenraeume geben
kann (bizarrer Einfall, BTW) nichts zu tun.
Na ja, in einem Zwischenraum wäre außerhalb des Arrays.
Der Zwischenraum könnte ja beliebig groß sein, wären solche möglich.

"bizarrer Einfall":
Da im Standard bei den Definitionen von Arrays nicht ausdrücklich
steht, daß Lücken zwischen Elementen ausgeschlossen sind, gingen
Programmierer davon aus, daß es sie geben könnte.
Ich gehörte nicht zu diesen Leuten.

Ich weiß, das Helmut Leitner dazu gehörte:
http://www.dsewiki.org/wiki.cgi?OffeneCeeFAQ/NichtlinearesFeldAlsLinearesFeld

Es gab auch Diskussionen:
int feld[2][3];
x = feld[0][3];
...
Daraus entstand auch die Unsicherheit, ob es Lücken
in einem Array geben könnte.
--
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
2018-08-04 13:51:17 UTC
Permalink
Post by Helmut Schellong
[...]
Post by Rainer Weikusat
Post by Helmut Schellong
Es gibt eine Verwandtschaft.
Der Standard garantiert dadurch, daß Arrays keinen Zwischenraum
zwischen ihren Elementen haben.
Auch nicht, wenn die Elemente Subarrays sind.
Dies wurde mal vor langer Zeit (hartnäckig) angezweifelt.
Sehe ich nicht so: Die Frage, ob ein Zeiger ausserhalb eines Feldes ist,
hat man der Frage, ob es zwischen Feldelementen Zwischenraeume geben
kann (bizarrer Einfall, BTW) nichts zu tun.
Na ja, in einem Zwischenraum wäre außerhalb des Arrays.
Falls Du hier unbedingt einen alten Streit aufwaermen moechtest - und so
scheint es mir jedenfalls - waere es ganz freundlich, dass nicht
grundlos an Postings von mir zu haengen ...
Helmut Schellong
2018-08-04 15:26:39 UTC
Permalink
Post by Rainer Weikusat
Post by Helmut Schellong
[...]
Post by Rainer Weikusat
Post by Helmut Schellong
Es gibt eine Verwandtschaft.
Der Standard garantiert dadurch, daß Arrays keinen Zwischenraum
zwischen ihren Elementen haben.
Auch nicht, wenn die Elemente Subarrays sind.
Dies wurde mal vor langer Zeit (hartnäckig) angezweifelt.
Sehe ich nicht so: Die Frage, ob ein Zeiger ausserhalb eines Feldes ist,
hat man der Frage, ob es zwischen Feldelementen Zwischenraeume geben
kann (bizarrer Einfall, BTW) nichts zu tun.
Na ja, in einem Zwischenraum wäre außerhalb des Arrays.
Falls Du hier unbedingt einen alten Streit aufwaermen moechtest - und so
scheint es mir jedenfalls - waere es ganz freundlich, dass nicht
grundlos an Postings von mir zu haengen ...
Ich will gar nicht einen alten Streit aufwärmen;
ich weiß auch nicht, welcher alte Streit gemeint ist...

Wenn

int on_wheel(struct timer *t)
{
return (uintptr_t)t->me - (uintptr_t)timers.wheel < TIMER_WHEEL_SZ;
}

gebracht wird, kommt man automatisch, nach und nach, auf diejenigen
Themen, die hier eine Verwandtschaft haben.
Ich erhalte jedenfalls eine Menge Assoziationen in den Vordergrund.
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Juergen Ilse
2018-08-03 21:15:02 UTC
Permalink
Hallo,
Post by Helmut Schellong
Die reale Praxis (64bit-Pointer) deutet stark darauf hin, daß
Pointer aus der selben Speicherklasse jeweils aus einem
linearen Feld stammen.
Der Standad verlangt das nicht, und bereits ein unzulaessiger Pointervergleich
ist undefiniertes Verhalten (und koennte, in Abhaengigkeit von der Implemen-
tierung, auch zum Absturz des Programms, falschen Ergebnissen, etc. fuehren ...

Der Standad garantiert dir kein bestimmtes Speichermodell, erst recht nicht
deine Annahme ...

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
Helmut Schellong
2018-08-04 12:12:15 UTC
Permalink
Post by Juergen Ilse
Hallo,
Post by Helmut Schellong
Die reale Praxis (64bit-Pointer) deutet stark darauf hin, daß
Pointer aus der selben Speicherklasse jeweils aus einem
linearen Feld stammen.
Der Standad verlangt das nicht, und bereits ein unzulaessiger Pointervergleich
ist undefiniertes Verhalten (und koennte, in Abhaengigkeit von der Implemen-
tierung, auch zum Absturz des Programms, falschen Ergebnissen, etc. fuehren ...
Der Standad garantiert dir kein bestimmtes Speichermodell, erst recht nicht
deine Annahme ...
Ich beziehe mich hier auch nicht auf den Standard.

Bezogen auf die nicht vergangene reale Praxis meine ich, daß
wohl niemand eine konkrete Hardware nennen kann, bei der
mehrere C-Objekte, die alle außerhalb von Funktionen
folgendermaßen definiert wurden:
'const int arrayname[n]; //Beispiel',
nicht alle innerhalb eines linearen Speicherbereiches liegen,
wobei die Arrays nur einen kleinen Bruchteil des
verfügbaren Arbeitsspeichers benötigen.
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Оlе Ѕtrеісhеr
2018-08-06 06:54:11 UTC
Permalink
Post by Helmut Schellong
Ich beziehe mich hier auch nicht auf den Standard.
Bezogen auf die nicht vergangene reale Praxis meine ich, daß
wohl niemand eine konkrete Hardware nennen kann, bei der
mehrere C-Objekte, die alle außerhalb von Funktionen
'const int arrayname[n]; //Beispiel',
nicht alle innerhalb eines linearen Speicherbereiches liegen,
wobei die Arrays nur einen kleinen Bruchteil des
verfügbaren Arbeitsspeichers benötigen.
Wenn Du Deine Programme nicht nur für den Augenblick schreibst, sondern
auch für die Zukunft, ist ein "das gibt es im Augeblick nicht" kein
gutes Argument. Glaskugeln sind nämlich derzeit schlecht zu haben.

Ein Beispiel dafür: "früher" war die Programminitialisierung
deterministisch, d.h. man konnte ein Programm zweimal starten und bekam
dann genau die gleichen statischen Speicheradressen. Das machte es
möglich, einfach den Speicherinhalt (incl. Pointer, solange nicht
per malloc erzeugt) in eine Datei zu dumpen und später wieder
einzulesen. Nicht standardkonform, aber: hey, who cares? "Bezogen auf
die reale Praxis..." hätte man da vor zehn Jahren auch formulieren
können.

Und heute gibt es Address Space Layout Optimization, die sowas kaputt
macht. Und die es dem heutigen Maintainer der Software schwer macht, das
Programm am Laufen zu halten.

Sorry, aber so ein pragmatisches Vorgehen ist schlicht "fishing for huge
future problems".

Cheers

Ole
Juergen Ilse
2018-08-06 11:17:08 UTC
Permalink
Post by Оlе Ѕtrеісhеr
Post by Helmut Schellong
Ich beziehe mich hier auch nicht auf den Standard.
Bezogen auf die nicht vergangene reale Praxis meine ich, daß
wohl niemand eine konkrete Hardware nennen kann, bei der
mehrere C-Objekte, die alle außerhalb von Funktionen
'const int arrayname[n]; //Beispiel',
nicht alle innerhalb eines linearen Speicherbereiches liegen,
wobei die Arrays nur einen kleinen Bruchteil des
verfügbaren Arbeitsspeichers benötigen.
Wenn Du Deine Programme nicht nur für den Augenblick schreibst, sondern
auch für die Zukunft, ist ein "das gibt es im Augeblick nicht" kein
gutes Argument. Glaskugeln sind nämlich derzeit schlecht zu haben.
... und billige Glaskugeln machen keine zuverlaessigen Vorhersagen, dazu
bedarf es dann schon hochwertiger Kristallkugeln, die noch schwerer zu
ergattern sind ...
;-)
Post by Оlе Ѕtrеісhеr
Sorry, aber so ein pragmatisches Vorgehen ist schlicht "fishing for huge
future problems".
ACK.

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
Helmut Schellong
2018-08-06 11:34:45 UTC
Permalink
Post by Оlе Ѕtrеісhеr
Post by Helmut Schellong
Ich beziehe mich hier auch nicht auf den Standard.
Bezogen auf die nicht vergangene reale Praxis meine ich, daß
wohl niemand eine konkrete Hardware nennen kann, bei der
mehrere C-Objekte, die alle außerhalb von Funktionen
'const int arrayname[n]; //Beispiel',
nicht alle innerhalb eines linearen Speicherbereiches liegen,
wobei die Arrays nur einen kleinen Bruchteil des
verfügbaren Arbeitsspeichers benötigen.
Wenn Du Deine Programme nicht nur für den Augenblick schreibst, sondern
auch für die Zukunft, ist ein "das gibt es im Augeblick nicht" kein
gutes Argument. Glaskugeln sind nämlich derzeit schlecht zu haben.
[...]
Post by Оlе Ѕtrеісhеr
Sorry, aber so ein pragmatisches Vorgehen ist schlicht "fishing for huge
future problems".
Ich werde sehr oft falsch verstanden, obwohl ich klar schreibe.

Bisher hat niemand eine 'konkrete Hardware' (s.o.) genannt ...

Das sind einfach Gedankengänge aufgrund von Assoziationen.
Damit will ich Verhältnisse richtig positionieren.

Der C-Standard will erreichen, daß C auf jeder (denkbaren)
Plattform der Welt (portabel) laufen kann.
Und sei sie ultra-exotisch.
Im Wesentlichen aus diesem Grund schreibt der Standard oftmals von UB.


In Anlehnung an den Betreff dieses Threads:

int inside_arr(void *base, size_t sz, void *test)
{
if ((uintptr_t)test >= (uintptr_t)base &&
(uintptr_t)test < (uintptr_t)base+sz) return 1;
return 0;
}

Das muß funktionieren, portabel und konform.
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Оlе Ѕtrеісhеr
2018-08-06 12:18:25 UTC
Permalink
Post by Helmut Schellong
int inside_arr(void *base, size_t sz, void *test)
{
if ((uintptr_t)test >= (uintptr_t)base &&
(uintptr_t)test < (uintptr_t)base+sz) return 1;
return 0;
}
Das muß funktionieren, portabel und konform.
Nein. Je nach genauer Anwendung könnte der Compiler nämlich zur
Compilezeit nachprüfen nönnen, dass base die Größe von (maximal) sz hat,
daher jeder standardkonforme Aufruf der Funktion immer eine 1
zurückliefern muss und der Funktionsaufruf keine Seiteneffekte hat. Dann
kann er den Aufruf schlicht wegoptimieren.

"Also bei mir hats funktioniert"

Schöne Grüße

Ole
Rainer Weikusat
2018-08-06 14:11:33 UTC
Permalink
Post by Оlе Ѕtrеісhеr
Post by Helmut Schellong
int inside_arr(void *base, size_t sz, void *test)
{
if ((uintptr_t)test >= (uintptr_t)base &&
(uintptr_t)test < (uintptr_t)base+sz) return 1;
return 0;
}
Das muß funktionieren, portabel und konform.
Nein. Je nach genauer Anwendung könnte der Compiler nämlich zur
Compilezeit nachprüfen nönnen, dass base die Größe von (maximal) sz hat,
daher jeder standardkonforme Aufruf der Funktion immer eine 1
zurückliefern muss
Es gibt wirre Beschraenkungen fuer Zeigervergleiche in der C-Norm aber
nicht fuer Zahlen. Diese sollen uebrigens Hardwareentwicklern helfen,
C-Compiler anbieten zu koennen _und nicht_ Compiler- und Bibliotheksentwickler dazu
animieren, beliebigen Bloedsinn zu implementieren, weil sie sich
einbilden, dass der irgendwie 'schneller' waere ...
Stefan Reuther
2018-08-06 17:30:28 UTC
Permalink
Post by Helmut Schellong
Post by Оlе Ѕtrеісhеr
Post by Helmut Schellong
'const int arrayname[n]; //Beispiel',
nicht alle innerhalb eines linearen Speicherbereiches liegen,
wobei die Arrays nur einen kleinen Bruchteil des
verfügbaren Arbeitsspeichers benötigen.
[...]
Post by Helmut Schellong
Post by Оlе Ѕtrеісhеr
Sorry, aber so ein pragmatisches Vorgehen ist schlicht "fishing for huge
future problems".
Ich werde sehr oft falsch verstanden, obwohl ich klar schreibe.
Bisher hat niemand eine 'konkrete Hardware' (s.o.) genannt ...
i286.

Wenn die dies als praktische, bekannte Hardware nicht ausreicht ("ist ja
vergangen"): Prozessorarchitekturen mit mehreren Speicherbänken gibt es
durchaus noch. Es gibt da Mikrocontroller in Harvard-Architektur (ich
meine, PIC was so ein Genosse). Es gibt DSPs mit X- und Y-Speicherbank.

Sicher alles Nischen, und die Abbildungen von C auf jene Architekturen
gehen jede Menge Kompromisse ein - teils, weil sie's im Compiler nicht
hinbekommen, aber eben teils auch, weil real existierende Programme halt
nach linearem Speicher verlangen. In Summe zwingt man dann eben doch die
Entwickler, Intrinsics zu schreiben.
Post by Helmut Schellong
int inside_arr(void *base, size_t sz, void *test)
{
if ((uintptr_t)test >= (uintptr_t)base &&
(uintptr_t)test < (uintptr_t)base+sz) return 1;
return 0;
}
Das muß funktionieren, portabel und konform.
Eben nicht auf i286.

Davon abgesehen muss das schon deswegen nicht funktionieren, weil
uintptr_t ein optionaler Typ ist, den der Compiler gar nicht anbieten muss.


Stefan
Helmut Schellong
2018-08-07 10:06:33 UTC
Permalink
Post by Stefan Reuther
Post by Helmut Schellong
Post by Оlе Ѕtrеісhеr
Post by Helmut Schellong
'const int arrayname[n]; //Beispiel',
nicht alle innerhalb eines linearen Speicherbereiches liegen,
wobei die Arrays nur einen kleinen Bruchteil des
verfügbaren Arbeitsspeichers benötigen.
[...]
Post by Helmut Schellong
Post by Оlе Ѕtrеісhеr
Sorry, aber so ein pragmatisches Vorgehen ist schlicht "fishing for huge
future problems".
Ich werde sehr oft falsch verstanden, obwohl ich klar schreibe.
Bisher hat niemand eine 'konkrete Hardware' (s.o.) genannt ...
i286.
Wenn die dies als praktische, bekannte Hardware nicht ausreicht ("ist ja
vergangen"): Prozessorarchitekturen mit mehreren Speicherbänken gibt es
durchaus noch. Es gibt da Mikrocontroller in Harvard-Architektur (ich
meine, PIC was so ein Genosse). Es gibt DSPs mit X- und Y-Speicherbank.
Solche Plattformen kann ich ausschließen.
Mikrocontroller habe ich direkt programmiert, Lauf ohne Betriebssytem.
Ich richtete mich nach der Hardware: Speichermodell und intensive
Konfiguration des Linkers.
Ich habe da auch 64KB-Einheiten per Skript auf C-Ebene hergestellt.
Fazit: Ich habe quasi alles selbst gemacht.

Um es anders zu sagen:
Ich meine natürlich Plattformen mit 'großem' Betriebssytem.
Und ich schloß Vergangenes aus, und sprach bereits
von 64bit-Pointern (flat).
Post by Stefan Reuther
Post by Helmut Schellong
int inside_arr(void *base, size_t sz, void *test)
{
if ((uintptr_t)test >= (uintptr_t)base &&
(uintptr_t)test < (uintptr_t)base+sz) return 1;
return 0;
}
Das muß funktionieren, portabel und konform.
Eben nicht auf i286.
Das kann eine Täuschung sein!
Die Segment-Register wurden hinsichtlich ihrer Verwendung
starr festgelegt.
cs ds ss es : Code- Daten- Stack- Extra-Segment
Der Compiler hatte gleich definierte Objekte _nicht_ beliebig
auf diverse Segmente verteilt.

Das Konzept far-Pointer entfällt hier ohnehin.
Wir wollen ja nicht verrückt spielen...
Post by Stefan Reuther
Davon abgesehen muss das schon deswegen nicht funktionieren, weil
uintptr_t ein optionaler Typ ist, den der Compiler gar nicht anbieten muss.
Das ist kein Argument, weil es dann einen ERROR gibt.
Die Formulierung 'nicht funktionieren' paßt hier - bei einem
Nichtvorhandensein - nicht so recht.
Man muß sich in diesem Fall eine andere Lösung ausdenken.
Ich schaffte das.
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Stefan Reuther
2018-08-07 17:33:21 UTC
Permalink
Post by Helmut Schellong
Post by Stefan Reuther
Post by Helmut Schellong
Post by Helmut Schellong
'const int arrayname[n]; //Beispiel',
[...]
Post by Helmut Schellong
Post by Stefan Reuther
Post by Helmut Schellong
Bisher hat niemand eine 'konkrete Hardware' (s.o.) genannt ...
i286.
Wenn die dies als praktische, bekannte Hardware nicht ausreicht ("ist ja
vergangen"): Prozessorarchitekturen mit mehreren Speicherbänken gibt es
durchaus noch. Es gibt da Mikrocontroller in Harvard-Architektur (ich
meine, PIC was so ein Genosse). Es gibt DSPs mit X- und Y-Speicherbank.
Solche Plattformen kann ich ausschließen.
Mikrocontroller habe ich direkt programmiert, Lauf ohne Betriebssytem.
Was du ausschließen kannst ist aber bei der Betrachtung einer
Programmiersprache nicht relevant. Du kannst privat machen was du
willst. Sobald es aber um etwas geht, das langlebiger sein soll und dein
Haus verlässt, gelten schon andere Kriterien.
Post by Helmut Schellong
Ich meine natürlich Plattformen mit 'großem' Betriebssytem.
Und ich schloß Vergangenes aus, und sprach bereits
von 64bit-Pointern (flat).
Um es anders zu sagen: du drehst dich solange im Kreis und änderst die
Rahmenbedingungen solange, bis deine Aussage passt.

Stehen die Rahmenbedingungen wenigstens dokumentiert in der Nähe des
Quellcodes?
Post by Helmut Schellong
Post by Stefan Reuther
Post by Helmut Schellong
int inside_arr(void *base, size_t sz, void *test)
{
if ((uintptr_t)test >= (uintptr_t)base &&
(uintptr_t)test < (uintptr_t)base+sz) return 1;
return 0;
}
Das muß funktionieren, portabel und konform.
Eben nicht auf i286.
Das kann eine Täuschung sein!
Die Segment-Register wurden hinsichtlich ihrer Verwendung
starr festgelegt.
cs ds ss es : Code- Daten- Stack- Extra-Segment
Der Compiler hatte gleich definierte Objekte _nicht_ beliebig
auf diverse Segmente verteilt.
In den Speichermodellen mit mehreren Datensegmenten (huge, large,
compact) schon.
Post by Helmut Schellong
Post by Stefan Reuther
Davon abgesehen muss das schon deswegen nicht funktionieren, weil
uintptr_t ein optionaler Typ ist, den der Compiler gar nicht anbieten muss.
Das ist kein Argument, weil es dann einen ERROR gibt.
Die Formulierung 'nicht funktionieren' paßt hier - bei einem
Nichtvorhandensein - nicht so recht.
"Es gibt einen Fehler" als "es funktioniert" zu buchen ist zumindest
eine sehr kreative Interpretation.


Stefan
Helmut Schellong
2018-08-08 11:55:53 UTC
Permalink
Post by Stefan Reuther
Post by Helmut Schellong
Post by Stefan Reuther
Post by Helmut Schellong
Post by Helmut Schellong
'const int arrayname[n]; //Beispiel',
[...]
Post by Helmut Schellong
Post by Stefan Reuther
Post by Helmut Schellong
Bisher hat niemand eine 'konkrete Hardware' (s.o.) genannt ...
i286.
Wenn die dies als praktische, bekannte Hardware nicht ausreicht ("ist ja
vergangen"): Prozessorarchitekturen mit mehreren Speicherbänken gibt es
durchaus noch. Es gibt da Mikrocontroller in Harvard-Architektur (ich
meine, PIC was so ein Genosse). Es gibt DSPs mit X- und Y-Speicherbank.
Solche Plattformen kann ich ausschließen.
Mikrocontroller habe ich direkt programmiert, Lauf ohne Betriebssytem.
Was du ausschließen kannst ist aber bei der Betrachtung einer
Programmiersprache nicht relevant. Du kannst privat machen was du
willst. Sobald es aber um etwas geht, das langlebiger sein soll und dein
Haus verlässt, gelten schon andere Kriterien.
Es ist relevant, und es ist nicht privat:
Wenn ich Mikrocontroller programmiere (ich hatte das beschrieben),
übernehme ich selbst die Aufgabe des Linkers, entscheidende
Objekte betreffend!
Ich bestimme also selbst, wo der Linker Objekte positionieren soll!
In diesem Fall erübrigt sich automatisch die Diskussion, ob bestimmte
Objekte in einem linearen Speicherbereich liegen oder nicht.

Die Diskussion ist nur sinnvoll für eine Plattform, auf der
ein Betriebssytem läuft, wie Windows, Linux, OSX, FreeBSD, etc.

Zum Beweis:
Nachfolgend wurden mehrere Sektionen
manuell positioniert, auch SSTACK:
=====================================================================
S_Addr. -E_Addr. Size Section Type Al Sec.
00000000-000000EF 000000F0 IO N RW-- 00 ABS IOBASE
00000100-000001D1 000000D2 DIR P RW-- 02 REL DIRDATA
000001D2-........ 00000000 DATA P RW-- 02 REL LIBDATA
000001D2-........ 00000000 DIR P RW-- 02 REL DIRINIT
000001D2-........ 00000000 DATA P RW-- 02 REL LIBINIT
000001D2-........ 00000000 DATA P RW-- 02 REL CINIT
000001D2-........ 00000000 DATA P RW-- 02 REL CINIT2
000001D2-000001D3 00000002 STACK P RW-- 02 REL USTACK
00000200-0000020F 00000010 DATA N RW-- 01 ABS Register Bank No. 08
00000210-0000021F 00000010 DATA N RW-- 01 ABS Register Bank No. 09
00000220-0000022F 00000010 DATA N RW-- 01 ABS Register Bank No. 10
00000230-0000023F 00000010 DATA N RW-- 01 ABS Register Bank No. 11
00000240-0000024F 00000010 DATA N RW-- 01 ABS Register Bank No. 12
00000250-0000025F 00000010 DATA N RW-- 01 ABS Register Bank No. 13
00000260-0000026F 00000010 DATA N RW-- 01 ABS Register Bank No. 14
00000270-0000027F 00000010 DATA N RW-- 01 ABS Register Bank No. 15
00000280-0000028F 00000010 DATA N RW-- 01 ABS Register Bank No. 16
00000290-00003E04 00003B75 DATA P RW-- 02 REL DATA
00003E06-00003E46 00000041 DATA P RW-- 02 REL INIT
00004500-000050FF 00000C00 STACK P RW-- 02 REL SSTACK
00007900-00007FFF 00000700 DATA N RW-- 00 ABS IOXTND
00F80000-00F8371A 0000371B CODE P R-XI 01 REL CODE_upc3
00F8371B-00F83C07 000004ED CODE P R-XI 01 REL CODE_init
00F83C08-00F83DDC 000001D5 CODE P R-XI 01 REL CODE_m_isol
00F83DDD-00F892BF 000054E3 CODE P R-XI 01 REL CODE_maschine
00F892C0-00F8934E 0000008F CODE P R-XI 01 REL CODE_START
00F8934F-00F8A8B4 00001566 CODE P R-XI 01 REL CODE_bts
00F8A8B5-00F8B03B 00000787 CODE P R-XI 01 REL CODE_candev
00F8B03C-00F8D094 00002059 CODE P R-XI 01 REL CODE_cfg
00F8D095-00F8D5A8 00000514 CODE P R-XI 01 REL CODE_combits
00F8D5A9-00F8DFF6 00000A4E CODE P R-XI 01 REL CODE_driver
00F8DFF7-00F8FE71 00001E7B CODE P R-XI 01 REL CODE_funktion
00F8FE72-00F8FFA6 00000135 CODE P R-XI 01 REL CODE_textf
00F8FFA8-00F8FFB2 0000000B CONST P R--I 02 REL VERSIONS
00F8FFB4-00F8FFF4 00000041 CONST P R--I 02 REL DCONST
00F8FFF6-........ 00000000 CONST P R--I 02 REL LIBDCONST
00F8FFF6-........ 00000000 CONST P R--I 02 REL CONST2
00F8FFF6-00F8FFF6 00000001 CONST P R--I 02 REL CONST_var
00F8FFF8-........ 00000000 DIRC P R--I 02 REL DIRCONST
00F90000-00F91499 0000149A CODE P R-XI 01 REL CODE_kpzcalc
00F9149A-00F91A58 000005BF CODE P R-XI 01 REL CODE_lcd
00F91A59-00F93DE5 0000238D CODE P R-XI 01 REL CODE_m_batt
00F93DE6-00F94FD5 000011F0 CODE P R-XI 01 REL CODE_m_cankon
00F94FD6-00F951B0 000001DB CODE P R-XI 01 REL CODE_m_diginp
00F951B1-00F9552D 0000037D CODE P R-XI 01 REL CODE_m_events
00F9552E-00F959D7 000004AA CODE P R-XI 01 REL CODE_m_lvdpld
00F959D8-00F960F5 0000071E CODE P R-XI 01 REL CODE_m_netz
00F960F6-00F96441 0000034C CODE P R-XI 01 REL CODE_m_rec
00F96442-00F976DC 0000129B CODE P R-XI 01 REL CODE_m_schw
00F976DD-00F98851 00001175 CODE P R-XI 01 REL CODE_modem
00F98852-00F9939D 00000B4C CODE P R-XI 01 REL CODE_prep
00F9939E-00F99E33 00000A96 CODE P R-XI 01 REL CODE_regis
00F99E34-00F9E317 000044E4 CODE P R-XI 01 REL CODE_regler
00F9E318-00F9F09A 00000D83 CODE P R-XI 01 REL CODE_req
00F9F09B-00F9FC17 00000B7D CODE P R-XI 01 REL CODE_uart
00F9FC18-00F9FD79 00000162 CODE P R-XI 01 REL CODE_mesz
00F9FD7A-00F9FF1A 000001A1 CODE P R-XI 01 REL CODE_m_hwinit
00F9FF1C-00F9FF2D 00000012 CONST P R--I 02 REL DCLEAR
00F9FF2E-00F9FF5F 00000032 CONST P R--I 02 REL DTRANS
00FA0000-00FA784B 0000784C CODE P R-XI 01 REL CODE_atp
00FA784C-00FA822E 000009E3 CODE P R-XI 01 REL CODE_calib
00FA822F-00FAC328 000040FA CODE P R-XI 01 REL CODE_menu
00FAC329-00FAD0A5 00000D7D CODE P R-XI 01 REL CODE_cachefl
00FAD0A6-00FAD5C4 0000051F CODE P R-XI 01 REL CODE_key
00FAD5C5-00FAF15C 00001B98 CODE P R-XI 01 REL CODE_auth
00FAF15D-00FAFBE5 00000A89 CODE P R-XI 01 REL CODE_m_can
00FAFBE6-00FAFDC5 000001E0 CODE P R-XI 01 REL CODE_rdp
00FB0000-00FB1E81 00001E82 CODE P R-XI 01 REL CODE_can
00FB1E82-00FB245C 000005DB CODE P R-XI 01 REL CODE_rd1
00FB245D-00FB3105 00000CA9 CODE P R-XI 01 REL LIBCODE
00FB3106-00FB6AC0 000039BB CONST P R--I 02 REL CONST_mesdata
00FB6AC2-00FBD47D 000069BC CONST P R--I 02 REL CONST_texte
00FBD47E-00FBE9BD 00001540 CONST P R--I 02 REL CONST_lcd
00FC0000-00FCE32F 0000E330 CONST P R--I 02 REL CONST_cfgdata
00FD0000-00FDA4A1 0000A4A2 CONST P R--I 02 REL CONST_t2h10000
00FF0000-00FF7FF3 00007FF4 CONST P R--I 02 REL CONST_t1h8000
00FF8000-00FFD2CB 000052CC CONST P R--I 02 REL CONST
00FFFC00-00FFFFFF 00000400 CONST N R--I 00 ABS INTVECT
=====================================================================
Post by Stefan Reuther
Post by Helmut Schellong
Ich meine natürlich Plattformen mit 'großem' Betriebssytem.
Und ich schloß Vergangenes aus, und sprach bereits
von 64bit-Pointern (flat).
Um es anders zu sagen: du drehst dich solange im Kreis und änderst die
Rahmenbedingungen solange, bis deine Aussage passt.
Nein, gar nicht.
Ich hatte _zuvor_ geschrieben/definiert:
---------------------------------------------------------------------
Die reale Praxis (64bit-Pointer) deutet stark darauf hin, daß
Pointer aus der selben Speicherklasse jeweils aus einem
linearen Feld stammen.

Bezogen auf _die nicht vergangene_ reale Praxis meine ich, daß
wohl niemand eine konkrete Hardware nennen kann, bei der
mehrere C-Objekte, die alle außerhalb von Funktionen
folgendermaßen definiert wurden:
'const int arrayname[n]; //Beispiel',
nicht alle innerhalb eines linearen Speicherbereiches liegen,
wobei die Arrays nur einen kleinen Bruchteil des
verfügbaren Arbeitsspeichers benötigen.
---------------------------------------------------------------------
Ich nannte es nicht explizit, aber es geht daraus hervor, daß
ich Plattformen _mit_ Betriebssytem meine.
Post by Stefan Reuther
Stehen die Rahmenbedingungen wenigstens dokumentiert in der Nähe des
Quellcodes?
Das erübrigt sich bei Mikrocontroller-Plattformen ohne Betriebssytem.
Funktionen wie inside_arr() habe ich in Produktionscode
bisher nicht verwendet bzw. verwenden müssen.
Post by Stefan Reuther
Post by Helmut Schellong
Post by Stefan Reuther
Post by Helmut Schellong
int inside_arr(void *base, size_t sz, void *test)
{
if ((uintptr_t)test >= (uintptr_t)base &&
(uintptr_t)test < (uintptr_t)base+sz) return 1;
return 0;
}
Das muß funktionieren, portabel und konform.
Eben nicht auf i286.
Das kann eine Täuschung sein!
Die Segment-Register wurden hinsichtlich ihrer Verwendung
starr festgelegt.
cs ds ss es : Code- Daten- Stack- Extra-Segment
Der Compiler hatte gleich definierte Objekte _nicht_ beliebig
auf diverse Segmente verteilt.
In den Speichermodellen mit mehreren Datensegmenten (huge, large,
compact) schon.
Ich erwähnte bereits, daß ich far-Pointer vor jedem Zugriff
normierte, und so einen linearen Bereich von etwa 640 KB
damals unter DOS nutzen konnte.
Aber das ist Vergangenheit, heute und in Zukunft hat man
mindestens 64bit flat.
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Andreas Burmester
2018-08-13 17:47:31 UTC
Permalink
Post by Helmut Schellong
int inside_arr(void *base, size_t sz, void *test)
{
if ((uintptr_t)test >= (uintptr_t)base &&
(uintptr_t)test < (uintptr_t)base+sz) return 1;
return 0;
}
Das muß funktionieren, portabel und konform.
Selbst wenn man die berechtigten konkreten Einwände und das mehrfache
UB per Sprachdefinition beiseite und sich auf all die nachgeschobenen
Einschränkungen einlässt: Nein, das ist nicht "portabel und konform",
vor allem funktioniert das nicht, das allerdings schon portabel - weil:

char ca[2];
inside_arr(ca,sizeof(ca),&ca[2])); // sollte 0 ergeben, liefert
wahrscheinlich aber 1

double da[2];
inside_arr(da,sizeof(da),&da[1])); // sollte 1 ergeben, liefert
wahrscheinlich aber 0

Wenn schon, dann:

int inside_arr(void *base, size_t sz, void *test) {
return test >= base && (char *)test < (char *)base + sz;
}

b.
Andreas Burmester
2018-08-14 03:29:18 UTC
Permalink
Post by Helmut Schellong
int inside_arr(void *base, size_t sz, void *test)
{
if ((uintptr_t)test >= (uintptr_t)base &&
(uintptr_t)test < (uintptr_t)base+sz) return 1;
return 0;
}
Das muß funktionieren, portabel und konform.
Selbst wenn man die berechtigten konkreten Einwände und das mehrfache
UB per Sprachdefinition beiseite und sich auf all die nachgeschobenen
Einschränkungen einlässt: Nein, das ist nicht "portabel und konform",
vor allem funktioniert das nicht, das allerdings schon portabel - weil:

char ca[2];
inside_arr(ca,sizeof(ca),&ca[2]); // sollte 0 ergeben, liefert
wahrscheinlich aber 1

double da[2];
inside_arr(da,sizeof(da),&da[1]); // sollte 1 ergeben, liefert
wahrscheinlich aber 0

Wenn schon, dann:

int inside_arr(void *base, size_t sz, void *test) {
return test >= base && (char *)test < (char *)base + sz;
}

b.
Helmut Schellong
2018-08-14 11:29:43 UTC
Permalink
Post by Andreas Burmester
Post by Helmut Schellong
int inside_arr(void *base, size_t sz, void *test)
{
if ((uintptr_t)test >= (uintptr_t)base &&
(uintptr_t)test < (uintptr_t)base+sz) return 1;
return 0;
}
Das muß funktionieren, portabel und konform.
Selbst wenn man die berechtigten konkreten Einwände und das mehrfache
UB per Sprachdefinition beiseite und sich auf all die nachgeschobenen
Einschränkungen einlässt: Nein, das ist nicht "portabel und konform",
Ich habe keine Einwände zu meiner Funktion inside_arr() gesehen.

Ich habe keine weitergehende Definition dieser Funktion
per Text vorgenommen.

Welches mehrfache UB?

Welche nachgeschobenen Einschränkungen?
Das waren zuvor genannte Einschränkungen!
Post by Andreas Burmester
char ca[2];
inside_arr(ca,sizeof(ca),&ca[2]); // sollte 0 ergeben, liefert
wahrscheinlich aber 1
double da[2];
inside_arr(da,sizeof(da),&da[1]); // sollte 1 ergeben, liefert
wahrscheinlich aber 0
============================================================================
# include <stdio.h>
# include <inttypes.h>

char ca[2];
double da[2];

int inside_arr(void *base, size_t sz, void *test)
{
int i=0;
if ((uintptr_t)test >= (uintptr_t)base &&
(uintptr_t)test < (uintptr_t)base+sz) i=1;
printf("%lu %lu s=%zu r=%d\n", (uintptr_t)base, (uintptr_t)test, sz, i);
return i;
}

int main(int C, char *A[])
{
if (A[1][0]=='c') return inside_arr(ca, sizeof(ca), &ca[2]);
if (A[1][0]=='d') return inside_arr(da, sizeof(da), &da[1]);
return 0;
}
.......................................................................
413] ./a.out c
6786976 6786978 s=2 r=0
414] ./a.out d
6786960 6786968 s=16 r=1
============================================================================

// sollte 0 ergeben, und liefert auch 0
// sollte 1 ergeben, und liefert auch 1
Na also; und dies gewiß nicht zufällig.
Das Speicher-Layout ist auch erkennbar.
Post by Andreas Burmester
int inside_arr(void *base, size_t sz, void *test) {
return test >= base && (char *)test < (char *)base + sz;
}
b.
Vorstehende Funktion liefert laut Standard UB, wenn Pointer
aus unterschiedlichen Objekten verglichen werden.
Im Unterschied zu meiner Funktion.
--
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
2018-08-14 14:18:53 UTC
Permalink
Post by Andreas Burmester
Post by Helmut Schellong
int inside_arr(void *base, size_t sz, void *test)
{
if ((uintptr_t)test >= (uintptr_t)base &&
(uintptr_t)test < (uintptr_t)base+sz) return 1;
return 0;
}
Das muß funktionieren, portabel und konform.
Selbst wenn man die berechtigten konkreten Einwände und das mehrfache
UB per Sprachdefinition beiseite und sich auf all die nachgeschobenen
Was bitte ist hier 'undefiniertes Verhalten'?
Post by Andreas Burmester
Nein, das ist nicht "portabel und konform",
char ca[2];
inside_arr(ca,sizeof(ca),&ca[2]); // sollte 0 ergeben, liefert
wahrscheinlich aber 1
Warum sollte das 'wahrscheinlich 1' ergeben?

ca + 2 < ca + 2

ist falsch.
Post by Andreas Burmester
double da[2];
inside_arr(da,sizeof(da),&da[1]); // sollte 1 ergeben, liefert
wahrscheinlich aber 0
Warum sollte das 0 ergeben?

da + 1 >= da

ist wahr.

da + sizeof(double) < da + sizeof(double) * 2

ist ebenfalls wahr.
Post by Andreas Burmester
int inside_arr(void *base, size_t sz, void *test) {
return test >= base && (char *)test < (char *)base + sz;
}
Das hat nun allerdings undefiniertes Verhalten ausser der Zeiger ist im
Feld (und dann kann man sich den Test sparen).
Helmut Schellong
2018-08-16 10:50:58 UTC
Permalink
Post by Rainer Weikusat
Post by Andreas Burmester
Post by Helmut Schellong
int inside_arr(void *base, size_t sz, void *test)
{
if ((uintptr_t)test >= (uintptr_t)base &&
(uintptr_t)test < (uintptr_t)base+sz) return 1;
return 0;
}
Das muß funktionieren, portabel und konform.
Selbst wenn man die berechtigten konkreten Einwände und das mehrfache
UB per Sprachdefinition beiseite und sich auf all die nachgeschobenen
Was bitte ist hier 'undefiniertes Verhalten'?
Irgendeinen Grund muß ja diese UB-Behauptung gehabt haben:
"... das mehrfache UB per Sprachdefinition ..."

Vielleicht hat er eine falsche Vorstellung von
'uintptr_t' gehabt.

Das ist ein _simpler_ Cast (LP64, LLP64):

typedef unsigned long uintptr_t;
oder
typedef unsigned long long uintptr_t;

7.20.1.4 Integer types capable of holding object pointers
The following type designates an unsigned integer type
with the property that any valid pointer to void can be
converted to this type, then converted back to pointer to void,
and the result will compare equal to the original pointer:
uintptr_t
These types are optional.
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Оlе Ѕtrеісhеr
2018-08-16 12:08:02 UTC
Permalink
Post by Helmut Schellong
7.20.1.4 Integer types capable of holding object pointers
The following type designates an unsigned integer type
with the property that any valid pointer to void can be
converted to this type, then converted back to pointer to void,
uintptr_t
These types are optional.
Ordnungsrelationen zwischen Pointern bleiben aber nicht notwendigerweise
bei der Umwandlung in uintptr_t erhalten. Es ist also
nicht gesagt, dass aus

a < b

(beides void*) folgt, dass

(uintptr_t)a < (uintptr_t)b


es gilt nicht mal notwendigerweise, dass

(uintptr_t)a + 1 == (uintptr_t)(a + 1)

Schöne Grüße

Ole
Rainer Weikusat
2018-08-16 13:58:26 UTC
Permalink
Post by Оlе Ѕtrеісhеr
Post by Helmut Schellong
7.20.1.4 Integer types capable of holding object pointers
The following type designates an unsigned integer type
with the property that any valid pointer to void can be
converted to this type, then converted back to pointer to void,
uintptr_t
These types are optional.
Ordnungsrelationen zwischen Pointern bleiben aber nicht notwendigerweise
bei der Umwandlung in uintptr_t erhalten.
Natuerlich nicht. Da aber Zeiger, die nicht in dasselbe Feld zeigen,
ueberhaupt nicht in dieser Weise verglichen werden duerfen (insofern es
die C-Norm angeht) ist das ziemlich belanglos:

Entweder man hat es mit einer Umbegung zu tun, in der solche Vergleiche
erlaubt sind und es eine bijektive Abbildung von Zeiger nach
vorzeichenlosen Zahlen gibt. Dann kann man (wie bereits mehrfache
geschrieben) ausdruecklich undefiniertes Verhalten durch den
Zahlenvergleich vermeiden.

Um nichts anderes geht es hier.
Оlе Ѕtrеісhеr
2018-08-16 18:42:19 UTC
Permalink
Post by Rainer Weikusat
Post by Оlе Ѕtrеісhеr
Post by Helmut Schellong
7.20.1.4 Integer types capable of holding object pointers
The following type designates an unsigned integer type
with the property that any valid pointer to void can be
converted to this type, then converted back to pointer to void,
uintptr_t
These types are optional.
Ordnungsrelationen zwischen Pointern bleiben aber nicht notwendigerweise
bei der Umwandlung in uintptr_t erhalten.
Natuerlich nicht. Da aber Zeiger, die nicht in dasselbe Feld zeigen,
ueberhaupt nicht in dieser Weise verglichen werden duerfen (insofern es
Entweder man hat es mit einer Umbegung zu tun, in der solche Vergleiche
erlaubt sind und es eine bijektive Abbildung von Zeiger nach
vorzeichenlosen Zahlen gibt. Dann kann man (wie bereits mehrfache
geschrieben) ausdruecklich undefiniertes Verhalten durch den
Zahlenvergleich vermeiden.
Eine mögliche bijektive Abbildung wäre z.B., bei segmentierter Adresse
die Segmente in die unteren und die eigentliche Adresse in die oberen
Bits eines entsprechend langen unsigned ints zu packen.

Das wäre vollkommen standardkonform, würde einen entsprechenden Typ zur
Verfügung stellen und die Funktion liefert trotzdem falsche Werte.

Oder sie könnte in ein paar unverwendete Bits eine Prüfsumme packen (mit
deren Hilfe sichergestellt wird, dass nur valide uintptr_t-Werte in
Adressen gewandelt werden). Dann könnte sogar die Ordnungsrelation
versagen.

Das ist zugegeben etwas exotisch -- der Compiler darf aber durchaus so
optimieren, dass er bei undefiniertem Verhalten "irgendwas" macht,
selbst auf "normalen" Plattformen. Und der Vergleich

(uintptr_t)a < (uintptr_t)b

ist für a!=b eben undefiniert.

Ich wäre mir nichtmal sicher, ob es für a==b definiert ist: es ist ja
nicht festgelegt, dass der Cast eindeutig ist, dass also notwendig

(uintptr_t)a == (uintptr_t)a

gilt. Es ist nur definiert, dass

(void *)(uintptr_t)a == (void *)(uintptr_t)a

gelten muss.

Schöne Grüße

Ole
Helmut Schellong
2018-08-16 21:35:36 UTC
Permalink
[...]
Post by Оlе Ѕtrеісhеr
Das ist zugegeben etwas exotisch -- der Compiler darf aber durchaus so
optimieren, dass er bei undefiniertem Verhalten "irgendwas" macht,
selbst auf "normalen" Plattformen. Und der Vergleich
(uintptr_t)a < (uintptr_t)b
ist für a!=b eben undefiniert.
Ich wäre mir nichtmal sicher, ob es für a==b definiert ist: es ist ja
nicht festgelegt, dass der Cast eindeutig ist, dass also notwendig
(uintptr_t)a == (uintptr_t)a
gilt. Es ist nur definiert, dass
(void *)(uintptr_t)a == (void *)(uintptr_t)a
gelten muss.
(uintptr_t)(void*)a == (uintptr_t)(void*)a

Aufgefallen?:
Bei meiner Funktion inside_arr() sind zwei Parameter 'void*'.
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Juergen Ilse
2018-08-17 10:43:56 UTC
Permalink
Hallo,
Post by Helmut Schellong
Post by Оlе Ѕtrеісhеr
Ich wäre mir nichtmal sicher, ob es für a==b definiert ist: es ist ja
nicht festgelegt, dass der Cast eindeutig ist, dass also notwendig
(uintptr_t)a == (uintptr_t)a
gilt. Es ist nur definiert, dass
(void *)(uintptr_t)a == (void *)(uintptr_t)a
gelten muss.
(uintptr_t)(void*)a == (uintptr_t)(void*)a
Bei meiner Funktion inside_arr() sind zwei Parameter 'void*'.
Das aendert aber nichts an Oles (berechtigtem) Einwand.
Es waere fuer eine standardkonforme Implementierung erlaubt, dass
(uintptr_t)(void*)a nicht immer den selben Wert ergibt, solange fuer
alle moeglichen Werte fuer (uintptr_t)(void*)a bei der Rueckkonvertierung
nach (void*) wieder (void*)a herauskommt. Es waere zugegebenermassen eine
ziemlich schraege Implementierung, aber trotzdem standardkonform. Dein
Konstrukt mag zwar oft funktionieren, aber "portabel" im Sinne von
"funktioniert mit jeder standardkonformen C-Implementierung" ist es
IMHO bei weitem nicht ...

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
Helmut Schellong
2018-08-17 22:44:06 UTC
Permalink
Post by Juergen Ilse
Hallo,
Post by Helmut Schellong
Post by Оlе Ѕtrеісhеr
Ich wäre mir nichtmal sicher, ob es für a==b definiert ist: es ist ja
nicht festgelegt, dass der Cast eindeutig ist, dass also notwendig
(uintptr_t)a == (uintptr_t)a
gilt. Es ist nur definiert, dass
(void *)(uintptr_t)a == (void *)(uintptr_t)a
gelten muss.
(uintptr_t)(void*)a == (uintptr_t)(void*)a
Bei meiner Funktion inside_arr() sind zwei Parameter 'void*'.
Das aendert aber nichts an Oles (berechtigtem) Einwand.
Es waere fuer eine standardkonforme Implementierung erlaubt, dass
(uintptr_t)(void*)a nicht immer den selben Wert ergibt, solange fuer
alle moeglichen Werte fuer (uintptr_t)(void*)a bei der Rueckkonvertierung
nach (void*) wieder (void*)a herauskommt. Es waere zugegebenermassen eine
ziemlich schraege Implementierung, aber trotzdem standardkonform.
Eine solche Implementation wäre irre, einfach grober Unfug.
Ich lehne es einfach ab, mir so etwas theoretisch auszudenken.
Der Standard definiert uintptr_t nicht für geisteskranke Implementatoren.
Ich lehne es auch ab, den Standard so zu deuten, indem ich mir
einfach alles Mögliche, das ohne Sinn und Verstand ist, ausdenke.
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Juergen Ilse
2018-08-18 02:04:17 UTC
Permalink
Hallo,
Post by Helmut Schellong
Eine solche Implementation wäre irre, einfach grober Unfug.
Aber standardkonform.
Post by Helmut Schellong
Ich lehne es einfach ab, mir so etwas theoretisch auszudenken.
Dann schreib hier nicht ueber Portabilitaet.
Post by Helmut Schellong
Ich lehne es auch ab, den Standard so zu deuten,
Den Standard muss man nicht deuten sondern nur lesen. Und alles, was nicht
ausdruecklich im Standard festgelegt ist auch nicht als gegeben annehmen.
Du ionterpretierst zuviel in den Standard hinein, wenn du implizite An-
nahmen machst, die dir der Wortlaut des Standards in keinster Art und Weise
garantiert.

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
Helmut Schellong
2018-08-18 11:36:45 UTC
Permalink
Post by Juergen Ilse
Hallo,
Post by Helmut Schellong
Eine solche Implementation wäre irre, einfach grober Unfug.
Aber standardkonform.
Post by Helmut Schellong
Ich lehne es einfach ab, mir so etwas theoretisch auszudenken.
Dann schreib hier nicht ueber Portabilitaet.
Post by Helmut Schellong
Ich lehne es auch ab, den Standard so zu deuten,
Den Standard muss man nicht deuten sondern nur lesen. Und alles, was nicht
ausdruecklich im Standard festgelegt ist auch nicht als gegeben annehmen.
Genau richtig.
Viele Leute nehmen aber Dinge an, die der Standard weder nennt
noch festlegt.
Zum Beispiel, daß zwischen Array-Elementen Zwischenraum sein kann.
Warum?
Weil der Standard nicht explizit schreibt, daß zwischen Elementen eines
Arrays kein Zwischenraum sein darf.

sizeof(array) == sizeof(element_typ) * element_anzahl

Allein vorstehende Standard-Definition widerlegt Zwischenraum!
Post by Juergen Ilse
Du ionterpretierst zuviel in den Standard hinein, wenn du implizite An-
nahmen machst, die dir der Wortlaut des Standards in keinster Art und Weise
garantiert.
Das sehe ich nicht.
----------------------------------------------------------------------------
This International Standard is designed to promote the portability
of C programs among a variety of data-processing systems.
It is intended for use by implementors and programmers.
----------------------------------------------------------------------------
U.a. danach richte ich mich.

7.20 — integer types wide enough to hold pointers to objects;
7.20.1.4 Integer types capable of holding object pointers

Ist vorstehend "holding object pointers" aufgefallen?
Genau das soll die Wirkung der Definitionen in dieser
Subsektion sein.
Der Standard will, daß wenn der Typ uintptr_t zur Verfügung
steht, ein sinnvolles Mapping damit möglich ist.
Der Standard definierte dies, weil Pointer-Vergleiche
UB bewirken können.
Wenn das nicht so wäre, wäre diese Definition überflüssig.

Der Standard verlangt, daß uintptr_t ein 'typedef'-name ist.
Das ist ein Synonym einer Typ-Definition per 'typedef'.

(uintptr_t)(void*)a

Daß vorstehender Ausdruck aufeinanderfolgend verschiedene
Werte liefert, bei unverändertem 'a', ist ganz sicher nicht
die Erwartung des Standards.
So etwas wird auch nicht möglich sein, im Rahmen einer
'typedef'-Vereinbarung!
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
David Seppi
2018-08-18 12:15:55 UTC
Permalink
Post by Helmut Schellong
sizeof(array) == sizeof(element_typ) * element_anzahl
Allein vorstehende Standard-Definition widerlegt Zwischenraum!
Wo wird hier Zwischenraum widerlegt? Zwischenraum macht das Array
ja nicht größer.
--
David Seppi
1220 Wien
Helmut Schellong
2018-08-18 14:36:34 UTC
Permalink
Post by David Seppi
Post by Helmut Schellong
sizeof(array) == sizeof(element_typ) * element_anzahl
Allein vorstehende Standard-Definition widerlegt Zwischenraum!
Wo wird hier Zwischenraum widerlegt? Zwischenraum macht das Array
ja nicht größer.
Selbstverständlich macht Zwischenraum ein Array größer!

Englisch wird das Padding und Gap genannt.

sizeof(struct x) liefert die Größe in Byte
inklusive aller Zwischenräume (Padding).
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Juergen Ilse
2018-08-18 12:22:58 UTC
Permalink
Hallo,
Post by Helmut Schellong
Post by Juergen Ilse
Post by Helmut Schellong
Ich lehne es einfach ab, mir so etwas theoretisch auszudenken.
Ich lehne es auch ab, den Standard so zu deuten,
Den Standard muss man nicht deuten sondern nur lesen. Und alles, was nicht
ausdruecklich im Standard festgelegt ist auch nicht als gegeben annehmen.
Genau richtig.
Viele Leute nehmen aber Dinge an, die der Standard weder nennt
noch festlegt.
Ja, du zum Beispiel, wenn du zwingend vorraussetzen willst, dass fuer 2
vergleichbare Pointer ptr1 und ptr2 mit ptr1 < ptr2 denn auch
(uintptr_t)ptr1<(uintptr_t)ptr2 gelten muesse. Das verlangt der Standard
mit keinem Wort. Wie Oli anmerkte, gilt noch nicht einmal zwingend
(uintptr_t)ptr1==(unintptr_t)ptr1, wohl aber
(void*)(uintptr_t)ptr1==(void*)(uintptr_t)ptr1.
Post by Helmut Schellong
Zum Beispiel, daß zwischen Array-Elementen Zwischenraum sein kann.
Nein. Aber aufgrund von alignment-Anforderungen kann es in den array-
Elementen selbst padding geben (auch am Ende, um das korrekte alignment
des naechsten array-Elements zu ermoeglichen, das padding gehoert dann
aber zum array-Element und wird bei der Groesse eines array-Elements
mitgezaehlt).
Post by Helmut Schellong
Post by Juergen Ilse
Du ionterpretierst zuviel in den Standard hinein, wenn du implizite An-
nahmen machst, die dir der Wortlaut des Standards in keinster Art und Weise
garantiert.
Das sehe ich nicht.
Das weiss ich, deswegen versuche ich dir deinen Irrtum ja zu erklaeren.
Post by Helmut Schellong
7.20 — integer types wide enough to hold pointers to objects;
7.20.1.4 Integer types capable of holding object pointers
Ist vorstehend "holding object pointers" aufgefallen?
Ja. Daraus folgt aber nicht, dass es bei der Konvertierung von pointer zu
einem solchen integer-Typ keine Veraenderungen am Bitmuster geben darf.
Post by Helmut Schellong
Genau das soll die Wirkung der Definitionen in dieser
Subsektion sein.
Du interpretierst dort mehr hinein, als da wirklich drin steht.
Post by Helmut Schellong
Der Standard will, daß wenn der Typ uintptr_t zur Verfügung
steht, ein sinnvolles Mapping damit möglich ist.
Er verlangt aber nicht all die Eigenschaften dieses "Mappings", die du
davon erwartest.
Post by Helmut Schellong
Der Standard definierte dies, weil Pointer-Vergleiche
UB bewirken können.
Wenn das nicht so wäre, wäre diese Definition überflüssig.
In der Tat kann man darueber streiten, inwiefern dieses Mapping (abgesehen
von zusaetzlich von bestimmten Implementierungen ueber den Standard hinaus-
gehende Garantien von Eigenschaftendieses Mappings) sinnvoll ist.
Post by Helmut Schellong
Der Standard verlangt, daß uintptr_t ein 'typedef'-name ist.
Das ist ein Synonym einer Typ-Definition per 'typedef'.
(uintptr_t)(void*)a
Das spielt doch fuer die Argumentation gar keine Rolle.
Post by Helmut Schellong
Daß vorstehender Ausdruck aufeinanderfolgend verschiedene
Werte liefert, bei unverändertem 'a', ist ganz sicher nicht
die Erwartung des Standards.
Solange die Konvertierung nach uintptr_t un zurueck zum pointer garantiert
wieder den selben Wert liefert, verbietet der Standard das aber auch nicht.
Der Standard fordert nirgends, dass das Mapping zwingend bijektiv sein
muesste ...
Post by Helmut Schellong
So etwas wird auch nicht möglich sein, im Rahmen einer
'typedef'-Vereinbarung!
Warum nicht? Was soll genau die Eigenschaft "ist ein Typ" mit den moeglichen
Werten dieses Typs und wie sie erzeugt werden koennen zu tun haben?

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
Helmut Schellong
2018-08-18 16:22:49 UTC
Permalink
Post by Juergen Ilse
Hallo,
Post by Helmut Schellong
Post by Juergen Ilse
Post by Helmut Schellong
Ich lehne es einfach ab, mir so etwas theoretisch auszudenken.
Ich lehne es auch ab, den Standard so zu deuten,
Den Standard muss man nicht deuten sondern nur lesen. Und alles, was nicht
ausdruecklich im Standard festgelegt ist auch nicht als gegeben annehmen.
Genau richtig.
Viele Leute nehmen aber Dinge an, die der Standard weder nennt
noch festlegt.
Ja, du zum Beispiel, wenn du zwingend vorraussetzen willst, dass fuer 2
vergleichbare Pointer ptr1 und ptr2 mit ptr1 < ptr2 denn auch
(uintptr_t)ptr1<(uintptr_t)ptr2 gelten muesse. Das verlangt der Standard
mit keinem Wort.
Weil das selbstverständlich ist.
Andernfalls sollte eine Implementation uintptr_t nicht definieren.
Weil unbrauchbar.
Ich schätze, falls die Autoren des Standards all diese Einwände
hörten, würden sie sagen: "Oh, diese Deutschen!", und nachfolgend
eine Erweiterung des Absatzes vormerken, für C20.
Oder sie sagen: "Ach, laßt die Deutschen doch hadern ...".

Ich muß mich wiederholen:
Meine Funktion inside_arr() enthält 'void*' vor (uintptr_t).
Post by Juergen Ilse
Wie Oli anmerkte, gilt noch nicht einmal zwingend
(uintptr_t)ptr1==(unintptr_t)ptr1, wohl aber
(void*)(uintptr_t)ptr1==(void*)(uintptr_t)ptr1.
Ich hatte das schon mal korrigiert.
Und wieder kommt es falsch.
(uintptr_t)(void*)ptr1==(uintptr_t)(void*)ptr1
Wie vorstehend ist es korrekt.
Und in meiner Funktion ist es korrekt:
----------------------------------------------------------------
int inside_arr(void *base, size_t sz, void *test)
{
if ((uintptr_t)test >= (uintptr_t)base &&
(uintptr_t)test < (uintptr_t)base+sz) return 1;
return 0;
}
----------------------------------------------------------------
Post by Juergen Ilse
Post by Helmut Schellong
7.20 — integer types wide enough to hold pointers to objects;
7.20.1.4 Integer types capable of holding object pointers
Ist vorstehend "holding object pointers" aufgefallen?
Ja. Daraus folgt aber nicht, dass es bei der Konvertierung von pointer zu
einem solchen integer-Typ keine Veraenderungen am Bitmuster geben darf.
An anderer Stelle hier habe ich bereits geschrieben, daß eine
andere Bit-Repräsentation möglich ist.
Werte müssen aber transportiert werden.
Post by Juergen Ilse
Post by Helmut Schellong
Genau das soll die Wirkung der Definitionen in dieser
Subsektion sein.
Du interpretierst dort mehr hinein, als da wirklich drin steht.
Ja? Will der Standard keine Wirkung haben?
Soll nur das:
v1=(uintptr_t)(void*)p1;
p1=(int*)(void*)v1;
und wirklich nur das programmiert werden, weil nur das
explizit definiert wurde?
Absurder, sinnloser Kram!
Post by Juergen Ilse
Post by Helmut Schellong
Der Standard will, daß wenn der Typ uintptr_t zur Verfügung
steht, ein sinnvolles Mapping damit möglich ist.
Er verlangt aber nicht all die Eigenschaften dieses "Mappings", die du
davon erwartest.
Muß er das explizit verlangen?
Der Standard verlangt immerhin, daß ein Objekt-Pointer
von einem Integer gehalten werden kann.
Post by Juergen Ilse
Post by Helmut Schellong
Der Standard definierte dies, weil Pointer-Vergleiche
UB bewirken können.
Wenn das nicht so wäre, wäre diese Definition überflüssig.
In der Tat kann man darueber streiten, inwiefern dieses Mapping (abgesehen
von zusaetzlich von bestimmten Implementierungen ueber den Standard hinaus-
gehende Garantien von Eigenschaftendieses Mappings) sinnvoll ist.
Genau das in Klammern:
Meine Implementationen definieren:
typedef unsigned long uintptr_t;
typedef unsigned long long uintptr_t;
Das sind doch sinnvolle, brauchbare Definitionen!
Auch (void*) ist hier entbehrlich.

Auf meinen gängigen Plattformen findet keine Cast-Operation statt!
Wenn der Pointer z.B. im Register rdx ist, wird einfach
mov rax, rdx
gemacht, wobei rax den Typ uintptr_t hält.
Vollkommen gleiche Bit-Repräsentation!
Viele Casts erfordern keine zusätzliche Instruktion!
Post by Juergen Ilse
Post by Helmut Schellong
Der Standard verlangt, daß uintptr_t ein 'typedef'-name ist.
Das ist ein Synonym einer Typ-Definition per 'typedef'.
(uintptr_t)(void*)a
Das spielt doch fuer die Argumentation gar keine Rolle.
[1]
Was kann denn unternommen werden, damit xy:
typedef xy uintptr_t;
bewirkt, daß bei konstantem Pointer-Wert (uintptr_t)ptr
z.B. aufeinanderfolgend unterschiedliche Werte geliefert werden?
Ich sehe da keine Möglichkeit.
Post by Juergen Ilse
Post by Helmut Schellong
Daß vorstehender Ausdruck aufeinanderfolgend verschiedene
Werte liefert, bei unverändertem 'a', ist ganz sicher nicht
die Erwartung des Standards.
Solange die Konvertierung nach uintptr_t un zurueck zum pointer garantiert
wieder den selben Wert liefert, verbietet der Standard das aber auch nicht.
Der Standard fordert nirgends, dass das Mapping zwingend bijektiv sein
muesste ...
Der Standard verbietet aber auch keine Bijektivität.

Da sind wir wieder beim sinnlosen:
v1=(uintptr_t)(void*)p1;
p1=(int*)(void*)v1;
Post by Juergen Ilse
Post by Helmut Schellong
So etwas wird auch nicht möglich sein, im Rahmen einer
'typedef'-Vereinbarung!
Warum nicht? Was soll genau die Eigenschaft "ist ein Typ" mit den moeglichen
Werten dieses Typs und wie sie erzeugt werden koennen zu tun haben?
Siehe [1].
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Claus Reibenstein
2018-08-19 10:46:45 UTC
Permalink
Post by Helmut Schellong
Post by Juergen Ilse
Post by Helmut Schellong
Viele Leute nehmen aber Dinge an, die der Standard weder nennt
noch festlegt.
Ja, du zum Beispiel, wenn du zwingend vorraussetzen willst, dass fuer 2
vergleichbare Pointer ptr1 und ptr2 mit ptr1 < ptr2 denn auch
(uintptr_t)ptr1<(uintptr_t)ptr2 gelten muesse. Das verlangt der Standard
mit keinem Wort.
Weil das selbstverständlich ist.
Warum sollte das selbstverständlich sein?
Post by Helmut Schellong
Andernfalls sollte eine Implementation uintptr_t nicht definieren.
Weil unbrauchbar.
Sehe ich nicht so.
Post by Helmut Schellong
Ich schätze, falls die Autoren des Standards all diese Einwände
hörten, würden sie sagen: "Oh, diese Deutschen!", und nachfolgend
eine Erweiterung des Absatzes vormerken, für C20.
Oder sie sagen: "Ach, laßt die Deutschen doch hadern ...".
Glaube ich nicht. Ich denke eher, sie würden sich freuen, dass endlich
mal jemand den Standard verstanden hat, und gleichzeitig bedauern, dass
_ein_ Deutscher hartnäckig dagegenhält.
Post by Helmut Schellong
Post by Juergen Ilse
Post by Helmut Schellong
Der Standard will, daß wenn der Typ uintptr_t zur Verfügung
steht, ein sinnvolles Mapping damit möglich ist.
Er verlangt aber nicht all die Eigenschaften dieses "Mappings", die du
davon erwartest.
Muß er das explizit verlangen?
Ja. Muss er. Sonst sind sie als nicht gegeben anzunehmen.
Post by Helmut Schellong
Der Standard verlangt immerhin, daß ein Objekt-Pointer
von einem Integer gehalten werden kann.
Wo verlangt er das?
Post by Helmut Schellong
Auf meinen gängigen Plattformen findet keine Cast-Operation statt!
Wenn der Pointer z.B. im Register rdx ist, wird einfach
mov rax, rdx
gemacht, wobei rax den Typ uintptr_t hält.
Vollkommen gleiche Bit-Repräsentation!
Aber eben nur auf Deinen "gängigen Plattformen". Der Standard setzt
solcherlei Plattformen jedoch nicht voraus.

Auf DEC-Rechnern würde eine solche Implementierung z.B. bewirken, dass
bei zwei Pointern p1 und p2, für die p1 > p2 gilt, (uintptr_t) p1 <
(uintptr_t) p2 wäre, da die Arrays "rückwärts" im Speicher abgelegt
werden, die Adressen aufeinanderfolgender Arrayelemente also absteigend
sind. Bei einer 1:1-Übernahme der Adresse beim Cast hätte genau das zur
Folge.
Post by Helmut Schellong
Viele Casts erfordern keine zusätzliche Instruktion!
Irrelevant.

Gruß
Claus
Helmut Schellong
2018-08-19 18:54:47 UTC
Permalink
[...]
Ich habe die Fragen und Kommentare schon einmal
oder mehrmals beantwortet, und möchte das nicht
ein weiteres Mal - sinnlos - tun.
--
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
2018-08-18 17:03:54 UTC
Permalink
Post by Juergen Ilse
Post by Helmut Schellong
Post by Juergen Ilse
Post by Helmut Schellong
Ich lehne es einfach ab, mir so etwas theoretisch auszudenken.
Ich lehne es auch ab, den Standard so zu deuten,
Den Standard muss man nicht deuten sondern nur lesen. Und alles, was nicht
ausdruecklich im Standard festgelegt ist auch nicht als gegeben annehmen.
Genau richtig.
Viele Leute nehmen aber Dinge an, die der Standard weder nennt
noch festlegt.
Ja, du zum Beispiel, wenn du zwingend vorraussetzen willst, dass fuer 2
vergleichbare Pointer ptr1 und ptr2 mit ptr1 < ptr2 denn auch
(uintptr_t)ptr1<(uintptr_t)ptr2 gelten muesse. Das verlangt der Standard
mit keinem Wort.
Es wird lediglich in einer Fussnote erwaehnt, dass dieses beabsichtigt
war ("consistent with the addressing structure of the execution
environment"). Dh falls ptr1 und ptr2 in dasselbe Feld zeigen und ptr1 <
ptr2 gilt, darf man erwarten, das (uintptr_t)ptr1 < (uinptr_t)ptr2
ebenfalls gilt. Ebenso darf man erwarten, dass der Zahlenwert eines
Zeiger, der nichts anderes ist, als ein Index in ein Bytefeld, durch
eine Konvertierung in eine geeignet grossen, vorzeichenlosen
Ganzzahltyp nicht aendert. Die Frage ob eine Implementierung, bei der
das nicht der Fall ist, standardkonform waere ist belanglos. Relevant
ist bloss "gibts die" und "gibts einen Grund, sie in Anwendungscode zu
unterstuetzen" und die Antworten auf diese Fragen lauten "wahrscheinlich
nicht" und "jedenfalls nicht".
Post by Juergen Ilse
Wie Oli anmerkte, gilt noch nicht einmal zwingend
(uintptr_t)ptr1==(unintptr_t)ptr1, wohl aber
(void*)(uintptr_t)ptr1==(void*)(uintptr_t)ptr1.
Dieses Argument kann man einfach umdrehen: Im Normtext steht nicht das

(uintptr_t)ptr1 != (uintptr_t)ptr1

wahr sein muesse. Also ist es falsch. Oder vielleicht mit etwas mehr
Phantasie: "In der C-Norm steht nicht, das die Sonne im Osten aufgeht,
also geht sie vielleicht nicht im Osten auf". Man kann generell in jeden
Text beliebigen Unsinn hineinlesen, in dem man Luecken in der
Darstellung nach Gutduenken ausfuellt.

Richtig ist allerdings: Abzueglich oben erwaehnter Absichtserklaerung
steht darueber nichts in der C-Norm. Also kann man allein auf Basis der
C-Norm keine Aussage darueber machen. Und das ist nicht dasselbe wie
"jede beliebige Aussage".
Оlе Ѕtrеісhеr
2018-08-18 19:40:57 UTC
Permalink
Die Frage ob eine Implementierung, bei der das nicht der Fall ist,
standardkonform waere ist belanglos. Relevant ist bloss "gibts die"
und "gibts einen Grund, sie in Anwendungscode zu unterstuetzen" und
die Antworten auf diese Fragen lauten "wahrscheinlich nicht" und
"jedenfalls nicht".
Die erste Antwort wäre bestenfalls "noch nicht". Ich hatte ja schon das
Beispiel gebracht, dass die Umwandlung in uintptr_t eine Prüfsumme
hinzufügen könnte, und bei der Umwandlung von uintptr_t in Pointer genau
diese Prüfsumme überprüft wird.
Post by Juergen Ilse
Wie Oli anmerkte, gilt noch nicht einmal zwingend
(uintptr_t)ptr1==(unintptr_t)ptr1, wohl aber
(void*)(uintptr_t)ptr1==(void*)(uintptr_t)ptr1.
Dieses Argument kann man einfach umdrehen: Im Normtext steht nicht das
(uintptr_t)ptr1 != (uintptr_t)ptr1
wahr sein muesse. Also ist es falsch.
Also ist es nicht zwingend wahr.
Oder vielleicht mit etwas mehr Phantasie: "In der C-Norm steht nicht,
das die Sonne im Osten aufgeht, also geht sie vielleicht nicht im
Osten auf".
Sie geht auch nicht zwingend im Osten auf, wie jeder weiß, der schonmal
am Südpol überwintert hat.

Wenn Deine Software von solchen Voraussetzungen ausgeht, dann würde ich
ihr nicht vertrauen.
Man kann generell in jeden Text beliebigen Unsinn hineinlesen, in dem
man Luecken in der Darstellung nach Gutduenken ausfuellt.
Das Problem ist, dass sich die Ansicht, was "Unsinn" ist und was nicht,
mit der Zeit ändert. Und damit wird ein Programm, was nicht
standardkonform ist, morgen plötzlich nicht mehr funktionieren, weil der
frühere "Unsinn" heute eine allgemein angewendete Hardening-Prozedur
ist.

Das ist eben genau das, wovor einem der Standard eigentlich bewahren
soll. Und wo ich heute allgemein die Krise bekomme, weil man früher eben
so bestimmte Annahmen getätigt hat ("alles andere ist doch Unsinn"), die
heute einfach nicht mehr stimmen.

Und diese Erfahrung lehrt mich, dass man auch bei Dingen, die einem
"selbstverständlich" erscheinen, vorsichtig sein sollte, wenn sie denn
nicht im Standard stehen.

Zum Beispiel bei der Umwandlung von Pointern in Integers.

Ole
Rainer Weikusat
2018-08-18 22:27:55 UTC
Permalink
ole-usenet-***@gmx.net (Оlе Ѕtrеісhеr) writes:

[...]
Post by Оlе Ѕtrеісhеr
Wenn Deine Software von solchen Voraussetzungen ausgeht, dann würde ich
ihr nicht vertrauen.
Man kann generell in jeden Text beliebigen Unsinn hineinlesen, in dem
man Luecken in der Darstellung nach Gutduenken ausfuellt.
Was fuer Leute Du mit Dir kompatibel haelst, interessiert mich einen
feuchten Kericht.
Rainer Weikusat
2018-08-18 22:29:14 UTC
Permalink
ole-usenet-***@gmx.net (Оlе Ѕtrеісhеr) writes:

[...]
Post by Оlе Ѕtrеісhеr
Wenn Deine Software von solchen Voraussetzungen ausgeht, dann würde ich
ihr nicht vertrauen.
Man kann generell in jeden Text beliebigen Unsinn hineinlesen, in dem
man Luecken in der Darstellung nach Gutduenken ausfuellt.
Was fuer Leute Du fuer mit Dir kompatibel haelst, interessiert mich einen
feuchten Kericht.
Оlе Ѕtrеісhеr
2018-08-19 08:38:04 UTC
Permalink
(Quoting ergänzt)
Post by Rainer Weikusat
Post by Оlе Ѕtrеісhеr
Oder vielleicht mit etwas mehr Phantasie: "In der C-Norm steht nicht,
das die Sonne im Osten aufgeht, also geht sie vielleicht nicht im
Osten auf".
Sie geht auch nicht zwingend im Osten auf, wie jeder weiß, der schonmal
am Südpol überwintert hat.
Wenn Deine Software von solchen Voraussetzungen ausgeht, dann würde ich
ihr nicht vertrauen.
Man kann generell in jeden Text beliebigen Unsinn hineinlesen, in dem
man Luecken in der Darstellung nach Gutduenken ausfuellt.
Was fuer Leute Du fuer mit Dir kompatibel haelst, interessiert mich einen
feuchten Kericht.
Mal abgesehen von Deiner überaus sachlichen Antwort: Man hat tatsächlich
die Freiheit, das was ein Standard nicht festlegt (vulgo "die Lücken")
in der Implementierung frei ("nach Gutdünken") zu wählen. Es bleibt dann
eine standardkonforme Implementierung.

Und wenn ein Programm in einer solchen Implementierung nicht korrekt
läuft, ist es halt nicht standardkonform.

Ole
Helmut Schellong
2018-08-19 09:43:02 UTC
Permalink
Post by Оlе Ѕtrеісhеr
(Quoting ergänzt)
[...]
Post by Оlе Ѕtrеісhеr
Post by Rainer Weikusat
Was fuer Leute Du fuer mit Dir kompatibel haelst, interessiert mich einen
feuchten Kericht.
Mal abgesehen von Deiner überaus sachlichen Antwort: Man hat tatsächlich
die Freiheit, das was ein Standard nicht festlegt (vulgo "die Lücken")
in der Implementierung frei ("nach Gutdünken") zu wählen. Es bleibt dann
eine standardkonforme Implementierung.
Zunächst einmal kann das so 'digital' gesehen werden.
Man darf aber davon ausgehen, daß Implementierer bestrebt sind,
nützliche, brauchbare Mittel zu entwickeln.
Post by Оlе Ѕtrеісhеr
Und wenn ein Programm in einer solchen Implementierung nicht korrekt
läuft, ist es halt nicht standardkonform.
Eine Implementation bietet uintptr_t an, jedoch mit einem Verhalten,
das vollkommen unbrauchbar ist, und dadurch ist diese
Implementation nicht standardkonform.

Ich glaube nicht, daß jemals solch ein Vorgang beobachtbar sein wird.
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Juergen Ilse
2018-08-19 10:51:16 UTC
Permalink
Hallo,
Post by Helmut Schellong
Post by Оlе Ѕtrеісhеr
Und wenn ein Programm in einer solchen Implementierung nicht korrekt
läuft, ist es halt nicht standardkonform.
So ist es. Eines der wohl populaersten Beispiele fuer "nicht standadkonformen
Code" war eine Stelle im Linux Kernel 2.0.35, der nachvollziehbar *nicht*
funktionierte, wenn man ihn mit gcc 2.8 oder neuer (einschliesslich der da-
maligen egcs Versionen) compilierte. Was war passiert? Es gab eine kleine
C-Funktion, die von Assembler aus aufgerufen wurde. Der Assembler-Code hatte
mit "pusha" alle Register-Inhalte (einschliesslich des "Maschinenstatusworts"
auf den stack geschoben. Die C-Funktion hat nun in den uebergebenen Parametern
"herumgepatched", sprich im dort abgelegten Maschinenstatuswort ein Bit fuer
das setzen von IO-privileges gesetzt (wenn ich mich da richtig erinnere).
Ab gcc2.8 war nun der Optizer ausgereift genug, um zu erkennen, dass der
veraenderte Wert nach verlassen der Funktion keine Gueltigkeit mehr hatte
und in der Funktion nach Veraenderung des Wertes nicht mehr verwendet wurde.
Daher hatte der Optimizer dann die gesamte Veraenderung des Wertes "wegopti-
miert", womit die Funktion letztendlich nur ein return enthielt ...
Da sich aber der Assembler-Code auf den veraenderten Parameter auf dem Stack
verlasssen hatte, funktionierte das setzen der IO-privileges im Kernel nicht
mehr -> BUMMM!!! Die erste "Korrektur" im Kernel 2.0.36 war eigentlich genauso
daneben wie der Original Code aus 2.0.35: der veraendernde Zugriff auf den
auf dem Stack liegenden Parameter wurde indirekt ueber Referenzierung eines
Pointers durchgefuehrt, was der Optimizer der damaligen Compiler dann nicht
mehr als "Optimierungsmoeglichkeit" erkannte ... Bei der naechsten deutlichen
Verbesserung des Optimizers haette es dann abermals gescheppert, aber (ich
hatte mir das in spaeteren Kernel-Versionen nicht mehr angesehen) asnscheinend
hat man eine "langlebigere) Loseung gefunden ...
Post by Helmut Schellong
Eine Implementation bietet uintptr_t an, jedoch mit einem Verhalten,
das vollkommen unbrauchbar ist, und dadurch ist diese
Implementation nicht standardkonform.
Im Gegenteil: Ein Programm, dass in das vorhandensein von uintptr_t mehr
hineininterpretiert, als der Standard explizit vorgibt (ob allein mit den
Zusagen es Standards uintptr_t nun noch sinnvoll nutzbar ist oder nicht,
ist dabei egal), ist nicht mehr standardkonform. Es gibt Probleme, bei denen
es kaum sinnvoll imoeglich ist, sie mit standardkonformem C zu loesen, das
ist ja auch in Ordnung, aber dann sollten die *zusaetzlich* getroffenen
Annahmen sauber dokumentiert werden.Du siehst noch nicht einmal ein, dass
du ueber den Standard hinausgehende Annahmen getroffen hast ...
Post by Helmut Schellong
Ich glaube nicht, daß jemals solch ein Vorgang beobachtbar sein wird.
Die Linux Kernelentwickler hatten wohl auch nicht damit gerechnet, jemals
auf einen C-Compiler (zumindest nicht beim gcc) zu treffen, auf denen ihr
Code nicht mehr funktionieren wuerde ...).

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
David Seppi
2018-08-19 13:27:44 UTC
Permalink
Post by Juergen Ilse
Die erste "Korrektur" im Kernel 2.0.36 war eigentlich genauso
daneben wie der Original Code aus 2.0.35: der veraendernde Zugriff auf den
auf dem Stack liegenden Parameter wurde indirekt ueber Referenzierung eines
Pointers durchgefuehrt, was der Optimizer der damaligen Compiler dann nicht
mehr als "Optimierungsmoeglichkeit" erkannte ... Bei der naechsten deutlichen
Verbesserung des Optimizers haette es dann abermals gescheppert
Weißt Du warum? Ich sehe auf dem ersten Blick keine Probleme, wenn die
Funktion einen Pointer erhält.
--
David Seppi
1220 Wien
Juergen Ilse
2018-08-20 21:14:24 UTC
Permalink
Hallo,
Post by David Seppi
Post by Juergen Ilse
Die erste "Korrektur" im Kernel 2.0.36 war eigentlich genauso
daneben wie der Original Code aus 2.0.35: der veraendernde Zugriff auf den
auf dem Stack liegenden Parameter wurde indirekt ueber Referenzierung eines
Pointers durchgefuehrt, was der Optimizer der damaligen Compiler dann nicht
mehr als "Optimierungsmoeglichkeit" erkannte ... Bei der naechsten deutlichen
Verbesserung des Optimizers haette es dann abermals gescheppert
Weißt Du warum? Ich sehe auf dem ersten Blick keine Probleme, wenn die
Funktion einen Pointer erhält.
Wenn der Compiler zur Compilezeit durch Codeanalyse herausbekommt, dass
letztlich nur ein auf dem Stack liegender Zebergabeparameter manipuliert
wird, auf den anschliessend in der Funktion nicht mehr lesend zugegriffen
wird und dessen Gueltigkeit mit dem Ende der Funktion endet, darf er diese
Wert-Manipulation komplett wegoptimieren. Das fuehrt dann allerdigs dazu,
dass der aufrufende Assimbler-Code den unmanipulierten (falschen) Wert vom
Stack ins Maschinemstatuswort laedt und beim naechsten Port-Zugriff scheppert
es dann ...

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
Thomas Koenig
2018-08-20 22:33:03 UTC
Permalink
Post by Juergen Ilse
Wenn der Compiler zur Compilezeit durch Codeanalyse herausbekommt, dass
letztlich nur ein auf dem Stack liegender Zebergabeparameter manipuliert
wird, auf den anschliessend in der Funktion nicht mehr lesend zugegriffen
wird und dessen Gueltigkeit mit dem Ende der Funktion endet, darf er diese
Wert-Manipulation komplett wegoptimieren. Das fuehrt dann allerdigs dazu,
dass der aufrufende Assimbler-Code den unmanipulierten (falschen) Wert vom
Stack ins Maschinemstatuswort laedt und beim naechsten Port-Zugriff scheppert
es dann ...
Regehr's law:

A sufficiently advanced compiler is indistinguishable from an adversary.
David Seppi
2018-08-21 08:33:12 UTC
Permalink
Post by Juergen Ilse
Wenn der Compiler zur Compilezeit durch Codeanalyse herausbekommt, dass
letztlich nur ein auf dem Stack liegender Zebergabeparameter manipuliert
wird, auf den anschliessend in der Funktion nicht mehr lesend zugegriffen
wird und dessen Gueltigkeit mit dem Ende der Funktion endet, darf er diese
Wert-Manipulation komplett wegoptimieren.
Ah, ja, danke. Ich habe übersehen, daß nicht nur der Pointer, sondern
auch der zu manipulierende Wert selbst auf den Stack gelegt wird.
--
David Seppi
1220 Wien
Helmut Schellong
2018-08-21 10:45:00 UTC
Permalink
Post by Juergen Ilse
Hallo,
Post by Оlе Ѕtrеісhеr
Und wenn ein Programm in einer solchen Implementierung nicht korrekt
läuft, ist es halt nicht standardkonform.
So ist es. Eines der wohl populaersten Beispiele fuer "nicht standadkonformen
Code" war eine Stelle im Linux Kernel 2.0.35, der nachvollziehbar *nicht*
funktionierte, wenn man ihn mit gcc 2.8 oder neuer (einschliesslich der da-
maligen egcs Versionen) compilierte. Was war passiert? Es gab eine kleine
C-Funktion, die von Assembler aus aufgerufen wurde. Der Assembler-Code hatte
mit "pusha" alle Register-Inhalte (einschliesslich des "Maschinenstatusworts"
auf den stack geschoben. Die C-Funktion hat nun in den uebergebenen Parametern
"herumgepatched", sprich im dort abgelegten Maschinenstatuswort ein Bit fuer
das setzen von IO-privileges gesetzt (wenn ich mich da richtig erinnere).
Ab gcc2.8 war nun der Optizer ausgereift genug, um zu erkennen, dass der
veraenderte Wert nach verlassen der Funktion keine Gueltigkeit mehr hatte
und in der Funktion nach Veraenderung des Wertes nicht mehr verwendet wurde.
Daher hatte der Optimizer dann die gesamte Veraenderung des Wertes "wegopti-
miert", womit die Funktion letztendlich nur ein return enthielt ...
Da sich aber der Assembler-Code auf den veraenderten Parameter auf dem Stack
verlasssen hatte, funktionierte das setzen der IO-privileges im Kernel nicht
mehr -> BUMMM!!!
Es wurden dort entscheidende Grundfehler gemacht:

Wenn Assembler selbst geschrieben und mit C-Code 'vermischt' wird, müssen
alle Konventionen des Compilers nachgemacht bzw. beachtet werden.

Ich habe hierzu viel Erfahrung, weil ich größere Assembler-Bibliotheken
für iX86 und Mikrokontroller schrieb.
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Juergen Ilse
2018-08-21 20:26:20 UTC
Permalink
Hallo,
Post by Helmut Schellong
Post by Juergen Ilse
So ist es. Eines der wohl populaersten Beispiele fuer "nicht standadkonformen
Code" war eine Stelle im Linux Kernel 2.0.35, der nachvollziehbar *nicht*
funktionierte, wenn man ihn mit gcc 2.8 oder neuer (einschliesslich der da-
maligen egcs Versionen) compilierte. Was war passiert? Es gab eine kleine
C-Funktion, die von Assembler aus aufgerufen wurde. Der Assembler-Code hatte
mit "pusha" alle Register-Inhalte (einschliesslich des "Maschinenstatusworts"
auf den stack geschoben. Die C-Funktion hat nun in den uebergebenen Parametern
"herumgepatched", sprich im dort abgelegten Maschinenstatuswort ein Bit fuer
das setzen von IO-privileges gesetzt (wenn ich mich da richtig erinnere).
Ab gcc2.8 war nun der Optizer ausgereift genug, um zu erkennen, dass der
veraenderte Wert nach verlassen der Funktion keine Gueltigkeit mehr hatte
und in der Funktion nach Veraenderung des Wertes nicht mehr verwendet wurde.
Daher hatte der Optimizer dann die gesamte Veraenderung des Wertes "wegopti-
miert", womit die Funktion letztendlich nur ein return enthielt ...
Da sich aber der Assembler-Code auf den veraenderten Parameter auf dem Stack
verlasssen hatte, funktionierte das setzen der IO-privileges im Kernel nicht
mehr -> BUMMM!!!
Eigentlich wurde *EIN* entscheidender Fehler gemacht ...
Post by Helmut Schellong
Wenn Assembler selbst geschrieben und mit C-Code 'vermischt' wird, müssen
alle Konventionen des Compilers nachgemacht bzw. beachtet werden.
... nur dies war er nicht. Der Fehler war, dass ueber die nach Aufruf der
Funktion auf dem Stack liegenden Parameter bestimmte Annagmen gemacht und
diese Werte weiter verwendet wurden, obwohl ihr Gueltigkeitsbereich mit
dem verlassen der Funktion abgelaifen war. Diese dem Compiler nicht be-
kannte Verwendung der Uebergabeparameter ausserhalb ihres Gueltigkeots-
bereich fuehrte letztlich zum Fehler, denn da der geaenderte Parameter-
wert innerhalb seines Gueltigkeitsbereichs nicht mehr verwendet wurde,
hat der Compiler die Wertaenderung wegoptimieren duerfen, und dieses
wegoptimieren verursachte den Fehler ...
Post by Helmut Schellong
Ich habe hierzu viel Erfahrung, weil ich größere Assembler-Bibliotheken
für iX86 und Mikrokontroller schrieb.
Dir war bis eben noch nicht einmal klar, was genau der Fehler war (da du
hier von "Nichteinhaltung der Aufrufkonventionen" geschrieben hast, ob-
wohl das hier gerade *nicht* das Problem war ...).

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
Helmut Schellong
2018-08-22 12:30:58 UTC
Permalink
Post by Juergen Ilse
Post by Helmut Schellong
Ich habe hierzu viel Erfahrung, weil ich größere Assembler-Bibliotheken
für iX86 und Mikrokontroller schrieb.
Dir war bis eben noch nicht einmal klar, was genau der Fehler war (da du
hier von "Nichteinhaltung der Aufrufkonventionen" geschrieben hast, ob-
wohl das hier gerade *nicht* das Problem war ...).
Was ich im Detail meinte:
'pusha' zu verwenden ist etwas, das gegen die Konventionen verstößt.

Das ist compiler-individuell zu beachten.
Keiner der von mir benutzten Compiler hat das jemals verwendet.
Beispielsweise in der Datei 'bsh.s', die etwa 80000 Zeilen hat,
gibt und gab es 'pusha' nicht.

Ich würde daher 'pusha' _pauschal_ vermeiden - unbedingt!

Auf den Fehler, der hier bei Linux gemacht wurde, ging ich gar nicht ein!

Ich habe pauschal geschrieben, daß beim Schreiben von Assembler, der mit
einem Kompilat (eines Compilers) zusammenarbeiten soll, alle Konventionen
des betreffenden Compilers beachtet werden müssen.

Ich hatte nämlich bei der Entwicklung von Assembler-Bibliotheken zunächst
auch 'fortschrittliche' Instruktionen/Konzepte verwendet, die der
Compiler nicht verwandte, mit dem beispielhaften Ergebnis, daß
am dritten Tag der Tests, beim Durchlaufen bestimmter Code-Abschnitte,
durch bestimmte Werte bewirkt, Fehler auftraten, die in etwa 97% des
Gesamt-Codes _nicht_ auftraten.

Seitdem verhalte ich mich so, wie beschrieben: nämlich zurückgenommen!

Dennoch sind/waren diese Asm-Libs ein gigantischer Erfolg.
Mir gelang es z.B., eine Quadratwurzel aus 32 Bit innerhalb
von 16 µs zu ziehen, auf einem Controller mit 16 Bit und 24 MHz Takt.
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Juergen Ilse
2018-08-24 01:34:05 UTC
Permalink
Hallo,
Post by Helmut Schellong
Post by Juergen Ilse
Post by Helmut Schellong
Ich habe hierzu viel Erfahrung, weil ich größere Assembler-Bibliotheken
für iX86 und Mikrokontroller schrieb.
Dir war bis eben noch nicht einmal klar, was genau der Fehler war (da du
hier von "Nichteinhaltung der Aufrufkonventionen" geschrieben hast, ob-
wohl das hier gerade *nicht* das Problem war ...).
'pusha' zu verwenden ist etwas, das gegen die Konventionen verstößt.
Weshalb sollte das so sein? Die Registerinhalte, die damit auf den Stack
geschoben wurden, waren exakt in der Reihenfolge und mit (fuer gcc auf
dieser Architektur) passenden Typen deklariert. In der Parameterueber-
gabe gab es auch nicht den geringsten Fehler. Der Aufruf und die Para-
meteruebergabe waren bei dem Code eben *NICHT* das Problem. Gegen welche
Konventionen soll denn die Verwendung von pusha in "handheschhriebenem"
Assemblercode verstossen haben?
Post by Helmut Schellong
Keiner der von mir benutzten Compiler hat das jemals verwendet.
Beispielsweise in der Datei 'bsh.s', die etwa 80000 Zeilen hat,
gibt und gab es 'pusha' nicht.
Du hast immer noch nicht verstanden, wo der Fehler lag. Ein Wert, der mit
auf den Stack geschoben wurde, wurde in der C-Funktion geaendert, und der
Assemblercode hoöte sich den (vermeintlich geaenderten) Wert dann wieder
vom Stack. Zu diesem Zeitpunkt (aks der Wert wieder vom Stack geholt wurde)
war der Gueltigkeitsbereich des Uebergabeparameters kaut C-Standard bereits
abgelaufen. Da der Compiler nach der Wertaenderung des Parameters keine
weitere Verwendung innerhalb des Gueltihkeitsbereichs festgestellt hat,
hatte er die Wert<enderung des Parameters "wegoptimiert", und das fuehrte
letztlich zu dem Fehler. Es hatte also nicht das geringste mit "Nichtein-
haltung von Konventionen" zu tun, ausser, dass ein Variablenwert ausserhalb
des Gueltigleitsbereichs der Variable verwendet wurde (weshalb der Compiler
dann eine Optimierung durchgefuehrt hatte, die zum Fehlverhalten des Codes
fuehrte) ...
Post by Helmut Schellong
Ich würde daher 'pusha' _pauschal_ vermeiden - unbedingt!
Das war dort aber gar nicht das Problem ...
Post by Helmut Schellong
Auf den Fehler, der hier bei Linux gemacht wurde, ging ich gar nicht ein!
Um den drehte sich aber der Teilthread. Und worin der Fehler bestand hast
du anscheinend nicht verstanden sondern stattdessen OffTopic irgend etwas
von "Einhaltung von Konventionen" herumfabuliert.

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
Helmut Schellong
2018-08-24 09:26:17 UTC
Permalink
Post by Juergen Ilse
Hallo,
Post by Helmut Schellong
Post by Juergen Ilse
Post by Helmut Schellong
Ich habe hierzu viel Erfahrung, weil ich größere Assembler-Bibliotheken
für iX86 und Mikrokontroller schrieb.
Dir war bis eben noch nicht einmal klar, was genau der Fehler war (da du
hier von "Nichteinhaltung der Aufrufkonventionen" geschrieben hast, ob-
wohl das hier gerade *nicht* das Problem war ...).
'pusha' zu verwenden ist etwas, das gegen die Konventionen verstößt.
Weshalb sollte das so sein? Die Registerinhalte, die damit auf den Stack
geschoben wurden, waren exakt in der Reihenfolge und mit (fuer gcc auf
dieser Architektur) passenden Typen deklariert. In der Parameterueber-
gabe gab es auch nicht den geringsten Fehler. Der Aufruf und die Para-
meteruebergabe waren bei dem Code eben *NICHT* das Problem. Gegen welche
Konventionen soll denn die Verwendung von pusha in "handheschhriebenem"
Assemblercode verstossen haben?
Der Compiler verwendet es nicht.
Deshalb verstößt es gegen die Konventionen des Compilers.
Und deshalb würde ich es nicht verwenden.
Ganz einfach!

Zu 'pusha' gehört 'popa', das an anderer Stelle gegeben
werden muß, um zu restaurieren.
Oder es muß u.a. eine passende Sequenz 'push' gegeben werden.

pushad
------
Temp ← (ESP);
Push(EAX);
Push(ECX);
Push(EDX);
Push(EBX);
Push(Temp);
Push(EBP);
Push(ESI);
Push(EDI);

Vom "Maschinenstatuswort" sehe ich hier nichts.

Ich kann Deinen Text zu dem Fehler nicht so recht nachvollziehen.
'pusha' puscht die 8 general-purpose register, Funktionsargumente
aber nur, wenn die Register passend gefüllt wurden.
Und was soll man mit dem kopierten Wert des "Maschinenstatuswort"
anfangen?
Jedenfalls baut der Compiler den Stackframe ganz anders.
Bei x64 werden 6 Register für Argumentübergabe verwendet.
Post by Juergen Ilse
Post by Helmut Schellong
Keiner der von mir benutzten Compiler hat das jemals verwendet.
Beispielsweise in der Datei 'bsh.s', die etwa 80000 Zeilen hat,
gibt und gab es 'pusha' nicht.
Du hast immer noch nicht verstanden, wo der Fehler lag. Ein Wert, der mit
auf den Stack geschoben wurde, wurde in der C-Funktion geaendert, und der
Assemblercode hoöte sich den (vermeintlich geaenderten) Wert dann wieder
vom Stack. Zu diesem Zeitpunkt (aks der Wert wieder vom Stack geholt wurde)
war der Gueltigkeitsbereich des Uebergabeparameters kaut C-Standard bereits
abgelaufen. Da der Compiler nach der Wertaenderung des Parameters keine
weitere Verwendung innerhalb des Gueltihkeitsbereichs festgestellt hat,
hatte er die Wert<enderung des Parameters "wegoptimiert", und das fuehrte
letztlich zu dem Fehler. Es hatte also nicht das geringste mit "Nichtein-
haltung von Konventionen" zu tun, ausser, dass ein Variablenwert ausserhalb
des Gueltigleitsbereichs der Variable verwendet wurde (weshalb der Compiler
dann eine Optimierung durchgefuehrt hatte, die zum Fehlverhalten des Codes
fuehrte) ...
Ich schrieb:

"Auf den Fehler, der hier bei Linux gemacht wurde, ging ich gar nicht ein!"

Ich wollte den Fehler gar nicht verstehen.
Das Wort 'pauschal' scheint so mancher nicht zu verstehen.

Es wurde 'volatile' vergessen.
Das ist auch ein Grundfehler.
Post by Juergen Ilse
Post by Helmut Schellong
Ich würde daher 'pusha' _pauschal_ vermeiden - unbedingt!
Das war dort aber gar nicht das Problem ...
Egal -> pauschal.
Post by Juergen Ilse
Post by Helmut Schellong
Auf den Fehler, der hier bei Linux gemacht wurde, ging ich gar nicht ein!
Um den drehte sich aber der Teilthread. Und worin der Fehler bestand hast
du anscheinend nicht verstanden sondern stattdessen OffTopic irgend etwas
von "Einhaltung von Konventionen" herumfabuliert.
OffTopic?
Es ging darum, Assembler einzusetzen, operativ vermischt mit
C-Code, den der Compiler übersetzt.
So wie es auch bei Assembler-Libs der Fall ist.
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Juergen Ilse
2018-08-25 13:35:42 UTC
Permalink
Hallo,
Post by Helmut Schellong
Post by Juergen Ilse
Weshalb sollte das so sein? Die Registerinhalte, die damit auf den Stack
geschoben wurden, waren exakt in der Reihenfolge und mit (fuer gcc auf
dieser Architektur) passenden Typen deklariert. In der Parameterueber-
gabe gab es auch nicht den geringsten Fehler. Der Aufruf und die Para-
meteruebergabe waren bei dem Code eben *NICHT* das Problem. Gegen welche
Konventionen soll denn die Verwendung von pusha in "handheschhriebenem"
Assemblercode verstossen haben?
Der Compiler verwendet es nicht.
Deshalb verstößt es gegen die Konventionen des Compilers.
Und deshalb würde ich es nicht verwenden.
Ganz einfach!
Zu 'pusha' gehört 'popa', das an anderer Stelle gegeben
werden muß, um zu restaurieren.
Oder es muß u.a. eine passende Sequenz 'push' gegeben werden.
pushad
------
Temp ← (ESP);
Push(EAX);
Push(ECX);
Push(EDX);
Push(EBX);
Push(Temp);
Push(EBP);
Push(ESI);
Push(EDI);
Vom "Maschinenstatuswort" sehe ich hier nichts.
Moeglicherweise habe ich jetzt die Bezeichnungen der entsprechenden Assembler-
befehle verwechselt. Es wurde eine Instruktion verwendet, die neben anderen
Inhalten auch das Register, dass die IO-privilege-Bits enthielt (an Details
erinnere ich mich nicht, weil ich mich seit vielen Jahren noch nicht einmal
mehr ansatzweise mit x86 Assembler beschaeftigt habe). Die Idee war, die
Registerinhalte auf den stack zu schieben, darin die IO-Privilege-Bits zu
patchen und dann diese geaenderten Inhalte wieder in die selben Regisster
zurueckzuladen. Eigentlich war es eine saudumme Idee, das mit einer von
Assembler aus aufgerufenen C-Funktion zu tun, aber wenn man es schon so
versucht, sollte man ey richtig machen, und das ist dort eben *nicht*
passiert. Dadurch dass dys "patchen der Bits" durch den Optimizer als
"wirkungslos im Gueltigkeitsbereichs der Uebergabeparameter" erkannt und
daher wegoptimiert wurde, klappte das umsetzen der IO-privilege-Bits nicht,
was zum sicheren crash des Kernels fuehrte. Wenn dich das naeher interessiert,
lies es selbst im Source von Kernel 2.0.35 nach.
Post by Helmut Schellong
Jedenfalls baut der Compiler den Stackframe ganz anders.
Bei x64 werden 6 Register für Argumentübergabe verwendet.
2.0.35 lief nicht auf x86-64 (und liess sich IIRC auf x86 auch nicht als
64-Bit Kernel compilieren). Der entsprechende Teil des Kernels war in
entsprechende Praeprozessor-Direktiven eingehuellt, dass diese Variante
ohnehin nur fuer x86/32Bit verwendet wurde.
Post by Helmut Schellong
Post by Juergen Ilse
Um den drehte sich aber der Teilthread. Und worin der Fehler bestand hast
du anscheinend nicht verstanden sondern stattdessen OffTopic irgend etwas
von "Einhaltung von Konventionen" herumfabuliert.
OffTopic?
Es ging darum, Assembler einzusetzen, operativ vermischt mit
C-Code, den der Compiler übersetzt.
So wie es auch bei Assembler-Libs der Fall ist.
Dir ging es darum. Allen anderen ging es in dem Teilthred eher nicht darum ...

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
Helmut Schellong
2018-08-25 15:29:03 UTC
Permalink
Post by Juergen Ilse
Hallo,
Post by Helmut Schellong
Post by Juergen Ilse
Weshalb sollte das so sein? Die Registerinhalte, die damit auf den Stack
geschoben wurden, waren exakt in der Reihenfolge und mit (fuer gcc auf
dieser Architektur) passenden Typen deklariert. In der Parameterueber-
gabe gab es auch nicht den geringsten Fehler. Der Aufruf und die Para-
meteruebergabe waren bei dem Code eben *NICHT* das Problem. Gegen welche
Konventionen soll denn die Verwendung von pusha in "handheschhriebenem"
Assemblercode verstossen haben?
Der Compiler verwendet es nicht.
Deshalb verstößt es gegen die Konventionen des Compilers.
Und deshalb würde ich es nicht verwenden.
Ganz einfach!
Zu 'pusha' gehört 'popa', das an anderer Stelle gegeben
werden muß, um zu restaurieren.
Oder es muß u.a. eine passende Sequenz 'push' gegeben werden.
pushad
------
Temp ← (ESP);
Push(EAX);
Push(ECX);
Push(EDX);
Push(EBX);
Push(Temp);
Push(EBP);
Push(ESI);
Push(EDI);
Vom "Maschinenstatuswort" sehe ich hier nichts.
Moeglicherweise habe ich jetzt die Bezeichnungen der entsprechenden Assembler-
befehle verwechselt. Es wurde eine Instruktion verwendet, die neben anderen
Inhalten auch das Register, dass die IO-privilege-Bits enthielt (an Details
erinnere ich mich nicht, weil ich mich seit vielen Jahren noch nicht einmal
mehr ansatzweise mit x86 Assembler beschaeftigt habe). Die Idee war, die
Registerinhalte auf den stack zu schieben, darin die IO-Privilege-Bits zu
patchen und dann diese geaenderten Inhalte wieder in die selben Regisster
zurueckzuladen. Eigentlich war es eine saudumme Idee, das mit einer von
Assembler aus aufgerufenen C-Funktion zu tun, aber wenn man es schon so
versucht, sollte man ey richtig machen, und das ist dort eben *nicht*
passiert. Dadurch dass dys "patchen der Bits" durch den Optimizer als
"wirkungslos im Gueltigkeitsbereichs der Uebergabeparameter" erkannt und
daher wegoptimiert wurde, klappte das umsetzen der IO-privilege-Bits nicht,
was zum sicheren crash des Kernels fuehrte. Wenn dich das naeher interessiert,
lies es selbst im Source von Kernel 2.0.35 nach.
Post by Helmut Schellong
Jedenfalls baut der Compiler den Stackframe ganz anders.
Bei x64 werden 6 Register für Argumentübergabe verwendet.
Nachfolgend die EFLAGS:
-----------------------
X ID Flag (ID)
X Virtual Interrupt Pending (VIP)
X Virtual Interrupt Flag (VIF)
X Alignment Check / Access Control (AC)
X Virtual-8086 Mode (VM)
X Resume Flag (RF)
X Nested Task (NT)
X I/O Privilege Level (IOPL)
S Overflow Flag (OF)
C Direction Flag (DF)
X Interrupt Enable Flag (IF)
X Trap Flag (TF)
S Sign Flag (SF)
S Zero Flag (ZF)
S Auxiliary Carry Flag (AF)
S Parity Flag (PF)
S Carry Flag (CF)

S Indicates a Status Flag
C Indicates a Control Flag
X Indicates a System Flag

Dies Register kann mit 'pushf' auf den Stack gebracht werden.
(Das DF bestimmt die Arbeitsrichtung im Speicher für einige Instruktionen!)
Post by Juergen Ilse
2.0.35 lief nicht auf x86-64 (und liess sich IIRC auf x86 auch nicht als
64-Bit Kernel compilieren). Der entsprechende Teil des Kernels war in
entsprechende Praeprozessor-Direktiven eingehuellt, dass diese Variante
ohnehin nur fuer x86/32Bit verwendet wurde.
Jedenfalls wurde C-seitig Code vom Compiler wegoptimiert.
Das ist übrigens ebenfalls eine Konvention des Compilers, die ich
mahnte, sie zu beachten.
Gewöhnlich benutzt man 'volatile', um Wegoptimierung zu verhindern.
Post by Juergen Ilse
Post by Helmut Schellong
Post by Juergen Ilse
Um den drehte sich aber der Teilthread. Und worin der Fehler bestand hast
du anscheinend nicht verstanden sondern stattdessen OffTopic irgend etwas
von "Einhaltung von Konventionen" herumfabuliert.
OffTopic?
Es ging darum, Assembler einzusetzen, operativ vermischt mit
C-Code, den der Compiler übersetzt.
So wie es auch bei Assembler-Libs der Fall ist.
Dir ging es darum. Allen anderen ging es in dem Teilthred eher nicht darum ...
Jedenfalls wäre der Fehler mir nicht passiert.
Was genau daran zu erkennen ist, daß und wie ich mich in
diesen Teilthread eingemischt habe.
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Juergen Ilse
2018-08-25 14:17:28 UTC
Permalink
Hallo,
Post by Helmut Schellong
Post by Juergen Ilse
Weshalb sollte das so sein? Die Registerinhalte, die damit auf den Stack
geschoben wurden, waren exakt in der Reihenfolge und mit (fuer gcc auf
dieser Architektur) passenden Typen deklariert. In der Parameterueber-
gabe gab es auch nicht den geringsten Fehler. Der Aufruf und die Para-
meteruebergabe waren bei dem Code eben *NICHT* das Problem. Gegen welche
Konventionen soll denn die Verwendung von pusha in "handheschhriebenem"
Assemblercode verstossen haben?
Der Compiler verwendet es nicht.
Deshalb verstößt es gegen die Konventionen des Compilers.
Um zu ahnen, was denn dort passiert ist, muss man sich nur die C-Funktion
ansehen (und die Uebergabeparameter sehen danach aus, als waere pusha oder
ein damit verwandter Befehl im aufrufenden Assembler-Code verwendet worden).
Der C-Code war dieser hier (gerade noch einmal herausgesucht):
--------------------------------
/*
* sys_iopl has to be used when you want to access the IO ports
* beyond the 0x3ff range: to get the full 65536 ports bitmapped
* you'd need 8kB of bitmaps/process, which is a bit excessive.
*
* Here we just change the eflags value on the stack: we allow
* only the super-user to do it. This depends on the stack-layout
* on system-call entry - see also fork() and the signal handling
* code.
*/
asmlinkage int sys_iopl(long ebx,long ecx,long edx,
long esi, long edi, long ebp, long eax, long ds,
long es, long fs, long gs, long orig_eax,
long eip,long cs,long eflags,long esp,long ss)
{
unsigned int level = ebx;

if (level > 3)
return -EINVAL;
if (!suser() || securelevel > 0)
return -EPERM;
*(&eflags) = (eflags & 0xffffcfff) | (level << 12);
return 0;
}
-------------------------------
Es wurde der Parameter "eflags" auf dem Stack manipuliert und offenbar davon
ausgegangen, dass der aufrufende Assemblercode beim "restaurieren der Regis-
terinhalte" dann den modifizierten Wert fuer eflags verwendet. Der Optimizer
des Compilers hat ab gcc2.8/egcs1.0 und neuer nun festgestellt, dass mit

*(&eflags) = (eflags & 0xffffcfff) | (level << 12);

nur der Wert eines Uebergabeparameters geaendert wird, der nach verlassen
der Funktion seine Gueltigkeit verloren hat. Damit hat der Compiler diese
Anweisung komplett wegoptimiert und der aufrufende Assemblercode hat den
*unmodifizierten* Wert von eflags statt (wie erhofft) den modifizierten
Wert vom Stack geholt. Damit wurden dann die entsprechenden Bits fuer das
IO-Privilege (ich nehme anhand der Kommentare an, dass es bei dieser Bit-
Manipulation darum ging, denn so genau habe ich mich mangels Bedarf nie
mit 80386 Maschinensprache im 32 Bit protected moe beschaeftigt). Im auf-
gerufenen Code wurde also der (laut C-Standard an der Stelle nicht mehr
gueltige) Wert des Uebergabeparameters "eflags" verwendet, und das hat
nach Verbesserung des Optimizers daann gewaltig gescheppert ...

Mit Sicherheit hatte niemand von den damaligen Autoren solchen Codes nie
damit gerechnet, dass ein Optimizer eines C-Compilers jemals gut genug
sein wuerdee, um die obenstehende Zeile wegzuoptimierenxi, aber der Sprach-
standard haette es eigentlich schon lange vorher erlaubt, wenn der Optimizer
erkannt haette, was da genau passiert ...

So etwas passiert, wenn man sich auf bestimmtes Compilerverhalten ("ich habe
noch nie etwas anderes bei einem Compiler gesehen!") statt auf den exakten
Wortlaut des Standards verlaesst.

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
Helmut Schellong
2018-08-25 16:31:31 UTC
Permalink
Post by Juergen Ilse
Hallo,
[...]
Post by Juergen Ilse
Post by Helmut Schellong
Der Compiler verwendet es nicht.
Deshalb verstößt es gegen die Konventionen des Compilers.
Um zu ahnen, was denn dort passiert ist, muss man sich nur die C-Funktion
ansehen (und die Uebergabeparameter sehen danach aus, als waere pusha oder
ein damit verwandter Befehl im aufrufenden Assembler-Code verwendet worden).
Ich habe ein Antwort-Posting gerade abgesandt.
Die haben offenbar pushad, pushf, push, etc. verwendet.
Post by Juergen Ilse
--------------------------------
[...]
Post by Juergen Ilse
asmlinkage int sys_iopl(long ebx,long ecx,long edx,
long esi, long edi, long ebp, long eax, long ds,
long es, long fs, long gs, long orig_eax,
long eip,long cs,long eflags,long esp,long ss)
{
unsigned int level = ebx;
if (level > 3)
return -EINVAL;
if (!suser() || securelevel > 0)
return -EPERM;
*(&eflags) = (eflags & 0xffffcfff) | (level << 12);
return 0;
}
-------------------------------
Es wurde der Parameter "eflags" auf dem Stack manipuliert und offenbar davon
ausgegangen, dass der aufrufende Assemblercode beim "restaurieren der Regis-
terinhalte" dann den modifizierten Wert fuer eflags verwendet. Der Optimizer
des Compilers hat ab gcc2.8/egcs1.0 und neuer nun festgestellt, dass mit
*(&eflags) = (eflags & 0xffffcfff) | (level << 12);
nur der Wert eines Uebergabeparameters geaendert wird, der nach verlassen
der Funktion seine Gueltigkeit verloren hat. Damit hat der Compiler diese
Anweisung komplett wegoptimiert und der aufrufende Assemblercode hat den
*unmodifizierten* Wert von eflags statt (wie erhofft) den modifizierten
Wert vom Stack geholt.
[...]

Es ist jedenfalls eine abenteuerliche Programmierung.
*(&eflags) ≡ *&eflags ≡ eflags
Da hat jemand 'irgendwie geahnt', was passieren kann.
Das entscheidende 'long volatile eflags,' wurde versäumt.
Ich hätte auch 'unsigned long' verwendet.
Post by Juergen Ilse
Mit Sicherheit hatte niemand von den damaligen Autoren solchen Codes nie
damit gerechnet, dass ein Optimizer eines C-Compilers jemals gut genug
sein wuerdee, um die obenstehende Zeile wegzuoptimierenxi, aber der Sprach-
standard haette es eigentlich schon lange vorher erlaubt, wenn der Optimizer
erkannt haette, was da genau passiert ...
So etwas passiert, wenn man sich auf bestimmtes Compilerverhalten ("ich habe
noch nie etwas anderes bei einem Compiler gesehen!") statt auf den exakten
Wortlaut des Standards verlaesst.
Daß insbesondere gcc schon sehr lange wegoptimiert, weiß ich seit <2000.
clang wegoptimiert noch stärker.
'volatile' gibt es seit 1989, mit entsprechender Beschreibung S.206 im K&R.
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Оlе Ѕtrеісhеr
2018-08-19 19:49:40 UTC
Permalink
Post by Helmut Schellong
Post by Оlе Ѕtrеісhеr
(Quoting ergänzt)
[...]
Post by Оlе Ѕtrеісhеr
Post by Rainer Weikusat
Was fuer Leute Du fuer mit Dir kompatibel haelst, interessiert mich einen
feuchten Kericht.
Mal abgesehen von Deiner überaus sachlichen Antwort: Man hat tatsächlich
die Freiheit, das was ein Standard nicht festlegt (vulgo "die Lücken")
in der Implementierung frei ("nach Gutdünken") zu wählen. Es bleibt dann
eine standardkonforme Implementierung.
Zunächst einmal kann das so 'digital' gesehen werden.
Man darf aber davon ausgehen, daß Implementierer bestrebt sind,
nützliche, brauchbare Mittel zu entwickeln.
Und deren Anforderungen sind äußerst unterschiedlich und ändern sich
auch im Laufe der Zeit. Hardening ist etwas, was z.B. erst in den
letzten Jahren Einzug in den Mainstream gehalten hat -- und eben genau
einige "Schlupflöcher" stopft, die schludrige Programmierer gerne
genutzt hatten (und die dann heute Probleme machen).

Und ich hatte auch schon geschrieben: Es muss gar nicht so implementiert
sein. Es reicht schon, wenn ein Compiler den Standard genau
interpretiert und an Stellen, wo er Freiheiten hat, sich für diejenige
Variante mit der besten Codeoptimierung entscheidet. Zum Beispiel, indem
er annimmt, dass der Vergleich zweier unabhängig erhaltener uintptr_t
nicht festgelegt ist.
Post by Helmut Schellong
Post by Оlе Ѕtrеісhеr
Und wenn ein Programm in einer solchen Implementierung nicht korrekt
läuft, ist es halt nicht standardkonform.
Eine Implementation bietet uintptr_t an, jedoch mit einem Verhalten,
das vollkommen unbrauchbar ist, und dadurch ist diese Implementation
nicht standardkonform.
Nach Standard sind uintptr_t nur dazu "brauchbar", ein Integer und
wieder in einen Pointer konvertierbar zu sein. Mehr "Brauchbarkeit" wird
vom Standard nicht gefordert. Wenn man mehr gewollt hätte, dann hätte
man es hineingeschrieben.

Das ist zugegeben nicht viel, aber offenbar war es nicht Intention des
Standards, die üblichen Undefiniertheiten beim Umgang mit Pointern
aufzuweichen (eben z.B. der Vergleich zweier unabhängiger Pointer).

Man kann dann nicht einfach sagen "das ist nicht brauchbar, und darum
bedeutet der Standard für mich was anderes". Das muss man mit dem
Standardisierungsgremium ausmachen, aber es ändert nichts an der
Nicht-Konformität Deines Codeschnipsels.
Post by Helmut Schellong
Ich glaube nicht, daß jemals solch ein Vorgang beobachtbar sein wird.
Dein Glaube hat nur eben nichts mit Standardkonformität zu tun.

Ole
Helmut Schellong
2018-08-19 20:43:56 UTC
Permalink
[...]
Post by Оlе Ѕtrеісhеr
Nach Standard sind uintptr_t nur dazu "brauchbar", ein Integer und
wieder in einen Pointer konvertierbar zu sein. Mehr "Brauchbarkeit" wird
vom Standard nicht gefordert. Wenn man mehr gewollt hätte, dann hätte
man es hineingeschrieben.
Das tat man:
--------------------------------------------------------
integer types wide enough to hold pointers to objects;
Integer types capable of holding object pointers
--------------------------------------------------------
Der Standard schreibt nicht: "to hold converted pointers",
sondern eben das, was vorstehend geschrieben ist!
Übersetzung:
Integer-Typen, die breit genug sind, um Zeiger auf Objekte
enthalten zu können.

Das bedeutet, daß ein Wert mit dem Typ uintptr_t
dem betreffenden von einem Objekt abgeleiteten Zeigerwert
direkt entsprechen muß.

Ein Beweggrund wird sein, daß es Prozessoren gibt, die dedizierte
Register für Adressenwerte haben.
Operationen mit diesen Registern können UB bewirken.
Durch uintptr_t können nun andere Register benutzt werden, die
bei gleichen Operationen kein UB bewirken.
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Juergen Ilse
2018-08-18 23:26:59 UTC
Permalink
Hallo,
Post by Rainer Weikusat
Post by Juergen Ilse
Post by Helmut Schellong
Post by Juergen Ilse
Post by Helmut Schellong
Ich lehne es einfach ab, mir so etwas theoretisch auszudenken.
Ich lehne es auch ab, den Standard so zu deuten,
Den Standard muss man nicht deuten sondern nur lesen. Und alles, was nicht
ausdruecklich im Standard festgelegt ist auch nicht als gegeben annehmen.
Genau richtig.
Viele Leute nehmen aber Dinge an, die der Standard weder nennt
noch festlegt.
Ja, du zum Beispiel, wenn du zwingend vorraussetzen willst, dass fuer 2
vergleichbare Pointer ptr1 und ptr2 mit ptr1 < ptr2 denn auch
(uintptr_t)ptr1<(uintptr_t)ptr2 gelten muesse. Das verlangt der Standard
mit keinem Wort.
Es wird lediglich in einer Fussnote erwaehnt, dass dieses beabsichtigt
war ("consistent with the addressing structure of the execution
environment"). Dh falls ptr1 und ptr2 in dasselbe Feld zeigen und ptr1 <
ptr2 gilt, darf man erwarten, das (uintptr_t)ptr1 < (uinptr_t)ptr2
ebenfalls gilt.
Das mag die Intention gewesen sei n (war es vermutlich auch), aber es steht
so nicht im Standard ...
Post by Rainer Weikusat
Ebenso darf man erwarten, dass der Zahlenwert eines Zeiger, der nichts
anderes ist, als ein Index in ein Bytefeld, durch eine Konvertierung in
eine geeignet grossen, vorzeichenlosen Ganzzahltyp nicht aendert.
Das steht definitiv *nicht* im Standard. Sonst koennte man auch davon
ausgehen, dass ein Nullpointer in seiner internen Darstellung nur 0-Bits
enthaelt, gerade das ist aber *keine* garantierte Eigenschaft eines
Nullpointers. Das relevante Requirement lautet nur "ist verschieden von
allen Pointern auf existierende C-Objekte".
Post by Rainer Weikusat
Die Frage ob eine Implementierung, bei der
das nicht der Fall ist, standardkonform waere ist belanglos. Relevant
ist bloss "gibts die" und "gibts einen Grund, sie in Anwendungscode zu
unterstuetzen" und die Antworten auf diese Fragen lauten "wahrscheinlich
nicht" und "jedenfalls nicht".
Das ist aber zu kurz gedacht, wenn man auch fuer die Zukunft portable
Programme erstellen wioll, denn man kann nicht aus den Eigenschaften
aktuell existierender Implementierungen auf saemtliche Eigenschaften
saemtlicher in Zukunft neu geschriebenen Implementierungen schliessen ...
Post by Rainer Weikusat
Post by Juergen Ilse
Wie Oli anmerkte, gilt noch nicht einmal zwingend
(uintptr_t)ptr1==(unintptr_t)ptr1, wohl aber
(void*)(uintptr_t)ptr1==(void*)(uintptr_t)ptr1.
Dieses Argument kann man einfach umdrehen: Im Normtext steht nicht das
(uintptr_t)ptr1 != (uintptr_t)ptr1
wahr sein muesse.
Natuerlich nicht, weil weder das eine noch das andere ein *Requirement*
fuer eine standardkonforme Implementierung ist (nicht nach dem Wortlaut
des Standaards). Wollte man das Argument wie in deinem Vorschlag umdrehen,
wuerde man die fatale "Juristen-Logik" anwenden (einer meiner Mathe-Profs
bezeichnete sie mal scherzhaft so): (A => B) <=> (nicht A => nicht B).
Jeder, der sich schon einmal ansatzweise oberflaechlich mit Logik be-
schaeftigt hat, weiss, dass das nicht stimmt, und das es statt dessen
heissen muss: (A => B) <=> (nicht B => nicht A).
Post by Rainer Weikusat
Also ist es falsch. Oder vielleicht mit etwas mehr Phantasie: "In der
C-Norm steht nicht, das die Sonne im Osten aufgeht, also geht sie viel-
leicht nicht im Osten auf".
Es muesste heissen "Die Norm ermoeglicht zu dieser Frage keine konkrete
Aussage", und das waere korrekt. Bei der oben genannten Frage nach der
Notwendigkeit der Gleichheit beider Ausdruecke in einer standardkonformen
C-Implementierung gibt es aber (im Gegensatz zur Frage, ob die Sonne im
Osten aufgehtt) keine andere Informationsquelle als der C-Standard zur
Verfuegung.
Post by Rainer Weikusat
Man kann generell in jeden Text beliebigen Unsinn hineinlesen, in dem
man Luecken in der Darstellung nach Gutduenken ausfuellt.
Deswegen sollten *technische* Normen so formuliert sein, dass kein solcher
Interpretationsspielraum mehr besteht, es sei denn, diese Interpretations-
spielraum ist *gewollt*. Aus diesem Grund werden technische Standards nach-
gebessert, wenn sie diese Anforderung nicht erfuellen.
Post by Rainer Weikusat
Richtig ist allerdings: Abzueglich oben erwaehnter Absichtserklaerung
steht darueber nichts in der C-Norm. Also kann man allein auf Basis der
C-Norm keine Aussage darueber machen.
Eben. Und "portabel im Sinne des Standards" ist Code nur, wenn man allein
anhand des Standards eine definitive Aussage darueber machen kann, wie sich
der Code bei Ausfuehrung im Rahmen einer standardkonformen Implementierung
verhalten wuerde. Da man das ja offebar nicht kann, waere der Code im Sinne
des Standards *nicht* portabel. Nur weigert sich Helmut, das zu akzeptieren
sondern er erweiter statt dessen kurzerhand den Standard um implizite An-
nahmen, die Im Standard *nicht* garantiert werden.

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
Thomas Koenig
2018-08-19 13:34:20 UTC
Permalink
Post by Helmut Schellong
sizeof(array) == sizeof(element_typ) * element_anzahl
Allein vorstehende Standard-Definition widerlegt Zwischenraum!
Es sagt lediglich aus, dass der Zwischenraum im sizeof vom
element-Typ mit drin ist.
Helmut Schellong
2018-08-19 19:54:10 UTC
Permalink
Post by Thomas Koenig
Post by Helmut Schellong
sizeof(array) == sizeof(element_typ) * element_anzahl
Allein vorstehende Standard-Definition widerlegt Zwischenraum!
Es sagt lediglich aus, dass der Zwischenraum im sizeof vom
element-Typ mit drin ist.
Wenn von 'Zwischenraum im Array' die Rede ist, ist _nicht_
eventueller Zwischenraum in Array-Elementen gemeint!
Du hast den Thread nicht komplett gelesen.

Falls Zwischenraum außerhalb von Array-Elementen vorkäme,
würde obenstehende Gleichung keine Gleichung mehr sein.

Der Inhalt von Array-Elementen spielt bei der Definition
des Arrays keine Rolle:
==================================================================
An array type describes a contiguously allocated nonempty set of
objects with a particular member object type, called the element type.
The element type shall be complete whenever the array type is specified.
Array types are characterized by their element type and
by the number of elements in the array.
An array type is said to be derived from its element type, and if its
element type is T , the array type is sometimes called ‘array of T’.
The construction of an array type from an element type is called
‘‘array type derivation’’.
==================================================================
Der erste Satz im vorstehenden Zitat beweist _ebenfalls_, daß
Arrays lückenlos sind.
--
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
2018-08-17 12:04:37 UTC
Permalink
Post by Оlе Ѕtrеісhеr
Post by Rainer Weikusat
Post by Оlе Ѕtrеісhеr
Post by Helmut Schellong
7.20.1.4 Integer types capable of holding object pointers
The following type designates an unsigned integer type
with the property that any valid pointer to void can be
converted to this type, then converted back to pointer to void,
uintptr_t
These types are optional.
Ordnungsrelationen zwischen Pointern bleiben aber nicht notwendigerweise
bei der Umwandlung in uintptr_t erhalten.
Natuerlich nicht. Da aber Zeiger, die nicht in dasselbe Feld zeigen,
ueberhaupt nicht in dieser Weise verglichen werden duerfen (insofern es
Entweder man hat es mit einer Umbegung zu tun, in der solche Vergleiche
erlaubt sind und es eine bijektive Abbildung von Zeiger nach
vorzeichenlosen Zahlen gibt. Dann kann man (wie bereits mehrfache
geschrieben) ausdruecklich undefiniertes Verhalten durch den
Zahlenvergleich vermeiden.
Eine mögliche bijektive Abbildung wäre z.B.,
Ich bezweifle ernsthaft, das Du nicht verstanden hast, was ich aussagen
wollte, also vergessen wir diesen Teil hier mal.

[...]
Post by Оlе Ѕtrеісhеr
(uintptr_t)a < (uintptr_t)b
ist für a!=b eben undefiniert.
Das ist falsch, denn beides sind Zahlen und keine Zeiger. Das Ergebnis
ist implementierungsabhaengig aber die Operation hat ein definiertes
Verhalten (Vergleich von zwei Operanden, die einen sogenannten 'realen
Typ' haben).

Inwiefern diese implementierungsabhaengie Ergebnis das gewuenschte
Ergebnis ist, waere eine "quality of implementation"-Angelegenheit. ZB
koennte eine Implementierung waehrend der Konversion eine bitweise
Negation durchfuehren. Falls die mal jemand ausser dem Autor benutzen
sollte, muesste man sich eben darauf einstellen.
Оlе Ѕtrеісhеr
2018-08-17 13:52:00 UTC
Permalink
Post by Rainer Weikusat
Post by Оlе Ѕtrеісhеr
Post by Rainer Weikusat
Post by Оlе Ѕtrеісhеr
Post by Helmut Schellong
7.20.1.4 Integer types capable of holding object pointers
The following type designates an unsigned integer type
with the property that any valid pointer to void can be
converted to this type, then converted back to pointer to void,
uintptr_t
These types are optional.
Ordnungsrelationen zwischen Pointern bleiben aber nicht notwendigerweise
bei der Umwandlung in uintptr_t erhalten.
Natuerlich nicht. Da aber Zeiger, die nicht in dasselbe Feld zeigen,
ueberhaupt nicht in dieser Weise verglichen werden duerfen (insofern es
Entweder man hat es mit einer Umbegung zu tun, in der solche Vergleiche
erlaubt sind und es eine bijektive Abbildung von Zeiger nach
vorzeichenlosen Zahlen gibt. Dann kann man (wie bereits mehrfache
geschrieben) ausdruecklich undefiniertes Verhalten durch den
Zahlenvergleich vermeiden.
Eine mögliche bijektive Abbildung wäre z.B.,
Ich bezweifle ernsthaft, das Du nicht verstanden hast, was ich aussagen
wollte, also vergessen wir diesen Teil hier mal.
[...]
Post by Оlе Ѕtrеісhеr
(uintptr_t)a < (uintptr_t)b
ist für a!=b eben undefiniert.
Das ist falsch, denn beides sind Zahlen und keine Zeiger. Das Ergebnis
ist implementierungsabhaengig aber die Operation hat ein definiertes
Verhalten (Vergleich von zwei Operanden, die einen sogenannten 'realen
Typ' haben).
Damit hast Du natürlich recht. Die konkrete C-Implementierung kann hier
aber beliebig sein -- zum Beispiel so, dass sich konkrete Fälle aus
Sicht des Compilers gut optimieren lassen. Das muss nicht immer dem
entsprechen, was man erwartet.
Post by Rainer Weikusat
Inwiefern diese implementierungsabhaengie Ergebnis das gewuenschte
Ergebnis ist, waere eine "quality of implementation"-Angelegenheit. ZB
koennte eine Implementierung waehrend der Konversion eine bitweise
Negation durchfuehren. Falls die mal jemand ausser dem Autor benutzen
sollte, muesste man sich eben darauf einstellen.
Die Implementation könnte zum Beispiel (wenn noch Bits frei sind, z.B.
48-Bit-Adressierung auf einer 64-Bit-Maschine) die verfügbaren Bits mit
einer Prüfsumme auffüllen und die dafür nutzen, manipulierte Pointer
aufzuspüren. Diese Bits würden dann natürlich auftauchen, wenn man nach
uintptr_t konvertiert, und bei der Konvertierung eines int in einen
Pointer werden sie überprüft.

Wäre vollkommen standardkonform, und würde obiges sicher kaputtmachen.
Wer sagt Dir, dass das nicht in den nächsten 5 Jahren allgemein
eingeführt wird?

Schöne Grüße

Ole
Juergen Ilse
2018-08-17 14:28:11 UTC
Permalink
Hallo,
Post by Rainer Weikusat
Post by Оlе Ѕtrеісhеr
(uintptr_t)a < (uintptr_t)b
ist für a!=b eben undefiniert.
Das ist falsch, denn beides sind Zahlen und keine Zeiger. Das Ergebnis
ist implementierungsabhaengig aber die Operation hat ein definiertes
Verhalten (Vergleich von zwei Operanden, die einen sogenannten 'realen
Typ' haben).
Stimmt, Korrekt waere "unspecified" statt "undefined". Der Wert des
Vergleichs koennte alles moegliche (auch unerwartete) sein (und ist somit
genau genommen voellig bedeutungslos), aber "undefiniertes Verhalten" darf
das von einem standardkonformen Compiler generierte Programm hier nicht
aufweisen (z.B. duerfte es allein aufgrund dieses Vergleichs nicht ab-
stuerzen, was es bei "undefined" durchaus duerfte ...).
Post by Rainer Weikusat
Inwiefern diese implementierungsabhaengie Ergebnis das gewuenschte
Ergebnis ist, waere eine "quality of implementation"-Angelegenheit. ZB
koennte eine Implementierung waehrend der Konversion eine bitweise
Negation durchfuehren. Falls die mal jemand ausser dem Autor benutzen
sollte, muesste man sich eben darauf einstellen.
Ueber die Art der Konvertierung von Pointer zu Ganzzahl aeussert sich
der Standard nicht 8insbesondere legt er nicht fest, ob dabei "<>"
Relationen bei der Konvertierung erhalten bleiben. Und das ist der
Punkt, wo Helmuts Code eben *nicht* portabel (im Sinne von "funktioniert
mit jedem standardkonformen Compiler") ist ...
Oles Beispiel mit "ablegen der Segment-Adresse in den niedrigwertigen Bits"
bei segmentiertem Speicher waere eine (u.U. durchaus sinnvolle) Moeglichkeit,
bei der "<>" Relationen durch dioe Konvertierung von Pointern zu Ganzzahlen
*nicht* erhalten bliebe ...

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
Helmut Schellong
2018-08-16 16:15:56 UTC
Permalink
Post by Оlе Ѕtrеісhеr
Post by Helmut Schellong
7.20.1.4 Integer types capable of holding object pointers
The following type designates an unsigned integer type
with the property that any valid pointer to void can be
converted to this type, then converted back to pointer to void,
uintptr_t
These types are optional.
Ordnungsrelationen zwischen Pointern bleiben aber nicht notwendigerweise
bei der Umwandlung in uintptr_t erhalten. Es ist also
nicht gesagt, dass aus
a < b
(beides void*) folgt, dass
(uintptr_t)a < (uintptr_t)b
Hier widerspreche ich aufgrund von 6.3.2.3 Pointers.
Es werden stets die Bytes eines Objekts/Elements gezählt.
Ein Pointer zeigt stets auf den Objektbeginn (in Byte), sofern
zuvor keine Cast-Manipulationen an ihm stattfanden.

Hier spielen auch Definitionen der Implementation eine Rolle.
Diese geben in der Regel Entwarnung hinsichtlich der pauschalen
Warnungen des Standards.
Post by Оlе Ѕtrеісhеr
es gilt nicht mal notwendigerweise, dass
(uintptr_t)a + 1 == (uintptr_t)(a + 1)
Vorstehendes ist selbstverständlich.
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Оlе Ѕtrеісhеr
2018-08-16 18:25:53 UTC
Permalink
Post by Helmut Schellong
Post by Оlе Ѕtrеісhеr
Post by Helmut Schellong
7.20.1.4 Integer types capable of holding object pointers
The following type designates an unsigned integer type
with the property that any valid pointer to void can be
converted to this type, then converted back to pointer to void,
uintptr_t
These types are optional.
Ordnungsrelationen zwischen Pointern bleiben aber nicht notwendigerweise
bei der Umwandlung in uintptr_t erhalten. Es ist also
nicht gesagt, dass aus
a < b
(beides void*) folgt, dass
(uintptr_t)a < (uintptr_t)b
Hier widerspreche ich aufgrund von 6.3.2.3 Pointers.
Es werden stets die Bytes eines Objekts/Elements gezählt.
Ein Pointer zeigt stets auf den Objektbeginn (in Byte), sofern
zuvor keine Cast-Manipulationen an ihm stattfanden.
Klar doch. a und b sind Pointer.

Aber (uintptr_t)a ist *kein* Pointer, sondern eine Zahl, die nach
irgendeinem nicht weiter festgelegten Algorithmus aus dem Pointer
gewonnen wurde.

Darum kann man über den Größenvergleich von (uintptr_t)a und
(uintptr_t)b keine Aussagen machen, selbst wenn man weiß, dass a<b.
Post by Helmut Schellong
Hier spielen auch Definitionen der Implementation eine Rolle.
Diese geben in der Regel Entwarnung hinsichtlich der pauschalen
Warnungen des Standards.
Post by Оlе Ѕtrеісhеr
es gilt nicht mal notwendigerweise, dass
(uintptr_t)a + 1 == (uintptr_t)(a + 1)
Vorstehendes ist selbstverständlich.
Und folgt aus welchem Teil des Standards?

Ole
Helmut Schellong
2018-08-16 21:24:41 UTC
Permalink
Post by Оlе Ѕtrеісhеr
Post by Helmut Schellong
Post by Оlе Ѕtrеісhеr
Post by Helmut Schellong
7.20.1.4 Integer types capable of holding object pointers
The following type designates an unsigned integer type
with the property that any valid pointer to void can be
converted to this type, then converted back to pointer to void,
uintptr_t
These types are optional.
Ordnungsrelationen zwischen Pointern bleiben aber nicht notwendigerweise
bei der Umwandlung in uintptr_t erhalten. Es ist also
nicht gesagt, dass aus
a < b
(beides void*) folgt, dass
(uintptr_t)a < (uintptr_t)b
Hier widerspreche ich aufgrund von 6.3.2.3 Pointers.
Es werden stets die Bytes eines Objekts/Elements gezählt.
Ein Pointer zeigt stets auf den Objektbeginn (in Byte), sofern
zuvor keine Cast-Manipulationen an ihm stattfanden.
Klar doch. a und b sind Pointer.
Aber (uintptr_t)a ist *kein* Pointer, sondern eine Zahl, die nach
irgendeinem nicht weiter festgelegten Algorithmus aus dem Pointer
gewonnen wurde.
Die Algorithmen bei Typwandlungen sind festgelegt!

Ein Typ-Cast konvertiert den T y p !
Der W e r t wird zwangsläufig nur verändert, wenn der Zieltyp
'schmaler' als für den Wert notwendig ist.
Post by Оlе Ѕtrеісhеr
Darum kann man über den Größenvergleich von (uintptr_t)a und
(uintptr_t)b keine Aussagen machen, selbst wenn man weiß, dass a<b.
Du hast u.a. '6.3.2.3 Pointer' nicht vollständig gelesen
und/oder verstanden.
Gesamtheitliche Gedanken und diverse Ausschlüsse sind hier wichtig.

Mein Algorithmus und der von R.Weikusat prüfen, ob Adressenzahlen
innerhalb des Bereiches der Adressenzahlen eines Arrays liegen.
Ein Array hat vom Beginn bis hinter sein Ende garantiert gleichmäßig
aufsteigende Adressenzahlen ohne Lücken.
Wenn eine Adressenzahl aus dem jeweiligen Array stammt, kann das auch
zweifelsfrei festgestellt werden.

Jeder beliebige Pointer kann in einen Integer verwandelt werden.
Wenn der Typ 'uintptr_t' verfügbar ist, kann das Konversionsergebnis
garantiert repräsentiert werden und der Zahlenbereich reicht aus.
( 7.20.1.4 Integer types capable of holding object pointers )

Alle Elemente eines Arrays haben eine Adresse.
Diese sind aufsteigend bei aufsteigendem Index.
Die Werte (uintptr_t)(void*)(array+x) sind aufsteigend bei steigendem x.
Wenn nun eine zuvor aus _diesem_ Array gespeicherte Adresse verglichen
wird mit (uintptr_t)(void*)(array+0) ... (uintptr_t)(void*)(array+end),
genau so gecastet, so ist das Resultat eindeutig.
Post by Оlе Ѕtrеісhеr
Post by Helmut Schellong
Hier spielen auch Definitionen der Implementation eine Rolle.
Diese geben in der Regel Entwarnung hinsichtlich der pauschalen
Warnungen des Standards.
Post by Оlе Ѕtrеісhеr
es gilt nicht mal notwendigerweise, dass
(uintptr_t)a + 1 == (uintptr_t)(a + 1)
Vorstehendes ist selbstverständlich.
Und folgt aus welchem Teil des Standards?
Ist Dir das unbekannt?
Z.B. 6.5.6 Additive operators
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Оlе Ѕtrеісhеr
2018-08-17 06:59:19 UTC
Permalink
Post by Helmut Schellong
Post by Оlе Ѕtrеісhеr
Post by Helmut Schellong
Post by Оlе Ѕtrеісhеr
Post by Helmut Schellong
7.20.1.4 Integer types capable of holding object pointers
The following type designates an unsigned integer type
with the property that any valid pointer to void can be
converted to this type, then converted back to pointer to void,
uintptr_t
These types are optional.
Ordnungsrelationen zwischen Pointern bleiben aber nicht notwendigerweise
bei der Umwandlung in uintptr_t erhalten. Es ist also
nicht gesagt, dass aus
a < b
(beides void*) folgt, dass
(uintptr_t)a < (uintptr_t)b
Hier widerspreche ich aufgrund von 6.3.2.3 Pointers.
Es werden stets die Bytes eines Objekts/Elements gezählt.
Ein Pointer zeigt stets auf den Objektbeginn (in Byte), sofern
zuvor keine Cast-Manipulationen an ihm stattfanden.
(uintptr_t)a ist aber eben kein Pointer, sondern ein Integer.
Post by Helmut Schellong
Post by Оlе Ѕtrеісhеr
Klar doch. a und b sind Pointer.
Aber (uintptr_t)a ist *kein* Pointer, sondern eine Zahl, die nach
irgendeinem nicht weiter festgelegten Algorithmus aus dem Pointer
gewonnen wurde.
Die Algorithmen bei Typwandlungen sind festgelegt!
Ein Typ-Cast konvertiert den T y p !
Der W e r t wird zwangsläufig nur verändert, wenn der Zieltyp
'schmaler' als für den Wert notwendig ist.
Es gibt nur wenige Festlegungen zur Konversion zwischen Integer und
Pointer:

1. der Integer 0 wird zu einem Pointer konvertiert, der verschieden von
allen Pointern von Objekten ist

2. man kann integers in Pointer konvertieren, das Ergebnis ist aber
(abgesehen vom Nullpointer) implementationsabhängig

3. Man kann Pointer in Integer konvertieren. Das Ergebnis ist
implementationsabhängig.

Das steht alles in 6.3.2.3 Pointers.

Da steht *nichts* drin, was der "Wert" eines Pointers ist, und die
Details der KOnversion sind explizit "implementationsabhängig".

Dem Implementierer steht es frei, bei der Konversion z.B. Prüfbits
hinzuzufügen, das ist vollkommen implementationsabhängig.

Interessanterweise ist nichtmal definiert, dass

(uintptr_t)(void *)0 == 0

gilt.
Post by Helmut Schellong
Post by Оlе Ѕtrеісhеr
Darum kann man über den Größenvergleich von (uintptr_t)a und
(uintptr_t)b keine Aussagen machen, selbst wenn man weiß, dass a<b.
Du hast u.a. '6.3.2.3 Pointer' nicht vollständig gelesen
und/oder verstanden.
Gesamtheitliche Gedanken und diverse Ausschlüsse sind hier wichtig.
Dann leite doch mal (uintptr_t)a<(uintptr_t)b aus a<b mit dem Wortlaut
des Standards her.
Post by Helmut Schellong
Mein Algorithmus und der von R.Weikusat prüfen, ob Adressenzahlen
innerhalb des Bereiches der Adressenzahlen eines Arrays liegen.
C kennt aber keine "Adresszahlen". C kennt Pointer. Und die kann man per
Cast in Zahlen konvertieren. Die Regeln (Größenrelationen) gelten aber
für die *Pointer*, nicht für die Zahlen.
Post by Helmut Schellong
Ein Array hat vom Beginn bis hinter sein Ende garantiert gleichmäßig
aufsteigende Adressenzahlen ohne Lücken.
Das ist nicht spezifiziert. Es ist nur spezifiziert, dass man sie mit
aufsteigenden Pointern ansprechen kann. Die Repräsentation der Pointer
durch Zahlen ist nicht notwendig lückenlos oder aufsteigend.
Post by Helmut Schellong
Wenn eine Adressenzahl aus dem jeweiligen Array stammt, kann das auch
zweifelsfrei festgestellt werden.
Es gibt keine "Adressen" in C. Es gibt Pointer.
Post by Helmut Schellong
Jeder beliebige Pointer kann in einen Integer verwandelt werden.
Aber aufeinanderfolgende Pointer müssen nicht notwendig in
aufeinanderfolgende Integer umgewandelt werden. Das ist
implementationsabhängig.
Post by Helmut Schellong
Post by Оlе Ѕtrеісhеr
Post by Helmut Schellong
Post by Оlе Ѕtrеісhеr
es gilt nicht mal notwendigerweise, dass
(uintptr_t)a + 1 == (uintptr_t)(a + 1)
Vorstehendes ist selbstverständlich.
Und folgt aus welchem Teil des Standards?
Ist Dir das unbekannt?
Z.B. 6.5.6 Additive operators
Abschnitt 8 dort betrifft die Addition von ints zu einem Pointer, und
der sagt nichts über die anschließende Umwandlung des Pointers in
Integers aus.

Homomorpfieerhalt bei der Umwandlung von Pointern in Integers ist
einfach nicht im Standard festgelegt.

Ole
Helmut Schellong
2018-08-17 21:37:46 UTC
Permalink
[...]

**********************************************************************
Alle Zitate aus dem Standard gelten jeweils zusammengenommen
als Teil jeder meiner Antworten.

Es ist ein Fakt, daß zu vielen Fragestellungen zu C
jeweils ein großer Teil des Standards herangezogen werden muß.
Es kommen zwangsläufig Mehrfachnennungen vor.

Die meisten, die sich mit C beschäftigen, kommen damit nicht
klar, und erwarten zu _jeder_ Fragestellung eine separate
und spezialisierte Antwort des Standards, die es nicht
gibt und auch nicht geben kann.
**********************************************************************
Post by Оlе Ѕtrеісhеr
Post by Helmut Schellong
Post by Оlе Ѕtrеісhеr
Post by Helmut Schellong
Hier widerspreche ich aufgrund von 6.3.2.3 Pointers.
Es werden stets die Bytes eines Objekts/Elements gezählt.
Ein Pointer zeigt stets auf den Objektbeginn (in Byte), sofern
zuvor keine Cast-Manipulationen an ihm stattfanden.
(uintptr_t)a ist aber eben kein Pointer, sondern ein Integer.
Das erschreckt mich nicht besonders.

===========================================================================
6.3.2.3 Pointers
Any pointer type may be converted to an integer type.
Except as previously specified, the result is implementation-defined.
If the result cannot be represented in the integer type, the behavior
is undefined.
The result need not be in the range of values of any integer type.

A pointer to an object type may be converted to a pointer to a different
object type.
If the resulting pointer is not correctly aligned68) for the referenced
type, the behavior is undefined.
Otherwise, when converted back again, the result shall compare equal
to the original pointer.
When a pointer to an object is converted to a pointer to a character type,
the result points to the lowest addressed byte of the object.
Successive increments of the result, up to the size of the object, yield
pointers to the remaining bytes of the object.

67) The mapping functions for converting a pointer to an integer
or an integer to a pointer are intended to be consistent
with the addressing structure of the execution environment.
===========================================================================
Der Standard knüpft _nicht nur hier_ eine
Verbindung zwischen Pointer/Adressen und Zahlen.
Oben werden Bytes abgezählt, die Adressen haben.

Konversionen Ptr<>Int sollen auch sinnvoll funktionieren.

Except as previously specified:
Vorbehaltlich vorhergehender Spezifikationen

===========================================================================
6.5.6 Additive operators
The result of the binary + operator is the sum of the operands.

The result of the binary - operator is the difference resulting
from the subtraction of the second operand from the first.

For the purposes of these operators, a pointer to an object
that is not an element of an array behaves the same as a pointer
to the first element of an array of length one with the type
of the object as its element type.

When an expression that has integer type is added to or subtracted
from a pointer, the result has the type of the pointer operand.

If the pointer operand points to an element of an array object, and
the array is large enough, the result points to an element offset
from the original element such that the difference of the subscripts
of the resulting and original array elements equals the integer
expression.
In other words, if the expression P points to the i-th element
of an array object, the expressions (P)+N (equivalently, N+(P)) and
(P)-N (where N has the value n) point to, respectively, the i+n-th
and i−n-th elements of the array object, provided they exist.
Moreover, if the expression P points to the last element of an
array object, the expression (P)+1 points one past the last element
of the array object, and if the expression Q points one past
the last element of an array object, the expression (Q)-1 points
to the last element of the array object.
If both the pointer operand and the result point to elements
of the same array object, or one past the last element of the
array object, the evaluation shall not produce an overflow;
otherwise, the behavior is undefined.
If the result points one past the last element of the array object,
it shall not be used as the operand of a unary * operator
that is evaluated.

When two pointers are subtracted, both shall point to elements
of the same array object, or one past the last element
of the array object; the result is the difference of the subscripts
of the two array elements.
The size of the result is implementation-defined, and its type
(a signed integer type) is ptrdiff_t defined in the <stddef.h> header.
If the result is not representable in an object of that type, the
behavior is undefined.
In other words, if the expressions P and Q point to, respectively, the
i-th and j-th elements of an array object, the expression (P)-(Q) has
the value i−j provided the value fits in an object of type ptrdiff_t.
Moreover, if the expression P points either to an element of an
array object or one past the last element of an array object, and the
expression Q points to the last element of the same array object, the
expression ((Q)+1)-(P) has the same value as ((Q)-(P))+1 and
as -((P)-((Q)+1)), and has the value zero if the expression P
points one past the last element of the array object, even though the
expression (Q)+1 does not point to an element of the array object.106)

106) Another way to approach pointer arithmetic is first to convert
the pointer(s) to character pointer(s): In this scheme the
integer expression added to or subtracted from the converted pointer
is first multiplied by the size of the object originally pointed to, and
the resulting pointer is converted back to the original type.
For pointer subtraction, the result of the difference between the
character pointers is similarly divided by the size of the object
originally pointed to.
When viewed in this way, an implementation need only provide
one extra byte (which may overlap another object in the program)
just after the end of the object in order to satisfy the
‘‘one past the last element’’ requirements.
===========================================================================
Auch vorstehend wird die Lückenlosigkeit eines Arrays definiert.
Weiterhin, daß ein Array aus Elementen eines bestimmten Typs besteht.
Die Elemente sind daher gleich groß: sizeof(element-typ)
Die Adressen des jeweils ersten Byte der Elemente liegen
um sizeof(element-typ) auseinander.
Das konvergiert mit den Integer-Werten im Array[subscript].

ptrdiff_t ist implementations-definiert.
Ja, aber w a s ist hier implementations-definiert?
Nur sizeof(ptrdiff_t) -- sonst nichts!

===========================================================================
7.20.1.4 Integer types capable of holding object pointers
The following type designates a signed integer type with the property
that any valid pointer to void can be converted to this type, then
converted back to pointer to void, and the result will compare equal
to the original pointer:
intptr_t
The following type designates an unsigned integer type with the property
that any valid pointer to void can be converted to this type, then
converted back to pointer to void, and the result will compare equal
to the original pointer:
uintptr_t
These types are optional.
===========================================================================
Die vorstehende Überschrift sollte genau begriffen werden!
Fußnote 67) ist wichtig.
Das nachfolgende Zitat (6.5.8) ist wichtig.
Etc.

===========================================================================
6.5.8 Relational operators
When two pointers are compared, the result depends on the relative
locations in the address space of the objects pointed to.
If two pointers to object types both point to the same object, or both
point one past the last element of the same array object, they compare equal.
If the objects pointed to are members of the same aggregate object,
pointers to structure members declared later compare greater than pointers
to members declared earlier in the structure, and pointers to array elements
with larger subscript values compare greater than pointers to elements
of the same array with lower subscript values.
All pointers to members of the same union object compare equal.
If the expression P points to an element of an array object and the
expression Q points to the last element of the same array object, the
pointer expression Q+1 compares greater than P.
In all other cases, the behavior is undefined.
===========================================================================

===========================================================================
6.5.9 Equality operators
Two pointers compare equal if and only if both are null pointers, both are
pointers to the same object (including a pointer to an object and
a subobject at its beginning) or function, both are pointers to one past
the last element of the same array object, or one is a pointer to one past
the end of one array object and the other is a pointer to the start of a
different array object that happens to immediately follow the first
array object in the address space.109)

109) Two objects may be adjacent in memory because they are adjacent
elements of a larger array or adjacent members of a structure with
no padding between them, or because the implementation chose
to place them so, even though they are unrelated.
If prior invalid pointer operations (such as accesses outside array bounds)
produced undefined behavior, subsequent comparisons also produce undefined
behavior.
===========================================================================

[...]
Post by Оlе Ѕtrеісhеr
3. Man kann Pointer in Integer konvertieren. Das Ergebnis ist
implementationsabhängig.
Das steht alles in 6.3.2.3 Pointers.
Ja, kenne ich, das Zitat stammt von mir.
Daher ist mir bekannt, daß die Implementationsabhängigkeit bedingt ist.

========================================================================
6.5.4 Cast operators
Preceding an expression by a parenthesized type name converts
the value of the expression to the named type.
This construction is called a cast.104)
A cast that specifies no conversion has no effect on the type
or value of an expression.

If the value of the expression is represented with greater range
or precision than required by the type named by the cast (6.3.1.8),
then the cast specifies a conversion even if the type of the expression
is the same as the named type and removes any extra range and precision.
========================================================================
Eine _mögliche_ Wertänderung durch einen Cast
wird im zweiten Absatz erklärt.
Weitere Gründe und Möglichkeiten für eine Wertänderung
sind nicht genannt.
Gäbe es sie, würden sie angegeben sein.

========================================================================
6.2.6.2 Integer types
The precision of an integer type is the number of bits it uses
to represent values, excluding any sign and padding bits.
========================================================================
Ein Cast 'Pointer zu Integer' wird nur einen Wert zum Integer
transportieren.
Post by Оlе Ѕtrеісhеr
Da steht *nichts* drin, was der "Wert" eines Pointers ist, und die
Details der KOnversion sind explizit "implementationsabhängig".
Es ist vielfältig und vielerorts erklärt, was der Wert eines Pointers ist.
Beispielsweise bei 'Relational operators'.
Implementationsabhängigkeit ist überhaupt kein Nachteil.
Wenn die Implementation eine optimale Definition trifft, so ist das gut!
Post by Оlе Ѕtrеісhеr
Dem Implementierer steht es frei, bei der Konversion z.B. Prüfbits
hinzuzufügen, das ist vollkommen implementationsabhängig.
Ja, aber die dürfen dann nicht in Erscheinung treten.
Es geht in den Ausdrücken um Wert-Bits(+Sign-Bit).
Post by Оlе Ѕtrеісhеr
Interessanterweise ist nichtmal definiert, dass
(uintptr_t)(void *)0 == 0
gilt.
Ja, aber das ist egal.
R.Weikusat und ich prüfen, ob ein Pointer aus einem Array stammt.
Vergessen?
Post by Оlе Ѕtrеісhеr
Post by Helmut Schellong
Post by Оlе Ѕtrеісhеr
Darum kann man über den Größenvergleich von (uintptr_t)a und
(uintptr_t)b keine Aussagen machen, selbst wenn man weiß, dass a<b.
Du hast u.a. '6.3.2.3 Pointer' nicht vollständig gelesen
und/oder verstanden.
Gesamtheitliche Gedanken und diverse Ausschlüsse sind hier wichtig.
Dann leite doch mal (uintptr_t)a<(uintptr_t)b aus a<b mit dem Wortlaut
des Standards her.
Das habe ich mit diesem Posting mit all seinen Zitaten getan.
Suchst Du nun einen entsprechenden Satz in:
7.20.1.4 Integer types capable of holding object pointers
Post by Оlе Ѕtrеісhеr
Post by Helmut Schellong
Mein Algorithmus und der von R.Weikusat prüfen, ob Adressenzahlen
innerhalb des Bereiches der Adressenzahlen eines Arrays liegen.
C kennt aber keine "Adresszahlen". C kennt Pointer. Und die kann man per
Cast in Zahlen konvertieren. Die Regeln (Größenrelationen) gelten aber
für die *Pointer*, nicht für die Zahlen.
Das mit den 'Adressenzahlen' ist kein Gegenargument.
R.Weikusat und ich prüfen, ob ein Pointer aus einem Array stammt.
R.Weikusat und ich prüfen, ob eine Adressenzahl innerhalb der
begrenzenden Adressenzahlen eines Arrays liegt.
Beides ist richtig.

'Relational operators' gelten u.a. für Pointer und Integer.
Das ist sogar ein Beweis, daß Pointer auch 'Zahlen' sind.
Post by Оlе Ѕtrеісhеr
Post by Helmut Schellong
Ein Array hat vom Beginn bis hinter sein Ende garantiert gleichmäßig
aufsteigende Adressenzahlen ohne Lücken.
Das ist nicht spezifiziert. Es ist nur spezifiziert, dass man sie mit
aufsteigenden Pointern ansprechen kann. Die Repräsentation der Pointer
durch Zahlen ist nicht notwendig lückenlos oder aufsteigend.
Das ist allein mehrfach in den Zitaten dieses Postings spezifiziert.

Ein Array liegt im Speicher und hat Adressen.
Ein Array ist lückenlos mit seinen Elementen und hat aufsteigende
Adressen für jedes seiner Bytes und Elemente.
Diese Adressen können in Pointer-Variablen gespeichert werden.

Wenn diese Adressen mit (uintptr_t)(void*) versehen werden, dann
sind die resultierenden Zahlen garantiert aufeinanderfolgend
und mit festem Abstand, könnten aber absteigend sein.
Solch eine Formulierung steht nicht im Standard, nein, aber
sie _ergibt sich_ aus dem Standard.

Es ist ausgeschlossen, daß die resultierenden Zahlen in Reihenfolge
beispielsweise lauten: 10245 56 23977 172 1891 ...
Sie sind streng monoton steigend oder fallend, mit
einem festen Abstand, bei Elementen eines Arrays als Quelle.
Post by Оlе Ѕtrеісhеr
Post by Helmut Schellong
Wenn eine Adressenzahl aus dem jeweiligen Array stammt, kann das auch
zweifelsfrei festgestellt werden.
Es gibt keine "Adressen" in C. Es gibt Pointer.
==========================================================================
6.2.8 Alignment of objects
Complete object types have alignment requirements which place
restrictions on the addresses at which objects of that type
may be allocated.
An alignment is an implementation-defined integer value representing the
number of bytes between successive addresses at which a given object
can be allocated.
==========================================================================
Es gibt eben doch Adressen in C.
Nicht nur vorstehend erwähnt.
Ich erkenne auch einen Unterschied zwischen Adressen und Pointern.
Post by Оlе Ѕtrеісhеr
Post by Helmut Schellong
Jeder beliebige Pointer kann in einen Integer verwandelt werden.
Aber aufeinanderfolgende Pointer müssen nicht notwendig in
aufeinanderfolgende Integer umgewandelt werden. Das ist
implementationsabhängig.
Doch. Siehe oben.
Post by Оlе Ѕtrеісhеr
Post by Helmut Schellong
Post by Оlе Ѕtrеісhеr
Post by Helmut Schellong
Post by Оlе Ѕtrеісhеr
es gilt nicht mal notwendigerweise, dass
(uintptr_t)a + 1 == (uintptr_t)(a + 1)
Vorstehendes ist selbstverständlich.
Und folgt aus welchem Teil des Standards?
Ist Dir das unbekannt?
Z.B. 6.5.6 Additive operators
Abschnitt 8 dort betrifft die Addition von ints zu einem Pointer, und
der sagt nichts über die anschließende Umwandlung des Pointers in
Integers aus.
Homomorpfieerhalt bei der Umwandlung von Pointern in Integers ist
einfach nicht im Standard festgelegt.
Hää?!
{(uintptr_t)a} + {1} == (uintptr_t){(a + 1)}

Es geht hier darum, daß erstens eine Zahl zu einer Zahl
addiert wird, und zweitens die gleiche Zahl zu einem Pointer
addiert wird!
D a s ist hier der Unterschied!
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Juergen Ilse
2018-08-18 02:41:00 UTC
Permalink
Hallo,
Post by Helmut Schellong
**********************************************************************
Alle Zitate aus dem Standard gelten jeweils zusammengenommen
als Teil jeder meiner Antworten.
Du solltest den Standard *genauer* lesen, als du es bisher tust.
Post by Helmut Schellong
===========================================================================
6.3.2.3 Pointers
Any pointer type may be converted to an integer type.
Except as previously specified, the result is implementation-defined.
If the result cannot be represented in the integer type, the behavior
is undefined.
The result need not be in the range of values of any integer type.
Sprich es muss kein fuer uintptr_t geeigneter Typ vorhanden sein.
Post by Helmut Schellong
A pointer to an object type may be converted to a pointer to a different
object type.
If the resulting pointer is not correctly aligned68) for the referenced
type, the behavior is undefined.
Otherwise, when converted back again, the result shall compare equal
to the original pointer.
When a pointer to an object is converted to a pointer to a character type,
the result points to the lowest addressed byte of the object.
Successive increments of the result, up to the size of the object, yield
pointers to the remaining bytes of the object.
Hier ist *nirgends* von Adressen die Rede, nur von Pointern und "addressed
bytes" (addressierten Byzes, und die werden in C durch Pointer addressiert,
nicht durch physikalische Adressen).
Post by Helmut Schellong
67) The mapping functions for converting a pointer to an integer
or an integer to a pointer are intended to be consistent
with the addressing structure of the execution environment.
Und was waere nun "inkonsistent with the adressing structure of the
execution environment", wenn z.B. bei segmentiertem Speicher der Offset
in den oberen Bytes und die Segmentadresse in den unteren Bytes abgelegt
wird? In dem Fall waere die von dir vorausgesetzte Relation schon nicht
mehr gegeben.
Post by Helmut Schellong
===========================================================================
Der Standard knüpft _nicht nur hier_ eine
Verbindung zwischen Pointer/Adressen und Zahlen.
Oben werden Bytes abgezählt, die Adressen haben.
Abgezaehöt als Pointer (sprich mit Pointer-Arithmetik, nicht mit Ganzzahl-
arithmetik). Dass das in "Ganzzahl-Arithmetik gleiche oder auch nur aehn-
liche Ergebnisse ergibt, verlangt der Standard eben gerade *nicht*.
Post by Helmut Schellong
Konversionen Ptr<>Int sollen auch sinnvoll funktionieren.
... nur schreibt der Standard das nicht fuer *deine* Interpretation von
"sinnvoll" vor ...
Post by Helmut Schellong
Vorbehaltlich vorhergehender Spezifikationen
===========================================================================
6.5.6 Additive operators
[...]

Irrelevant fuer die Frage, ob der Standard *zwingend* erfordert, dass das
Resultat von <> Relationen bei der vorherigen Konvertierung nach uintptr_t
erhalten bleiben muss.
Post by Helmut Schellong
106) Another way to approach pointer arithmetic is first to convert
the pointer(s) to character pointer(s): In this scheme the
integer expression added to or subtracted from the converted pointer
is first multiplied by the size of the object originally pointed to, and
the resulting pointer is converted back to the original type.
For pointer subtraction, the result of the difference between the
character pointers is similarly divided by the size of the object
originally pointed to.
When viewed in this way, an implementation need only provide
one extra byte (which may overlap another object in the program)
just after the end of the object in order to satisfy the
‘‘one past the last element’’ requirements.
Ebenfalls irrelevant fuer oben stehende Frage.
Post by Helmut Schellong
===========================================================================
Auch vorstehend wird die Lückenlosigkeit eines Arrays definiert.
Weiterhin, daß ein Array aus Elementen eines bestimmten Typs besteht.
Wenn man C auf einer Stackmaschine implementieren wuerde, koennte es u.U.
sinnvoll sein, arrays von hohen physikalischen Adressen zu niedrigen phy-
sikalischen Adressen im Süpeicher anzuordnen, vorausgesetzt, Pointer-
Arithmetik und Pointervergleiche werden entsprechend angepasst, dass
die <Abforderungen des Standards an Pointer Arithmetik und Pointer-
Vergleiche wieder erfuellt sind. Deine Vorstellungen bzgl. Konvertierung
zwischen Pointer und Integer Typen (und welche Eigenschaften dabei gegeben
sein sollten) waeren dabei zwar nicht unbedingt erfuellt, aber die Anfor-
derungen des Standards koennten dabei sehr wohl erfuellt sein.
Post by Helmut Schellong
===========================================================================
7.20.1.4 Integer types capable of holding object pointers
The following type designates a signed integer type with the property
that any valid pointer to void can be converted to this type, then
converted back to pointer to void, and the result will compare equal
intptr_t
The following type designates an unsigned integer type with the property
that any valid pointer to void can be converted to this type, then
converted back to pointer to void, and the result will compare equal
uintptr_t
These types are optional.
Aus dem oben stehenden folgt noch nicht einmal zwingend, dass der selbe
Pointer mehrfach zu intptr_t oder uintptr_t konvertiert jedesmal zwingend
den selben Wert ergibt. Wenn intptr_t "groesser" ist als ein pointer,
duerften die zusaetzlichen Bits jedes malmit Zufallszahlen gefuellt
wwerden, ohne obenstehendem Absatz aus dem Standard zu widersprechen ...
Post by Helmut Schellong
===========================================================================
Die vorstehende Überschrift sollte genau begriffen werden!
... wie sie dort steht, und keine zusaetlichen Eigenschaften hinzu-
interpretiert werden.
Post by Helmut Schellong
===========================================================================
6.5.8 Relational operators
When two pointers are compared, the result depends on the relative
locations in the address space of the objects pointed to.
If two pointers to object types both point to the same object, or both
point one past the last element of the same array object, they compare equal.
If the objects pointed to are members of the same aggregate object,
pointers to structure members declared later compare greater than pointers
to members declared earlier in the structure, and pointers to array elements
with larger subscript values compare greater than pointers to elements
of the same array with lower subscript values.
All pointers to members of the same union object compare equal.
If the expression P points to an element of an array object and the
expression Q points to the last element of the same array object, the
pointer expression Q+1 compares greater than P.
In all other cases, the behavior is undefined.
===========================================================================
Stimmt. Doch beagt dieser Absatz nur etwas ueber die Relationen zwischen
Pointern, nicht ueber die Ergebnisse entsprechender Relationen zwischen
den intptr_t Werten oder uintptr_t Werten, die man durch Konvertierung
der Pointer erhaelt. Ueber letzteres sagt dieser Absaatz nicht das
geringste aus.
Post by Helmut Schellong
===========================================================================
6.5.9 Equality operators
Two pointers compare equal if and only if both are null pointers, both are
pointers to the same object (including a pointer to an object and
a subobject at its beginning) or function, both are pointers to one past
the last element of the same array object, or one is a pointer to one past
the end of one array object and the other is a pointer to the start of a
different array object that happens to immediately follow the first
array object in the address space.109)
109) Two objects may be adjacent in memory because they are adjacent
elements of a larger array or adjacent members of a structure with
no padding between them, or because the implementation chose
to place them so, even though they are unrelated.
If prior invalid pointer operations (such as accesses outside array bounds)
produced undefined behavior, subsequent comparisons also produce undefined
behavior.
===========================================================================
Same here. Du interpretierst auch in diesen Absatz mehr hinein als der
Standad zwingend fordert. Das mag auf viele Implementierungen zutreffen,
es sind aber standardkonforme Implementierungwen moeglich, die deinen
Vorstellungen *nicht* genuegen. Wenn du dich auf deine Annahmen verlaesst,
ist der resultierende Code nicht mehr portabel im Sinne des Standards
(reagiert auf jeder standardkonformen C-Implementierung gleich).

Du konntest die Einwaende keienswegs entkraeften.

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
Helmut Schellong
2018-08-18 14:08:28 UTC
Permalink
Post by Juergen Ilse
Hallo,
Post by Helmut Schellong
**********************************************************************
Alle Zitate aus dem Standard gelten jeweils zusammengenommen
als Teil jeder meiner Antworten.
Du solltest den Standard *genauer* lesen, als du es bisher tust.
Das könnte eine Fehleinschätzung sein.
Post by Juergen Ilse
Post by Helmut Schellong
===========================================================================
6.3.2.3 Pointers
Any pointer type may be converted to an integer type.
Except as previously specified, the result is implementation-defined.
If the result cannot be represented in the integer type, the behavior
is undefined.
The result need not be in the range of values of any integer type.
Sprich es muss kein fuer uintptr_t geeigneter Typ vorhanden sein.
Ja - und?
In dem Fall darf dieser Typ eben nicht definiert sein.
Das verlangt der Standard.
Post by Juergen Ilse
Post by Helmut Schellong
A pointer to an object type may be converted to a pointer to a different
object type.
If the resulting pointer is not correctly aligned68) for the referenced
type, the behavior is undefined.
Otherwise, when converted back again, the result shall compare equal
to the original pointer.
When a pointer to an object is converted to a pointer to a character type,
the result points to the lowest addressed byte of the object.
Successive increments of the result, up to the size of the object, yield
pointers to the remaining bytes of the object.
Hier ist *nirgends* von Adressen die Rede, nur von Pointern und "addressed
bytes" (addressierten Byzes, und die werden in C durch Pointer addressiert,
nicht durch physikalische Adressen).
Das ist offensichtliche Haarspalterei.
Ich sprach von 'Adressenzahlen'.
Der Standard sagt, daß jedes Byte eines Objektes eine Adresse hat.
Ein Pointer, der auf ein Byte zeigt, muß zuvor mit einer Adresse
des zugehörigen Objektes gesetzt worden sein.
Objekte haben Adressen, die keine Lvalues sind, keine Pointer.
Post by Juergen Ilse
Post by Helmut Schellong
67) The mapping functions for converting a pointer to an integer
or an integer to a pointer are intended to be consistent
with the addressing structure of the execution environment.
Und was waere nun "inkonsistent with the adressing structure of the
execution environment", wenn z.B. bei segmentiertem Speicher der Offset
in den oberen Bytes und die Segmentadresse in den unteren Bytes abgelegt
wird? In dem Fall waere die von dir vorausgesetzte Relation schon nicht
mehr gegeben.
Inkonsistent wäre, wenn '(uintptr_t)(void*)a' aufeinanderfolgend
unterschiedliche Werte liefern würde, bei unverändertem 'a'.

Der Standard verlangt, daß uintptr_t einen Objekt-Pointer
aufnehmen können muß.

Wenn die Implementation zweigeteilte Pointer hat, sollte sie
uintptr_t z.B. als Struktur mit zwei Integern definieren.
Die Implementation weiß stets, ob eine Map-union funktioniert.
Das sagt Fußnote 67) aus.
Post by Juergen Ilse
Post by Helmut Schellong
===========================================================================
Der Standard knüpft _nicht nur hier_ eine
Verbindung zwischen Pointer/Adressen und Zahlen.
Oben werden Bytes abgezählt, die Adressen haben.
Abgezaehöt als Pointer (sprich mit Pointer-Arithmetik, nicht mit Ganzzahl-
arithmetik). Dass das in "Ganzzahl-Arithmetik gleiche oder auch nur aehn-
liche Ergebnisse ergibt, verlangt der Standard eben gerade *nicht*.
Das sagte ich dort auch nicht;
Das mußte ich dort auch nicht sagen.
Zitat ist Zitat, mit bestimmten, meist aufschlußreichen Aussagen.
Ich sagte, daß der Standard Pointer, Byte-Adressen, sizeof
und Integer verknüpft.
Post by Juergen Ilse
Post by Helmut Schellong
===========================================================================
6.5.6 Additive operators
[...]
Irrelevant fuer die Frage, ob der Standard *zwingend* erfordert, dass das
Resultat von <> Relationen bei der vorherigen Konvertierung nach uintptr_t
erhalten bleiben muss.
Dieses Standard-Zitat setzte ich gar nicht ein, um das komplett zu beweisen.
Schon wieder vergessen, was ich ganz oben schrieb?
Die Gesamtheit aller Zitate ist ein Beweis dafür.

Der Standard beschreibt hier intensiv eine Verbindung zwischen
Pointern und Arrays, auch UB, das uintptr_t vermeidet.
Und inside_arr() betrifft genau das.
Post by Juergen Ilse
Post by Helmut Schellong
106) Another way to approach pointer arithmetic is first to convert
the pointer(s) to character pointer(s): In this scheme the
integer expression added to or subtracted from the converted pointer
is first multiplied by the size of the object originally pointed to, and
the resulting pointer is converted back to the original type.
For pointer subtraction, the result of the difference between the
character pointers is similarly divided by the size of the object
originally pointed to.
When viewed in this way, an implementation need only provide
one extra byte (which may overlap another object in the program)
just after the end of the object in order to satisfy the
‘‘one past the last element’’ requirements.
Ebenfalls irrelevant fuer oben stehende Frage.
Schon wieder vergessen, was ich ganz oben schrieb?
Die Gesamtheit aller Zitate ist ein Beweis.

Nach all den Beschreibungen des Standards, die ich im Thread zitierte,
müssen die Werte, die uintptr_t liefert, eine Projektion der
Werte der Pointer sein.
Anders ist das gar nicht möglich!
Post by Juergen Ilse
Post by Helmut Schellong
===========================================================================
Auch vorstehend wird die Lückenlosigkeit eines Arrays definiert.
Weiterhin, daß ein Array aus Elementen eines bestimmten Typs besteht.
Wenn man C auf einer Stackmaschine implementieren wuerde, koennte es u.U.
sinnvoll sein, arrays von hohen physikalischen Adressen zu niedrigen phy-
sikalischen Adressen im Süpeicher anzuordnen, vorausgesetzt, Pointer-
Arithmetik und Pointervergleiche werden entsprechend angepasst, dass
die <Abforderungen des Standards an Pointer Arithmetik und Pointer-
Vergleiche wieder erfuellt sind. Deine Vorstellungen bzgl. Konvertierung
zwischen Pointer und Integer Typen (und welche Eigenschaften dabei gegeben
sein sollten) waeren dabei zwar nicht unbedingt erfuellt, aber die Anfor-
derungen des Standards koennten dabei sehr wohl erfuellt sein.
Das weiß ich seit Jahrzehnten!
Ich schrieb doch bereits, daß die Werte aus uintptr_t
streng monoton steigen oder fallen müssen.
Mit 'fallen' habe ich fallende Objekt-Adressen ab Start-Byte berücksichtigt.
Post by Juergen Ilse
Post by Helmut Schellong
===========================================================================
7.20.1.4 Integer types capable of holding object pointers
The following type designates a signed integer type with the property
that any valid pointer to void can be converted to this type, then
converted back to pointer to void, and the result will compare equal
intptr_t
The following type designates an unsigned integer type with the property
that any valid pointer to void can be converted to this type, then
converted back to pointer to void, and the result will compare equal
uintptr_t
These types are optional.
Aus dem oben stehenden folgt noch nicht einmal zwingend, dass der selbe
Pointer mehrfach zu intptr_t oder uintptr_t konvertiert jedesmal zwingend
den selben Wert ergibt. Wenn intptr_t "groesser" ist als ein pointer,
duerften die zusaetzlichen Bits jedes malmit Zufallszahlen gefuellt
wwerden, ohne obenstehendem Absatz aus dem Standard zu widersprechen ...
Das sind abstruse Vorstellungen.
Aus dem oben Stehenden muß das auch nicht zwingend folgen, denn
das folgt schon aufgrund ganz anderer (genannter) Definitionen.

Die Überschrift oben verlangt, daß ein uintptr_t einen
Objekt-Pointer enthalten können muß.
Das allein reicht schon aus.
Zusätzlich:
=========================================================================
7.20 Integer types <stdint.h>
For each type described herein that the implementation provides,261)
<stdint.h> shall declare that typedef name and define the associated macros.
Conversely, for each type described herein that the implementation
does not provide, <stdint.h> shall not declare that typedef name
nor shall it define the associated macros.
An implementation shall provide those types described as ‘‘required’’, but
need not provide any of the others (described as ‘‘optional’’).
=========================================================================
Vorstehend verlangt der Standard eine typedef-Vereinbarung für uintptr_t.
Es kann also kein Code ver-makro-t werden, sondern nur ein Typ.
Das verhindert die Umsetzung aller obigen abstrusen Vorstellungen.
Post by Juergen Ilse
Post by Helmut Schellong
===========================================================================
Die vorstehende Überschrift sollte genau begriffen werden!
... wie sie dort steht, und keine zusaetlichen Eigenschaften hinzu-
interpretiert werden.
Die Überschrift oben verlangt, daß ein uintptr_t einen
Objekt-Pointer enthalten können muß.
Der muß darin stecken können, komplett, aber nicht unbedingt
mit genau der gleichen Bit-Repräsentation.
Andernfalls könnte solch ein Integer nicht zum Ursprung
zurück konvertiert werden.
Post by Juergen Ilse
Post by Helmut Schellong
===========================================================================
6.5.8 Relational operators
When two pointers are compared, the result depends on the relative
locations in the address space of the objects pointed to.
If two pointers to object types both point to the same object, or both
point one past the last element of the same array object, they compare equal.
If the objects pointed to are members of the same aggregate object,
pointers to structure members declared later compare greater than pointers
to members declared earlier in the structure, and pointers to array elements
with larger subscript values compare greater than pointers to elements
of the same array with lower subscript values.
All pointers to members of the same union object compare equal.
If the expression P points to an element of an array object and the
expression Q points to the last element of the same array object, the
pointer expression Q+1 compares greater than P.
In all other cases, the behavior is undefined.
===========================================================================
Stimmt. Doch beagt dieser Absatz nur etwas ueber die Relationen zwischen
Pointern, nicht ueber die Ergebnisse entsprechender Relationen zwischen
den intptr_t Werten oder uintptr_t Werten, die man durch Konvertierung
der Pointer erhaelt. Ueber letzteres sagt dieser Absaatz nicht das
geringste aus.
Das weiß ich.
Es muß doch nicht jeder Absatz aus dem Standard jeweils beweisen, wie
sich uintptr_t genau verhält.
Die Standard-Zitate ergeben aber in ihrer Gesamtheit ein zwingendes
Gesamtbild.
Es geht um inside_arr() - ich wiederhole mich: Array!
Der vorstehende Absatz enthält wesentlich mehr klärende Information
als lediglich Pointer-Relationen.

[...]
Post by Juergen Ilse
Du konntest die Einwaende keienswegs entkraeften.
Na ja, Deine Meinung.
Meine Einschätzung ist anders.
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Juergen Ilse
2018-08-18 23:51:03 UTC
Permalink
Hallo,
Post by Helmut Schellong
Der Standard verlangt, daß uintptr_t einen Objekt-Pointer
aufnehmen können muß.
Nein, er verlangt, dass uintptr_t einen Integerwert aufnehmen kann, der
durch verlustfreie Konvertierung aus einem void pointer erzeugt wurde.
Das ist ein Unterschied, denn der Standard verlangt keineswegs, dass
bei dieser verlustfreien Konvertierung das Bitmuster zwingend erhalten
bleibt, sondern lediglich, dass bei der Rueckwandlung in einen void
pointer wieder genau der urspruengliche void pointer dabei herauskommt.
Post by Helmut Schellong
Wenn die Implementation zweigeteilte Pointer hat, sollte sie
uintptr_t z.B. als Struktur mit zwei Integern definieren.
Das waere nicht standardkonform, denn uintptr_t muss ein vorzeichenloser
Ganzzahltyp sein (wenn uintptr_t denn existiert), und das trifft auf eine
struktur aus mehreren Elementen eben *nicht* zu.
Post by Helmut Schellong
Das sind abstruse Vorstellungen.
Aus dem oben Stehenden muß das auch nicht zwingend folgen, denn
das folgt schon aufgrund ganz anderer (genannter) Definitionen.
Die Überschrift oben verlangt, daß ein uintptr_t einen
Objekt-Pointer enthalten können muß.
Nein, er verlangt, dass uintptr_t gross genug sein muss, um einen verlustfrei
in eine Ganzzahl konvertierten void pointer aufnehmen kann.
Post by Helmut Schellong
[...]
Post by Juergen Ilse
Du konntest die Einwaende keienswegs entkraeften.
Na ja, Deine Meinung.
Meine Einschätzung ist anders.
Deswegen aber keineswegs richtiger als meine. Ich habe ja gar nichts gegen
Programm-Code, der mehr voraussetzt als der Standard, nur sollte der Autor
sich darueber im klarren sein und das entsprechend dokumentieren. Du siehst
noch nicht einmal ein, dass dein Code mehr als den Wortlaut des Standards
voraussetzt.

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
Michael Bäuerle
2018-08-19 07:19:24 UTC
Permalink
Post by Juergen Ilse
Post by Helmut Schellong
Der Standard verlangt, daß uintptr_t einen Objekt-Pointer
aufnehmen können muß.
Nein, er verlangt, dass uintptr_t einen Integerwert aufnehmen kann, der
durch verlustfreie Konvertierung aus einem void pointer erzeugt wurde.
Das ist ein Unterschied, denn der Standard verlangt keineswegs, dass
bei dieser verlustfreien Konvertierung das Bitmuster zwingend erhalten
bleibt, sondern lediglich, dass bei der Rueckwandlung in einen void
pointer wieder genau der urspruengliche void pointer dabei herauskommt.
Post by Helmut Schellong
Wenn die Implementation zweigeteilte Pointer hat, sollte sie
uintptr_t z.B. als Struktur mit zwei Integern definieren.
Das waere nicht standardkonform, denn uintptr_t muss ein vorzeichenloser
Ganzzahltyp sein (wenn uintptr_t denn existiert), und das trifft auf eine
struktur aus mehreren Elementen eben *nicht* zu.
Wenn das Ergebnis von Rechenoperationen mit dem Zahlenwert eines
uintptr_t keinen sinnvollen Pointer beim zurückkonvertieren ergeben
muss, dann wäre es denkbar die Struktur einfach 1:1 als Bitmuster eines
ausreichend großen vorzeichenlosem Ganzzahltyps zu interpretieren
(im Sinne einer Union, bei der jedes Bit des Pointers auch auf den
Ganzzahl-Typ gemappt ist - anderherum müsste das nicht gegeben sein).

Wenn das eine eindeutige Zuordnung ist, würde zumindest beim zurück-
konvertieren wieder der ursprüngliche Pointer herauskommen.
Helmut Schellong
2018-08-19 10:23:24 UTC
Permalink
Post by Juergen Ilse
Hallo,
Post by Helmut Schellong
Der Standard verlangt, daß uintptr_t einen Objekt-Pointer
aufnehmen können muß.
Nein, er verlangt, dass uintptr_t einen Integerwert aufnehmen kann, der
durch verlustfreie Konvertierung aus einem void pointer erzeugt wurde.
Das ist ein Unterschied, denn der Standard verlangt keineswegs, dass
bei dieser verlustfreien Konvertierung das Bitmuster zwingend erhalten
bleibt, sondern lediglich, dass bei der Rueckwandlung in einen void
pointer wieder genau der urspruengliche void pointer dabei herauskommt.
--------------------------------------------------------
integer types wide enough to hold pointers to objects;
Integer types capable of holding object pointers
--------------------------------------------------------
Wenn ich strikt übersetze, muß ich widersprechen.
Der Standard schreibt nicht: "to hold converted pointers",
sondern eben das, was vorstehend geschrieben ist!
Übersetzung:
Integer-Typen, die breit genug sind, um Zeiger auf Objekte
enthalten zu können.
Post by Juergen Ilse
Post by Helmut Schellong
Wenn die Implementation zweigeteilte Pointer hat, sollte sie
uintptr_t z.B. als Struktur mit zwei Integern definieren.
Das waere nicht standardkonform, denn uintptr_t muss ein vorzeichenloser
Ganzzahltyp sein (wenn uintptr_t denn existiert), und das trifft auf eine
struktur aus mehreren Elementen eben *nicht* zu.
Ich kann dem einerseits zustimmen;
aber streng gesehen kann ja ein Integer 'on the fly' als Resultat
vorliegen, der allerdings erst nach weiteren modifizierenden
Schritten gespeichert wird.

Es kann ja eine implementations-definierte 'union' verwendet
werden, wie ich bereits schrieb.
Der Standard fordert nur einen »Integer-Typ« namens
'uintptr_t': "with the property ..."
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Juergen Ilse
2018-08-17 11:02:01 UTC
Permalink
Hallo,
Post by Helmut Schellong
Die Algorithmen bei Typwandlungen sind festgelegt!
Ein Typ-Cast konvertiert den T y p !
Der W e r t wird zwangsläufig nur verändert, wenn der Zieltyp
'schmaler' als für den Wert notwendig ist.
Das steht so *nicht* im Standard (nicht in dieser Allgemeinheit). Fuer die
Umwandlung zwischen Pointern und Ganzzahltypen legt der Standard rein gar
nichts fest (ausser, dass eine Umwandlung zwischen (void*) und (uintptr_t)
und zurueck moeglich sein muss, sofern es einen Typ uintptr_t gibt, und das
bei Konvertierung eines void pointers nach uintptr_t und zurueck wieder der
selbe void Pointer herauskommen muss). Ob sich dabei das Bitmuster veraendert
oder nicht, sagt der standard exakt *gar* *nichts* aus.
Post by Helmut Schellong
Mein Algorithmus und der von R.Weikusat prüfen, ob Adressenzahlen
innerhalb des Bereiches der Adressenzahlen eines Arrays liegen.
Ein Array hat vom Beginn bis hinter sein Ende garantiert gleichmäßig
aufsteigende Adressenzahlen ohne Lücken.
Du missachtest, dass das nicht zwingend fuer die Werte gilt, die man durch
Konvertierung der Pointer nach (uintptr_t) erhaelt, gelten muss. Zwar ist
es *ueblich*, dass die Relation dadurch erhalten bleibt, der Standard for-
dert das jedoch *nicht* (eben weil er *nicht* fordert, dass bei der Kon-
vertierung zwingend das Bitmuster des Pointers erhalten bleibt).
Post by Helmut Schellong
Alle Elemente eines Arrays haben eine Adresse.
Diese sind aufsteigend bei aufsteigendem Index.
"Aufsteigend" im Sinne der Pointer-Arithmetik. Eine C-Implementierung,
die Feldelemente im Speicher im *absteigender* Reihenfolge der physi-
kalischen Adressen anordnet, waere standardkonform, sofern die Opera-
tionen fuer Pointer-Vergleiche und Pointer-Arithmetik so angepasst waeren,
dass fuer einen Pointer (a+1) der Pointer auf das im Speicher an der
physikalisch naechst-*nierdigeren* Speicheradresse laege. Auch das waere
eine sehr ungewoehnliche aber durchaus standardkonforme Implementierung.
Post by Helmut Schellong
Die Werte (uintptr_t)(void*)(array+x) sind aufsteigend bei steigendem x.
Das verlangt der Standard *nicht*.
Post by Helmut Schellong
Wenn nun eine zuvor aus _diesem_ Array gespeicherte Adresse verglichen
wird mit (uintptr_t)(void*)(array+0) ... (uintptr_t)(void*)(array+end),
genau so gecastet, so ist das Resultat eindeutig.
Durch Wiederholung wird das nicht richtiger. Wenn man alle moeglichen
"Ungewoehnlichkeiten" einer standardkonformen Implementierung berueck-
sichtigen wuerde, waere (uintptr_t) nahezu nutzlos, weil man mit dem
Wert eigentlich nichts sinnvolles anfangen koennte, ausser ihn wieder
zurueck in einen Poiter zu wandeln (weder Vergleiche noch Arithmetik
waeren so verwendebar, dass das Ergebnis portabel waere).

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
Rainer Weikusat
2018-08-17 12:12:09 UTC
Permalink
ole-usenet-***@gmx.net (Оlе Ѕtrеісhеr) writes:

[...]
Post by Оlе Ѕtrеісhеr
es gilt nicht mal notwendigerweise, dass
(uintptr_t)a + 1 == (uintptr_t)(a + 1)
Angenommen a sei ein Zeiger, dann gilt das natuerlich nicht, wenigstens
nicht allgemein:

----------
#include <inttypes.h>
#include <stdio.h>

int a[347];

int main(void)
{
printf("%llu, %llu\n", (unsigned long long)((uintptr_t)a + 1), (unsigned long long)((uintptr_t)(a + 1)));
return 0;
}
---------

weil im zweiten Fall mit einer impliziten Einheit 'Groesse des Typs, auf
den der Zeiger zeigt' gerechnet wird.
Оlе Ѕtrеісhеr
2018-08-17 13:38:42 UTC
Permalink
Post by Rainer Weikusat
[...]
Post by Оlе Ѕtrеісhеr
es gilt nicht mal notwendigerweise, dass
(uintptr_t)a + 1 == (uintptr_t)(a + 1)
Angenommen a sei ein Zeiger, dann gilt das natuerlich nicht, wenigstens
----------
#include <inttypes.h>
#include <stdio.h>
int a[347];
int main(void)
{
printf("%llu, %llu\n", (unsigned long long)((uintptr_t)a + 1),
(unsigned long long)((uintptr_t)(a + 1)));
return 0;
}
---------
weil im zweiten Fall mit einer impliziten Einheit 'Groesse des Typs, auf
den der Zeiger zeigt' gerechnet wird.
Ich hatte ja "void *" als Typ vorausgesetzt.
Rainer Weikusat
2018-08-17 14:02:52 UTC
Permalink
Post by Оlе Ѕtrеісhеr
Post by Rainer Weikusat
[...]
Post by Оlе Ѕtrеісhеr
es gilt nicht mal notwendigerweise, dass
(uintptr_t)a + 1 == (uintptr_t)(a + 1)
Angenommen a sei ein Zeiger, dann gilt das natuerlich nicht, wenigstens
----------
#include <inttypes.h>
#include <stdio.h>
int a[347];
int main(void)
{
printf("%llu, %llu\n", (unsigned long long)((uintptr_t)a + 1),
(unsigned long long)((uintptr_t)(a + 1)));
return 0;
}
---------
weil im zweiten Fall mit einer impliziten Einheit 'Groesse des Typs, auf
den der Zeiger zeigt' gerechnet wird.
Ich hatte ja "void *" als Typ vorausgesetzt.
'void *'-Arithmetik mag in gcc 'definiertes Verhalten' haben, vermutlich
weil den Herrschaften sonst ihr eigener Code um die Ohren floege, aber
das C-Verhalten dieses Muellfeatures ist undefiniert.
Оlе Ѕtrеісhеr
2018-08-17 14:27:06 UTC
Permalink
Post by Rainer Weikusat
Post by Оlе Ѕtrеісhеr
Post by Rainer Weikusat
[...]
Post by Оlе Ѕtrеісhеr
es gilt nicht mal notwendigerweise, dass
(uintptr_t)a + 1 == (uintptr_t)(a + 1)
Angenommen a sei ein Zeiger, dann gilt das natuerlich nicht, wenigstens
----------
#include <inttypes.h>
#include <stdio.h>
int a[347];
int main(void)
{
printf("%llu, %llu\n", (unsigned long long)((uintptr_t)a + 1),
(unsigned long long)((uintptr_t)(a + 1)));
return 0;
}
---------
weil im zweiten Fall mit einer impliziten Einheit 'Groesse des Typs, auf
den der Zeiger zeigt' gerechnet wird.
Ich hatte ja "void *" als Typ vorausgesetzt.
'void *'-Arithmetik mag in gcc 'definiertes Verhalten' haben, vermutlich
weil den Herrschaften sonst ihr eigener Code um die Ohren floege, aber
das C-Verhalten dieses Muellfeatures ist undefiniert.
Zustimmung, mein Fehler.

Ole
Juergen Ilse
2018-08-17 14:30:10 UTC
Permalink
Hallo,
Post by Оlе Ѕtrеісhеr
Ich hatte ja "void *" als Typ vorausgesetzt.
Wenn ich mich richtig erinnere, ist Pointer-Arithmetik mit void Pointern
generell undefiniert ...

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
Helmut Schellong
2018-08-17 22:29:17 UTC
Permalink
Post by Juergen Ilse
Hallo,
Post by Оlе Ѕtrеісhеr
Ich hatte ja "void *" als Typ vorausgesetzt.
Wenn ich mich richtig erinnere, ist Pointer-Arithmetik mit void Pointern
generell undefiniert ...
Ein Bedarf von sizeof(void) ist nicht verarbeitbar.
Post by Juergen Ilse
< >= <= == != funktionieren.
vp+5 vpa-vpb funktionieren nicht.
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Juergen Ilse
2018-08-18 02:43:30 UTC
Permalink
Hallo,
Post by Juergen Ilse
Post by Оlе Ѕtrеісhеr
Ich hatte ja "void *" als Typ vorausgesetzt.
Wenn ich mich richtig erinnere, ist Pointer-Arithmetik mit void Pointern
generell undefiniert ...
< >= <= == != funktionieren.
Das faaellt dann ja auch nicht unter Pointer-Arithmetik sondern Pointer-
Vergleich (was sehr wohl mit void Pointern moeglich ist) ...

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
Juergen Ilse
2018-08-16 12:37:51 UTC
Permalink
Dieser beitrag ist möglicherweise unangemessen. Klicken sie auf, um es anzuzeigen.
Rainer Weikusat
2018-08-16 14:05:08 UTC
Permalink
Post by Juergen Ilse
Post by Helmut Schellong
Vielleicht hat er eine falsche Vorstellung von
'uintptr_t' gehabt.
typedef unsigned long uintptr_t;
oder
typedef unsigned long long uintptr_t;
7.20.1.4 Integer types capable of holding object pointers
The following type designates an unsigned integer type
with the property that any valid pointer to void can be
converted to this type, then converted back to pointer to void,
uintptr_t
These types are optional.
Eben. Der Standard verlangt noch nicht einmal, dass es ueberhaupt einen
Typ mit dieser Eigenschaft gibt. Wenn du dann Code, der diesen Typ benutzt
als "portabel" bezeichnest, ist das schon sehr eigenwillig ...
Es ist jedenfalls portabler, als einfach annzunehmen, Ganzahltyp xyz
"wird scho passe", was der Sinn hinter diesen Typen ist (und desgleichen
der anderen Ganzahltypen mit definierter Breite).
Helmut Schellong
2018-08-16 15:10:14 UTC
Permalink
Post by Juergen Ilse
Hallo,
Post by Helmut Schellong
Vielleicht hat er eine falsche Vorstellung von
'uintptr_t' gehabt.
typedef unsigned long uintptr_t;
oder
typedef unsigned long long uintptr_t;
7.20.1.4 Integer types capable of holding object pointers
The following type designates an unsigned integer type
with the property that any valid pointer to void can be
converted to this type, then converted back to pointer to void,
uintptr_t
These types are optional.
Eben. Der Standard verlangt noch nicht einmal, dass es ueberhaupt einen
Typ mit dieser Eigenschaft gibt. Wenn du dann Code, der diesen Typ benutzt
als "portabel" bezeichnest, ist das schon sehr eigenwillig ...
Eine Definition des Wortes 'Portabilität' wäre vonnöten.

Ich könnte auch definieren, daß Portabilität vorliegt, sobald
ein Quellcode auf zwei verschiedenen Plattformen kompilierbar ist.

Wenn der Typ 'uintptr_t' benutzt wird, kann nicht einfach
behauptet werden, der verwendende Code sei unportabel!
Das ist doch eh nur ein Typ als Service, den es
früher gar nicht gab.

Falls ein Compiler meldete, 'uintptr_t' ist unbekannt, so ergreift
man eben problemlösende Maßnahmen:
Was ich seit Jahrzehnten für solche Zwecke verwende, sind Typen
wie 'unsigned long', die immer da sind.
Oder es werden Zuweisungen an Variablen oder Funktionsparameter
vorgenommen:
unsigned long long test= pointer;
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Stefan Reuther
2018-08-02 17:07:49 UTC
Permalink
Post by Rainer Weikusat
Folgendes sollte allerdings auch 'funktionieren', wenigstens insofern
die technischen Bedingungen gegeben sind und dass man nicht befuerchten
muss, dass sich das ein Compiler-Entwickler wegen "Das ist undefinert
!!1 Wenn wirs nicht machen funktionierts schneller nicht !!2" als
int on_wheel(struct timer *t)
{
return (uintptr_t)t->me - (uintptr_t)timers.wheel < TIMER_WHEEL_SZ;
}
Das klappt halt, wenn der Speicher als ein halbwegs lineares Array
dargestellt wird. Nächste Speicheradresse = 'uintptr_t' um eins
inkrementieren. Dann ist obiges die Möglichkeit, den gewünschten Test
aufzuschreiben, ohne dass einem spitzfindige Compiler eine lange Nase
zeigen.

Bei sowas wie "MS-DOS huge model" dürfte auch das scheitern können: wenn
die Pointer 16 Byte auseinander liegen, kann der eine den Wert
0x12340000 und der andere 0x12350000 haben. Gibt es eigentlich einen
Compiler, der MS-DOS huge model compiliert, und ein uintptr_t-Typedef
mitbringt? :-)


Stefan
Helmut Schellong
2018-08-03 09:18:53 UTC
Permalink
On 08/02/2018 19:07, Stefan Reuther wrote:
[...]
Post by Stefan Reuther
Bei sowas wie "MS-DOS huge model" dürfte auch das scheitern können: wenn
die Pointer 16 Byte auseinander liegen, kann der eine den Wert
0x12340000 und der andere 0x12350000 haben. Gibt es eigentlich einen
Compiler, der MS-DOS huge model compiliert, und ein uintptr_t-Typedef
mitbringt? :-)
Ich hatte mal Borland dafür genommen.
Der hatte das nicht funktionierend kompiliert.
Daraufhin hatte ich alle large-Pointer vor jeder Verwendung
per Hand normiert und 'huge' nicht mehr verwendet.
Das war 100% erfolgreich.
--
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
2018-08-06 14:03:23 UTC
Permalink
Rainer Weikusat <***@talktalk.net> writes:

[...]
Post by Rainer Weikusat
int on_wheel(struct timer *t)
{
return (uintptr_t)t->me - (uintptr_t)timers.wheel < TIMER_WHEEL_SZ;
}
Dieser Code ist kaputt: Weil die beiden Zeiger in Ganzahlen umgewandelt
werden, wird bei der Subtraktion nicht automatisch mit der
Feldelementgroesse skaliert.

------------
#include <inttypes.h>
#include <stdio.h>

enum {
SZ = 20
};

static int a[SZ];

int broken(int *ip)
{
return (uintptr_t)ip - (uintptr_t)a < SZ;
}

int correct(int *ip)
{
return (uintptr_t)ip - (uintptr_t)a < SZ * sizeof(*ip);
}

int complicated(int *ip)
{
return (uintptr_t)ip >= (uintptr_t)a
&& (uintptr_t)ip < (uintptr_t)(a + SZ);
}

int main(void)
{
int *ii;

ii = a;
do {
printf("im Feld %d/ %d (%d)\n", broken(ii), correct(ii), complicated(ii));
} while (++ii - a <SZ);

return 0;
}
--------------

Die Funktionen sind nicht static, weil man sich so einfacher den
erzeugten Code ansehen kann (mit gcc wenigstens).
Juergen Ilse
2018-08-06 14:29:28 UTC
Permalink
Hallo,
Post by Rainer Weikusat
[...]
Post by Rainer Weikusat
int on_wheel(struct timer *t)
{
return (uintptr_t)t->me - (uintptr_t)timers.wheel < TIMER_WHEEL_SZ;
}
Dieser Code ist kaputt: Weil die beiden Zeiger in Ganzahlen umgewandelt
werden, wird bei der Subtraktion nicht automatisch mit der
Feldelementgroesse skaliert.
Ausserdem ist nicht zwingend auf jeder Plattform ein Ganzzahltyp vorhanden,
der einen Pointer verlustfrei aufnehmen koennte. IIRC hatte mindestens eine
Version des WATCOM C Compiler im "32 Bit huge memory model" 48 Bit Zeiger
aber keinen 48 oder 64 Bit Ganzzahltyp ...

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
Rainer Weikusat
2018-08-06 14:33:50 UTC
Permalink
Post by Juergen Ilse
Post by Rainer Weikusat
[...]
Post by Rainer Weikusat
int on_wheel(struct timer *t)
{
return (uintptr_t)t->me - (uintptr_t)timers.wheel < TIMER_WHEEL_SZ;
}
Dieser Code ist kaputt: Weil die beiden Zeiger in Ganzahlen umgewandelt
werden, wird bei der Subtraktion nicht automatisch mit der
Feldelementgroesse skaliert.
Ausserdem ist nicht zwingend auf jeder Plattform ein Ganzzahltyp vorhanden,
der einen Pointer verlustfrei aufnehmen koennte.
Das tangiert mich ausgesprochen peripher :-).
Loading...