Discussion:
Rechtskonsequenzen bei "Heardbleed"?
(zu alt für eine Antwort)
Peter J. Holzer
2014-04-26 10:26:26 UTC
Permalink
Raw Message
[Fup2 de.comp.lang.c]
Emm, mit welchen Programmiersprachen ist denn eine Plausibilitätsprüfung
unmöglich? - Die sollte man tatsächlich meiden wie die Pest!
C. C kennt nämlich gar keine echten Arrays; das ist alles nur
Pointerarithmetik (sonst würde der Code in meiner .sig nämlich gar nicht
funktionieren).
Das stimmt so nicht. C kennt sehr wohl Arrays. Allerdings kannst Du
keine Arrays zuweisen (und damit auch nicht an Funktionen übergeben oder
aus diesen zurückgeben).
Wenn Du z.B. eine Bibliotheksfunktion aufrufst, die ein Array
zurückliefert,
Das kann sie eigentlich gar nicht. Sie kann nur einen Pointer auf ein
(oder in ein) Array zurückliefern.
hast Du keine Möglichkeit zu überprüfen, ob Du wirklich ein Array von
der gewünschten Größe bekommen hast. Alles was Du hast, ist nämlich
ein simpler Pointer.
Ja, aber:

* Der Compiler hätte trotzdem die Möglichkeit, entsprechende Checks zu
generieren. Der C-Standard schreibt nämlich nicht vor, wie ein
"simpler Pointer" zu implementieren ist. Der kann durchaus
Information über die Arraygrenzen beinhalten. Er muss aber auch
nicht, denn Zugriff außerhalb des Arrays ist "undefined behaviour".
Und aus Performance- und Kompatibilitätsgründen ("fat pointers"
würden das ABI ändern) wird das nicht gemacht.

* Als Programmierer hat man natürlich die Möglichkeit, seine
Schnittstellen so zu gestalten, dass diese Informationen mitgegeben
werden. Muss man halt tun und ist lästig.

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Peter J. Holzer
2014-04-26 10:29:48 UTC
Permalink
Raw Message
[Fup2 de.comp.lang.c]
Das könnte man - auch in C - ändern, indem man z.B. die Bibliotheks-
funktion keinen Pointer zurückgeben lässt, sondern eine Struktur, die
noch ein wenig Metainformationen enthält. Man müsste es halt nur
konsequent tun.
Man müßte vor allem einen Haufen Programme umschreiben, wenn man C so
ändert, daß es nicht mehr C ist.
C wäre nach wie vor C. Sowohl, wenn die C-Programmierer das machen (was
Stefan wohl gemeint hat), als auch wenn die Compiler entsprechend
geändert würden. Wir sind hier im Bereich "undefined behaviour" und ein
Programm, das sich da auf ein bestimmtes Verhalten verlässt, ist ohnehin
kein korrektes C-Programm.

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Juergen Ilse
2014-04-26 11:01:59 UTC
Permalink
Raw Message
Hallo,
Post by Peter J. Holzer
[Fup2 de.comp.lang.c]
Das könnte man - auch in C - ändern, indem man z.B. die Bibliotheks-
funktion keinen Pointer zurückgeben lässt, sondern eine Struktur, die
noch ein wenig Metainformationen enthält. Man müsste es halt nur
konsequent tun.
Damit bliebe von C nur noch das uebrig, was vom Standard fuer eine free-
standing implementation gefordert wird, die gesamt libc muesste man nahezu
vollstaendig ersetzen. Das ist allerdings nicht wirklich praktikabel.
Post by Peter J. Holzer
Man müßte vor allem einen Haufen Programme umschreiben, wenn man C so
ändert, daß es nicht mehr C ist.
C wäre nach wie vor C. Sowohl, wenn die C-Programmierer das machen (was
Stefan wohl gemeint hat), als auch wenn die Compiler entsprechend
geändert würden.
Man muesste vor allem nahezu die gesamte libc ersetzen, und ein nicht
unerheblicher Teil des Standards ist davon abhaengig.
Post by Peter J. Holzer
Wir sind hier im Bereich "undefined behaviour" und ein
Programm, das sich da auf ein bestimmtes Verhalten verlässt, ist ohnehin
kein korrektes C-Programm.
Ich gehe davon aus, dass ein nicht unerheblicher Teil des vorhandenen
C-Codes sich auf Eigenschaften verlaesst, die vom C-Standard nicht
festgelegt sind (sprich "undefined" oder "implementation defind"
behaviour), insofern also teils eher "nur zufaellig" korrekt funktioniert.

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
--
Ein Domainname ist nur ein Name, nicht mehr und nicht weniger.
Wer mehr hineininterpretiert, hat das Domain-Name-System nicht
verstanden.
Thomas Koenig
2014-04-26 20:43:58 UTC
Permalink
Raw Message
Post by Juergen Ilse
Ich gehe davon aus, dass ein nicht unerheblicher Teil des vorhandenen
C-Codes sich auf Eigenschaften verlaesst, die vom C-Standard nicht
festgelegt sind (sprich "undefined" oder "implementation defind"
behaviour), insofern also teils eher "nur zufaellig" korrekt funktioniert.
Das ist sicher richtig. Ebenso richtig ist, dass Compilerschreiber
sich viele neue Optimierungen ausdenken, die nach der Sprachnorm
in Ordnung sind warum diverse Programme dann nicht mehr richtig
funktionieren, mit unterschiedlichen Konsequenzen...

Für manche kritische Sachen wäre eine andere Programmiersprache
daher deutlich besser geeignet.
Rainer Weikusat
2014-04-27 15:19:54 UTC
Permalink
Raw Message
Post by Thomas Koenig
Post by Juergen Ilse
Ich gehe davon aus, dass ein nicht unerheblicher Teil des vorhandenen
C-Codes sich auf Eigenschaften verlaesst, die vom C-Standard nicht
festgelegt sind (sprich "undefined" oder "implementation defind"
behaviour), insofern also teils eher "nur zufaellig" korrekt funktioniert.
Das ist sicher richtig. Ebenso richtig ist, dass Compilerschreiber
sich viele neue Optimierungen ausdenken, die nach der Sprachnorm
in Ordnung sind warum diverse Programme dann nicht mehr richtig
funktionieren, mit unterschiedlichen Konsequenzen...
Für manche kritische Sachen wäre eine andere Programmiersprache
daher deutlich besser geeignet.
Daraus, das niemand fehlerhaften Code in Sprachen veroeffentlicht, die
keiner benutzt (vereinfacht) folgt nicht, das weniger fehlerhafter Code
veroeffentlicht werden wuerde, wenn die Leute, die solchen
veroeffentlichen, ihn in einer anderen Sprache schreiben
wuerden. Insbesondere sind die Folgen eines sogenannten
"Programmabsturzes", zB, weil das Laufzeitsystem auf die
Bereichsueberschreitung mit einer Exception reagiert, nicht
notwendigerweise harmloser, als wenn irgendwelcher Datenmuell in der
Gegend herumgeschickt wird. Und Leute, die Eingabedaten von einer nicht
vertrauenswuerdigen Quelle ungeprueft benutzen anstatt vor allem anderen
Code zu schreiben, der sie ueberprueft, implementieren auch keine
Fehlerbehandlung "unnoetig", dh bevor "experimentell nachgewiesen
wurde", dass der Code tatsaechlich einen Programm-Absturz hervorrufen
kann und das nicht als "Ist doch zu 95% zuverlaessig" beseite gewischt
werden kann.
Hans-Peter Diettrich
2014-04-26 13:56:45 UTC
Permalink
Raw Message
Post by Peter J. Holzer
[Fup2 de.comp.lang.c]
Das könnte man - auch in C - ändern, indem man z.B. die Bibliotheks-
funktion keinen Pointer zurückgeben lässt, sondern eine Struktur, die
noch ein wenig Metainformationen enthält. Man müsste es halt nur
konsequent tun.
Man müßte vor allem einen Haufen Programme umschreiben, wenn man C so
ändert, daß es nicht mehr C ist.
C wäre nach wie vor C. Sowohl, wenn die C-Programmierer das machen (was
Stefan wohl gemeint hat), als auch wenn die Compiler entsprechend
geändert würden. Wir sind hier im Bereich "undefined behaviour" und ein
Programm, das sich da auf ein bestimmtes Verhalten verlässt, ist ohnehin
kein korrektes C-Programm.
Um den konkreten Fehler zu beheben, würde keine solche Änderung reichen.
Wenn eine Datenstruktur eine Unterstruktur mit eigener Längenangabe
enthält, kann dafür keine Metainformation eingefügt werden, womit auch
keine automatische Prüfung stattfinden kann. Damit liegt das Problem im
Protokoll selbst, nämlich in den dort definierten Datenstrukturen, wie
ich schon vermutet hatte :-]

DoDi
Martin Schoenbeck
2014-04-26 20:34:17 UTC
Permalink
Raw Message
Hallo Hans-Peter,
Post by Hans-Peter Diettrich
Post by Peter J. Holzer
[Fup2 de.comp.lang.c]
Das könnte man - auch in C - ändern, indem man z.B. die Bibliotheks-
funktion keinen Pointer zurückgeben lässt, sondern eine Struktur, die
noch ein wenig Metainformationen enthält. Man müsste es halt nur
konsequent tun.
Man müßte vor allem einen Haufen Programme umschreiben, wenn man C so
ändert, daß es nicht mehr C ist.
C wäre nach wie vor C. Sowohl, wenn die C-Programmierer das machen (was
Stefan wohl gemeint hat), als auch wenn die Compiler entsprechend
geändert würden. Wir sind hier im Bereich "undefined behaviour" und ein
Programm, das sich da auf ein bestimmtes Verhalten verlässt, ist ohnehin
kein korrektes C-Programm.
Um den konkreten Fehler zu beheben, würde keine solche Änderung reichen.
Das würde sie schon.
Post by Hans-Peter Diettrich
Wenn eine Datenstruktur eine Unterstruktur mit eigener Längenangabe
enthält, kann dafür keine Metainformation eingefügt werden, womit auch
keine automatische Prüfung stattfinden kann.
Es hilft auch nichts, solche Informationen zu prüfen. Beim Kopieren der
Unterstruktur in die Antwort hätte aber die Kopierroutine, der man dann ja
die Zeigerstruktur für den Empfangspuffer sowie den Offset der
Unterstruktur hätte übergeben müssen, dann den Zugriff hinter diesen
Empfangspuffer verhindert.
Post by Hans-Peter Diettrich
Damit liegt das Problem im
Protokoll selbst, nämlich in den dort definierten Datenstrukturen, wie
ich schon vermutet hatte :-]
Die Vermutung bleibt falsch.

Gruß Martin
--
Bitte nicht an der E-Mail-Adresse fummeln, die paßt so.
Rainer Weikusat
2014-04-27 14:59:42 UTC
Permalink
Raw Message
Post by Martin Schoenbeck
Post by Hans-Peter Diettrich
Post by Peter J. Holzer
[Fup2 de.comp.lang.c]
Das könnte man - auch in C - ändern, indem man z.B. die Bibliotheks-
funktion keinen Pointer zurückgeben lässt, sondern eine Struktur, die
noch ein wenig Metainformationen enthält. Man müsste es halt nur
konsequent tun.
Man müßte vor allem einen Haufen Programme umschreiben, wenn man C so
ändert, daß es nicht mehr C ist.
C wäre nach wie vor C. Sowohl, wenn die C-Programmierer das machen (was
Stefan wohl gemeint hat), als auch wenn die Compiler entsprechend
geändert würden. Wir sind hier im Bereich "undefined behaviour" und ein
Programm, das sich da auf ein bestimmtes Verhalten verlässt, ist ohnehin
kein korrektes C-Programm.
Um den konkreten Fehler zu beheben, würde keine solche Änderung reichen.
Das würde sie schon.
Post by Hans-Peter Diettrich
Wenn eine Datenstruktur eine Unterstruktur mit eigener Längenangabe
enthält, kann dafür keine Metainformation eingefügt werden, womit auch
keine automatische Prüfung stattfinden kann.
Es hilft auch nichts, solche Informationen zu prüfen. Beim Kopieren der
Unterstruktur in die Antwort hätte aber die Kopierroutine, der man dann ja
die Zeigerstruktur für den Empfangspuffer sowie den Offset der
Unterstruktur hätte übergeben müssen, dann den Zugriff hinter diesen
Empfangspuffer verhindert.
Oder anders ausgedrueckt: Wenn der Computer Herrn Seggelmann daran
gehindert haette, mittels "schreiben wir erstmal den Code, der die Daten
benutzt, denn Eingabeueberpruefung ist muehselig, langweilig und fuer
meine 'wissenschaftliche Arbeit' bestenfalls nebenrelevant'" fahrlaessig
einen gewissen Schaden anzurichten, denn haette er eben etwas anderes
falsch gemacht. Ggf mit tatsaechlich dramatischen Konsequenzen.

Man huete sich vor Leuten, die keine Fehler machen, denn die lernen nie
etwas dazu.
Peter J. Holzer
2014-04-27 10:28:09 UTC
Permalink
Raw Message
Post by Hans-Peter Diettrich
Post by Peter J. Holzer
Das könnte man - auch in C - ändern, indem man z.B. die Bibliotheks-
funktion keinen Pointer zurückgeben lässt, sondern eine Struktur, die
noch ein wenig Metainformationen enthält. Man müsste es halt nur
konsequent tun.
Man müßte vor allem einen Haufen Programme umschreiben, wenn man C so
ändert, daß es nicht mehr C ist.
C wäre nach wie vor C. Sowohl, wenn die C-Programmierer das machen (was
Stefan wohl gemeint hat), als auch wenn die Compiler entsprechend
geändert würden. Wir sind hier im Bereich "undefined behaviour" und ein
Programm, das sich da auf ein bestimmtes Verhalten verlässt, ist ohnehin
kein korrektes C-Programm.
Um den konkreten Fehler zu beheben, würde keine solche Änderung reichen.
Dir fehlt die Fantasie. Ich hoffe, Du schreibst keinen
sicherheitskritischen Code.
Post by Hans-Peter Diettrich
Wenn eine Datenstruktur eine Unterstruktur mit eigener Längenangabe
enthält, kann dafür keine Metainformation eingefügt werden, womit auch
keine automatische Prüfung stattfinden kann.
Wie die Prüfung mit Fat-Pointern stattfinden kann, habe ich bereits
beschrieben.

Hier die Variante mit einem selbstdefinierten speziellen Typ:

typedef slice { uint8_t *s, size_t l };

slice packet = get_packet();

type = get_uint8(packet, TYPE_OFFSET);
length = get_uint16(packet, LENGTH_OFFSET);

slice fragment = get_slice(packet, FRAGMENT_OFFSET, length);

switch (type) {
...
case TYPE_HEARTBEAT:
uint8_t heartbeat_messageType = get_uint8(fragment, HEARTBEATMESSAGETYPE_OFFSET);
uint16_t heartbeat_payloadlength = get_uint16(fragment, HEARTBEATPAYLOADLENGTH_OFFSET);
slice heartbeat_payload = get_slice(fragment, HEARTBEATPAYLOAD_OFFSET, heartbeat_payloadlength);

if (heartbeat_messageType == HEARTBEATMESSAGETYPE_REQUEST) {
slice response = allocate_slice(HEARTBEATPAYLOAD_OFFSET + heartbeat_payloadlength + HEARTBEATPADDING);
set_uint8(response, HEARTBEATMESSAGETYPE_OFFSET, HEARTBEATMESSAGETYPE_RESPONSE);
set_uint16(response, HEARTBEATPAYLOADLENGTH_OFFSET, heartbeat_payloadlength);
copy_slice(response, HEARTBEATPAYLOAD_OFFSET, heartbeat_payload);
padding = get_slice(response,
HEARTBEATPAYLOAD_OFFSET + heartbeat_payloadlength,
HEARTBEATPADDING));
fill_random(padding);
send_packet(response)
}
...


uint8_t get_uint8(slice s, size_t o) {
if (o >= s.l) fatal();
return s.s[o];
}

uint16_t get_uint16(slice s, size_t o) {
if (o + 1 >= s.l) fatal();
return s.s[o] * 256 + s.s[o + 1]; // network byte order
}

slice get_slice(slice s, size_t o, size_t l) {
if (o + l < o) fatal();
if (o + l > s.o) fatal(); // or use minimum, but ...
slice s1;
s1.s = s.s + o;
s1.l = l;
}

(Nur schnell hingeschrieben. Tipp- und Flüchtigkeitsfehler vorbehalten)

Man beachte, dass ich in der eigentlichen Behandlung des
Heartbeat-Pakets den gleichen Fehler eingebaut habe. Nirgends wird
überprüft, ob heartbeat_payloadlength + 3 + HEARTBEATPADDING < length
ist.

Aber was passiert nun, wenn der Angreifer das Paket:

{ TYPE_HEARTBEAT, 9, { HEARTBEATMESSAGETYPE_REQUEST, 0x2710, "STIRB!" } }

schickt?

get_packet() alloziert einen Slice mit 12 Bytes Länge (der Einfachheit
auf Adresse 0x1000, liest das Paket und liefert den Slice zurück,

get_uint8({s = 0x1000, l = 12}, 0) liefert den Typ (0 ist nicht >= 12);
get_uint16({s = 0x1000, l = 12}, 2) liefert die Länge 9 (2 + 1 ist nicht >= 12);

get_slice({s = 0x1000, l = 12}, 3, 9) liefert { s = 0x1003, l = 9 } ( 3
+ 9 ist nicht < 3 und nicht > 12).

get_uint8({s = 0x1003, l = 9}, 0) liefert den Typ
get_uint16({s = 0x1003, l = 9}, 2) liefert die Länge 10000.

Soweit alles ok, aber jetzt wird es interessant:

Was macht get_slice({s = 0x1003, l = 9}, 3, 10000)?

3 + 10000 ist nicht < 3: OK
3 + 10000 > 9. Bzzt! Game over!

Alternativ könnte get_slice die maximal verfügbare Länge zurückliefern
(das hätte z.B. den Vorteil, dass man mit get_slice(s, offset, -1)
einfach den Rest eines Slices bekommt), dann wäre das Ergebnis { s =
0x1006, l = 6 }. In diesem Fall würde aber der Rest durchlaufen und ein
Antwortpaket mit 10022 Bytes Länge geschickt werden, das 9994 Bytes
enthält, die allocate_slice() geliefert hat! Also keine gute Idee,
selbst wenn sich ein Datenleck leicht dadurch verhindern lässt, dass
allocate_slice() den Speicher ausnullt (was es wahrscheinlich sowieso
tun sollte).

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Rainer Weikusat
2014-04-26 15:00:08 UTC
Permalink
Raw Message
Post by Peter J. Holzer
[Fup2 de.comp.lang.c]
Das könnte man - auch in C - ändern, indem man z.B. die Bibliotheks-
funktion keinen Pointer zurückgeben lässt, sondern eine Struktur, die
noch ein wenig Metainformationen enthält. Man müsste es halt nur
konsequent tun.
Man müßte vor allem einen Haufen Programme umschreiben, wenn man C so
ändert, daß es nicht mehr C ist.
C wäre nach wie vor C. Sowohl, wenn die C-Programmierer das machen (was
Stefan wohl gemeint hat), als auch wenn die Compiler entsprechend
geändert würden. Wir sind hier im Bereich "undefined behaviour" und ein
Programm, das sich da auf ein bestimmtes Verhalten verlässt, ist ohnehin
kein korrektes C-Programm.
Das ist so nicht richtig: Es ist kein 'strikt konformes Programm' und
somit nicht 'maximal portabel'. Warum man im Jahre 2014 darauf
Ruecksicht nehmen sollte, dass sowas

Loading Image...

eine Repraesentation fuer negative Zahlen benutzt hatte, die sich
mittlerweile aehnlich seltsam ausnimmt, wie die Bekleidung der
abgebildeten Eingeborenen, ist nicht ganz einzusehen.]
Peter J. Holzer
2014-04-27 09:07:17 UTC
Permalink
Raw Message
Post by Rainer Weikusat
Post by Peter J. Holzer
[Fup2 de.comp.lang.c]
Man müßte vor allem einen Haufen Programme umschreiben, wenn man C so
ändert, daß es nicht mehr C ist.
C wäre nach wie vor C. Sowohl, wenn die C-Programmierer das machen (was
Stefan wohl gemeint hat), als auch wenn die Compiler entsprechend
geändert würden. Wir sind hier im Bereich "undefined behaviour" und ein
Programm, das sich da auf ein bestimmtes Verhalten verlässt, ist ohnehin
kein korrektes C-Programm.
Das ist so nicht richtig: Es ist kein 'strikt konformes Programm' und
somit nicht 'maximal portabel'. Warum man im Jahre 2014 darauf
Ruecksicht nehmen sollte, dass sowas
http://en.wikipedia.org/wiki/File:IBM_7090_computer.jpg
eine Repraesentation fuer negative Zahlen benutzt hatte, die sich
mittlerweile aehnlich seltsam ausnimmt, wie die Bekleidung der
abgebildeten Eingeborenen, ist nicht ganz einzusehen.]
Das ist implementation defined, nicht undefined. Das, worum es hier
geht, ist eine ganz anderere Kategorie, und ich möchte ein Beispiel
sehen, wo Zugriffe mittels Pointer auf zufällig benachbarte Objekte
sinnvoll sind. Und dann möchte ich den "Haufen Programme" sehen, die
man umschreiben müsste, wenn der Compiler das unterbindet.

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Rainer Weikusat
2014-04-27 15:30:35 UTC
Permalink
Raw Message
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Peter J. Holzer
[Fup2 de.comp.lang.c]
Man müßte vor allem einen Haufen Programme umschreiben, wenn man C so
ändert, daß es nicht mehr C ist.
C wäre nach wie vor C. Sowohl, wenn die C-Programmierer das machen (was
Stefan wohl gemeint hat), als auch wenn die Compiler entsprechend
geändert würden. Wir sind hier im Bereich "undefined behaviour" und ein
Programm, das sich da auf ein bestimmtes Verhalten verlässt, ist ohnehin
kein korrektes C-Programm.
Das ist so nicht richtig: Es ist kein 'strikt konformes Programm' und
somit nicht 'maximal portabel'. Warum man im Jahre 2014 darauf
Ruecksicht nehmen sollte, dass sowas
http://en.wikipedia.org/wiki/File:IBM_7090_computer.jpg
eine Repraesentation fuer negative Zahlen benutzt hatte, die sich
mittlerweile aehnlich seltsam ausnimmt, wie die Bekleidung der
abgebildeten Eingeborenen, ist nicht ganz einzusehen.]
Das ist implementation defined, nicht undefined. Das, worum es hier
geht, ist eine ganz anderere Kategorie, und ich möchte ein Beispiel
sehen, wo Zugriffe mittels Pointer auf zufällig benachbarte Objekte
sinnvoll sind.
Das kommt darauf an, wie man 'zufaellig benachbart' definiert. Spontan
wuerde mir da sowas einfallen:

struct vchar_t {
size_t l;
char *s;
};

vchar_t *
vmalloc(size)
size_t size;
{
vchar_t *var;
size_t want;

want = alloc_size(size); /* round to multiple of 64 */
var = calloc(want, 1);
if (!var) return NULL;

var->l = size;
var->v = size ? (void *)(var + 1) : NULL;

return var;
}

Den Zeiger braeuchte man hier natuerlich eigentlich nicht, aber um den
entfernen zu koennen, muesste ein Menge (ca 68.500 Zeilen) Code
ueberprueft und ggf geaendert werden (Sinn von diesem Code ist es, einen
bestimmten 'lange laufenden Prozess' daran zu hindern, sich zu
uebermaessig aufzublaehen, weil der malloc-heap 'fein gehaeckselt'
wurde).
Peter J. Holzer
2014-04-27 17:03:04 UTC
Permalink
Raw Message
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Peter J. Holzer
[Fup2 de.comp.lang.c]
Man müßte vor allem einen Haufen Programme umschreiben, wenn man C so
ändert, daß es nicht mehr C ist.
C wäre nach wie vor C. Sowohl, wenn die C-Programmierer das machen (was
Stefan wohl gemeint hat), als auch wenn die Compiler entsprechend
geändert würden. Wir sind hier im Bereich "undefined behaviour" und ein
Programm, das sich da auf ein bestimmtes Verhalten verlässt, ist ohnehin
kein korrektes C-Programm.
[...]
Post by Rainer Weikusat
Post by Peter J. Holzer
Das ist implementation defined, nicht undefined. Das, worum es hier
geht, ist eine ganz anderere Kategorie, und ich möchte ein Beispiel
sehen, wo Zugriffe mittels Pointer auf zufällig benachbarte Objekte
sinnvoll sind.
Das kommt darauf an, wie man 'zufaellig benachbart' definiert. Spontan
struct vchar_t {
size_t l;
char *s;
};
vchar_t *
vmalloc(size)
size_t size;
{
vchar_t *var;
size_t want;
want = alloc_size(size); /* round to multiple of 64 */
var = calloc(want, 1);
if (!var) return NULL;
var->l = size;
var->v = size ? (void *)(var + 1) : NULL;
return var;
}
Wo siehst Du da undefined behaviour?

(void *)(var + 1) ist vollkommen ok, solange want >= sizeof(vchar_t)
ist.

(var->v sollte hingegen wohl var->s sein)

Tatsächlich undefined ist der alte "struct hack":

struct foo {
int len;
char s[1];
};

struct foo *p = malloc (sizeof(int) + len);

p->len = len;
memcpy(p->s, whatever, len);

aber den habe ich schätzungsweise seit 1990 nicht mehr in freier
Wildbahn gesehen.

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Rainer Weikusat
2014-04-28 11:02:14 UTC
Permalink
Raw Message
[...]
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Peter J. Holzer
und ich möchte ein Beispiel
sehen, wo Zugriffe mittels Pointer auf zufällig benachbarte Objekte
sinnvoll sind.
Das kommt darauf an, wie man 'zufaellig benachbart' definiert. Spontan
struct vchar_t {
size_t l;
char *s;
};
vchar_t *
vmalloc(size)
size_t size;
{
vchar_t *var;
size_t want;
want = alloc_size(size); /* round to multiple of 64 */
var = calloc(want, 1);
if (!var) return NULL;
var->l = size;
var->v = size ? (void *)(var + 1) : NULL;
return var;
}
Wo siehst Du da undefined behaviour?
Technisch gesehen hat der Code vermutlich kein undefiniertes Verhalten,
aber das liegt nicht daran, dass es am guten Willen gefehlt haette:
Calloc alloziert Speicher fuer ein Objekt der Groesse

(size + sizeof(vchar_t) + 63) & ~63

Dieser Speicher soll 'suitably aligned for any purpose' sein, also kann
man am Anfang dieses Objektes auch die genannte Struktur
unterbringen. Zeigerarithmetik ist eigentlich nur innerhalb von Feldern
definiert, allerdings kann man diese Struktur 'legal' als Feld der
Laenge 1 ansehen und deswegen die Addresses 'letztes Element + 1'
berechnen. 'Eigentlich' ist das Verhalten undefiniert, falls man einen
solchermassen erzeugten Zeiger benutzt. Aber in diesem Fall zeigt er
'zufaellig' in den Rest des urspruenglich allozierten Objektes und auf
diesen via char * in der genannten Weise zuzugreifen, ist erlaubt.
Post by Peter J. Holzer
(void *)(var + 1) ist vollkommen ok, solange want >= sizeof(vchar_t)
ist.
(var->v sollte hingegen wohl var->s sein)
->v ist korrekt und bei der Strukturdefinition fehlt auch noch der
vchar_t-typedef. Die Strukturdefinition habe ich aus dem Kopf so
(falsch) hingeschrieben, den anderen Beispielcode aber aus der
entsprechenden Datei kopiert.
Peter J. Holzer
2014-04-29 19:23:19 UTC
Permalink
Raw Message
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Peter J. Holzer
und ich möchte ein Beispiel
sehen, wo Zugriffe mittels Pointer auf zufällig benachbarte Objekte
sinnvoll sind.
Das kommt darauf an, wie man 'zufaellig benachbart' definiert. Spontan
struct vchar_t {
size_t l;
char *s;
};
vchar_t *
vmalloc(size)
size_t size;
{
vchar_t *var;
size_t want;
want = alloc_size(size); /* round to multiple of 64 */
var = calloc(want, 1);
if (!var) return NULL;
var->l = size;
var->v = size ? (void *)(var + 1) : NULL;
return var;
}
Wo siehst Du da undefined behaviour?
Technisch gesehen hat der Code vermutlich kein undefiniertes Verhalten,
Du versuchst absichtlich undefiniertes Verhalten zu produzieren? Das
geht einfacher :-).
Post by Rainer Weikusat
Calloc alloziert Speicher fuer ein Objekt der Groesse
(size + sizeof(vchar_t) + 63) & ~63
Dieser Speicher soll 'suitably aligned for any purpose' sein, also
kann man am Anfang dieses Objektes auch die genannte Struktur
unterbringen. Zeigerarithmetik ist eigentlich nur innerhalb von
Feldern definiert, allerdings kann man diese Struktur 'legal' als Feld
der Laenge 1 ansehen und deswegen die Addresses 'letztes Element + 1'
berechnen.
Nicht nur das. Man kann den von calloc zurückgelieferten Speicher auch
als Feld der Länge (n*64 / sizeof(vchar_t)) ansehen.
Post by Rainer Weikusat
'Eigentlich' ist das Verhalten undefiniert, falls man einen
solchermassen erzeugten Zeiger benutzt.
Da bei einigermaßen realistischem sizeof(vchar_t) var[1] immer
existiert, kann man den Zeiger auch benützen. Er zeigt eben nicht hinter
das letzte Element des Arrays, sondern auf das zweite.
Post by Rainer Weikusat
Aber in diesem Fall zeigt er 'zufaellig'
Nein, nicht zufällig.
Post by Rainer Weikusat
in den Rest des urspruenglich allozierten Objektes und auf diesen via
char * in der genannten Weise zuzugreifen, ist erlaubt.
Die gesamte Pointer-Arithmetik spielt sich innerhalb eines Objekts ab
und es gibt keine Operationen mit undefiniertem Verhalten. Alles im
grünen Bereich.

Man könnte darüber streiten, ob es immer noch definiertes Verhalten
wäre, wenn Du nicht auf ein Vielfaches von 64 aufrundest (oder wenn
sizeof(vchar_t) > 64 ist). Dann könnte nämlich var[1] nur teilweise
existieren. Ich glaube, es ist immer noch definiert: (var + 1) kann ich
jedenfalls berechnen ('letztes Element + 1'-Regel), das auf (void*)
casten ebenfalls, das dann einem char * zuweisen ebenfalls, und das
zeigt wiederum in das ursprüngliche Objekt, man kann also darauf
zugreifen. Aber ich wäre nur mäßig überrascht, wenn jemand ein Loch in
diese Argumentationskette schießen würde.

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Rainer Weikusat
2014-04-30 20:15:24 UTC
Permalink
Raw Message
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Peter J. Holzer
und ich möchte ein Beispiel
sehen, wo Zugriffe mittels Pointer auf zufällig benachbarte Objekte
sinnvoll sind.
Das kommt darauf an, wie man 'zufaellig benachbart' definiert. Spontan
struct vchar_t {
size_t l;
char *s;
};
vchar_t *
vmalloc(size)
size_t size;
{
vchar_t *var;
size_t want;
want = alloc_size(size); /* round to multiple of 64 */
var = calloc(want, 1);
if (!var) return NULL;
var->l = size;
var->v = size ? (void *)(var + 1) : NULL;
return var;
}
Wo siehst Du da undefined behaviour?
Technisch gesehen hat der Code vermutlich kein undefiniertes Verhalten,
Du versuchst absichtlich undefiniertes Verhalten zu produzieren? Das
geht einfacher :-).
Wenn ich C benutze, dann meistens, weil ich einen Grund habe,
'Speicherverwaltung' selber zu machen, und ich schreiben Code dann immer
basierend auf der Annahme, dass ein 'Zeiger' 'grundsaetzlich' eine
vorzeichenlose Ganzzahl ist, die eine Distanz vom Beginn meines
Addressraumes ausdrueckt. Wenn der resultierende Code zufaellig kein
undefiniertes Verhalten hat, schadet das auch nichts.
Post by Peter J. Holzer
Post by Rainer Weikusat
Calloc alloziert Speicher fuer ein Objekt der Groesse
(size + sizeof(vchar_t) + 63) & ~63
Dieser Speicher soll 'suitably aligned for any purpose' sein, also
kann man am Anfang dieses Objektes auch die genannte Struktur
unterbringen. Zeigerarithmetik ist eigentlich nur innerhalb von
Feldern definiert, allerdings kann man diese Struktur 'legal' als Feld
der Laenge 1 ansehen und deswegen die Addresses 'letztes Element + 1'
berechnen.
Nicht nur das. Man kann den von calloc zurückgelieferten Speicher auch
als Feld der Länge (n*64 / sizeof(vchar_t)) ansehen.
Guter Einwand. Aber ich halte das immer noch fuer eine Nutzungsweise,
die zwar mit dem Wortlaut der C-Norm konform ist, aber die
dahinterstehende Intention (kraeftig) mit Fuessen tritt.
Peter J. Holzer
2014-05-01 13:01:06 UTC
Permalink
Raw Message
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
Calloc alloziert Speicher fuer ein Objekt der Groesse
(size + sizeof(vchar_t) + 63) & ~63
Dieser Speicher soll 'suitably aligned for any purpose' sein, also
kann man am Anfang dieses Objektes auch die genannte Struktur
unterbringen. Zeigerarithmetik ist eigentlich nur innerhalb von
Feldern definiert, allerdings kann man diese Struktur 'legal' als Feld
der Laenge 1 ansehen und deswegen die Addresses 'letztes Element + 1'
berechnen.
Nicht nur das. Man kann den von calloc zurückgelieferten Speicher auch
als Feld der Länge (n*64 / sizeof(vchar_t)) ansehen.
Guter Einwand. Aber ich halte das immer noch fuer eine Nutzungsweise,
die zwar mit dem Wortlaut der C-Norm konform ist, aber die
dahinterstehende Intention (kraeftig) mit Fuessen tritt.
Wäre dem so, wäre calloc (das, wie Signature zeigt, ja explizit dafür
gedacht ist, Arrays von n Elementen zu allozieren) einigermaßen sinnlos.
Das ist - gemeinsam mit der Array/Pointer-Dichtomie in C - durchaus im
Geist von C.

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Rainer Weikusat
2014-05-01 18:40:26 UTC
Permalink
Raw Message
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
Calloc alloziert Speicher fuer ein Objekt der Groesse
(size + sizeof(vchar_t) + 63) & ~63
Dieser Speicher soll 'suitably aligned for any purpose' sein, also
kann man am Anfang dieses Objektes auch die genannte Struktur
unterbringen. Zeigerarithmetik ist eigentlich nur innerhalb von
Feldern definiert, allerdings kann man diese Struktur 'legal' als Feld
der Laenge 1 ansehen und deswegen die Addresses 'letztes Element + 1'
berechnen.
Nicht nur das. Man kann den von calloc zurückgelieferten Speicher auch
als Feld der Länge (n*64 / sizeof(vchar_t)) ansehen.
Guter Einwand. Aber ich halte das immer noch fuer eine Nutzungsweise,
die zwar mit dem Wortlaut der C-Norm konform ist, aber die
dahinterstehende Intention (kraeftig) mit Fuessen tritt.
Wäre dem so, wäre calloc (das, wie Signature zeigt, ja explizit dafür
gedacht ist, Arrays von n Elementen zu allozieren) einigermaßen sinnlos.
Das ist - gemeinsam mit der Array/Pointer-Dichtomie in C - durchaus im
Geist von C.
Wie bereits geschrieben: Ich sehe das anders. Calloc wurde benutzt um
ein Element der Groesse ... anzufordern, nicht um ein Feld von Elementen
einer kleineren Groesse zu allozieren. Das spielt zwar praktisch keine
Rolle, weil der Speicherbereich, dessen Addresse zurueckgegeben wird,
'universell benutztbar' sein soll ('suitably aligned for any purpose')
und die C Speicherverwaltungsroutine eben nicht mit 'Objekten' arbeiten,
sondern mit 'farb- und formlosen' Speicherregionen, in denen ein
Benutzer alles unterbringen kann, was hineinpasst, aber 'dynamische
Speicherverwaltung' stellt hier einen Sonderfall dar denn
'normalerweise' gibt sich die C-Norm grosse Muehe, so zu tun als ob C
irgendetwas von 'typisierten Objekten' wuesste und nicht nur 'irgendwie
interpretierten Bytefolgen mit einer Addresse'. ZB waere das hier
undefiniert (AFAIK):

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

struct triple_shorts {
short s[3];
};

static int numbers[128];
static struct triple_shorts *ts = (void *)(numbers + 4);

int main(void)
{
numbers[4] = 0xdeafdad;
ts->s[1] = 0xbee;
ts[1].s[1] = 0xa;

printf("%x\n", numbers[4]);
printf("%x\n", numbers[6]);

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

obwohl es auch 'passt'.
Peter J. Holzer
2014-05-01 22:12:09 UTC
Permalink
Raw Message
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
Calloc alloziert Speicher fuer ein Objekt der Groesse
(size + sizeof(vchar_t) + 63) & ~63
Dieser Speicher soll 'suitably aligned for any purpose' sein, also
kann man am Anfang dieses Objektes auch die genannte Struktur
unterbringen. Zeigerarithmetik ist eigentlich nur innerhalb von
Feldern definiert, allerdings kann man diese Struktur 'legal' als Feld
der Laenge 1 ansehen und deswegen die Addresses 'letztes Element + 1'
berechnen.
Nicht nur das. Man kann den von calloc zurückgelieferten Speicher auch
als Feld der Länge (n*64 / sizeof(vchar_t)) ansehen.
Guter Einwand. Aber ich halte das immer noch fuer eine Nutzungsweise,
die zwar mit dem Wortlaut der C-Norm konform ist, aber die
dahinterstehende Intention (kraeftig) mit Fuessen tritt.
Wäre dem so, wäre calloc (das, wie Signature zeigt, ja explizit dafür
gedacht ist, Arrays von n Elementen zu allozieren) einigermaßen sinnlos.
Das ist - gemeinsam mit der Array/Pointer-Dichtomie in C - durchaus im
Geist von C.
Wie bereits geschrieben: Ich sehe das anders. Calloc wurde benutzt um
ein Element der Groesse ... anzufordern, nicht um ein Feld von Elementen
einer kleineren Groesse zu allozieren. Das spielt zwar praktisch keine
Rolle,
Es spielt IMNSHO auch theoretisch keine Rolle. Denn was dieses Element
ist, ist ja nicht festgelegt. Es könnte ein Array on vchar_t sein.
Post by Rainer Weikusat
weil der Speicherbereich, dessen Addresse zurueckgegeben wird,
'universell benutztbar' sein soll ('suitably aligned for any purpose')
und die C Speicherverwaltungsroutine eben nicht mit 'Objekten' arbeiten,
sondern mit 'farb- und formlosen' Speicherregionen, in denen ein
Benutzer alles unterbringen kann, was hineinpasst,
So ist es. Und ich glaube nicht, dass das "die Intention (kräftig) mit
Füßen tritt". Das ist genauso beabsichtigt. Die Autoren des Standards
waren schließlich keine Deppen.
Post by Rainer Weikusat
aber 'dynamische Speicherverwaltung' stellt hier einen Sonderfall dar
denn 'normalerweise' gibt sich die C-Norm grosse Muehe, so zu tun als
ob C irgendetwas von 'typisierten Objekten' wuesste und nicht nur
'irgendwie interpretierten Bytefolgen mit einer Addresse'. ZB waere
------------
#include <stdio.h>
struct triple_shorts {
short s[3];
};
static int numbers[128];
static struct triple_shorts *ts = (void *)(numbers + 4);
int main(void)
{
numbers[4] = 0xdeafdad;
ts->s[1] = 0xbee;
Ich nehme an, Du beabsichtigst, dass ts->s[1] und numbers[4] überlappen.
Dann ist das hier undefined behaviour, weil Du ein Objekt mit einem
anderen als seinem effektiven Typ ansprichst (6.5, Absatz 6 und 7).

Ein von malloc zurückgelieferter Speicherbereich hat aber erstmal keinen
effektiven Typ, sondern bekommt den erst dadurch, dass man etwas dort
speichert. Daher ist die Situation dort anders. (Dass der effektive Typ
durch eine Zuweisung bestimmt wird und nicht statisch, ist z.B. auch bei
unions der Fall).

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Rainer Weikusat
2014-05-02 16:46:10 UTC
Permalink
Raw Message
[...]
Post by Peter J. Holzer
Post by Rainer Weikusat
weil der Speicherbereich, dessen Addresse zurueckgegeben wird,
'universell benutztbar' sein soll ('suitably aligned for any purpose')
und die C Speicherverwaltungsroutine eben nicht mit 'Objekten' arbeiten,
sondern mit 'farb- und formlosen' Speicherregionen, in denen ein
Benutzer alles unterbringen kann, was hineinpasst,
So ist es. Und ich glaube nicht, dass das "die Intention (kräftig) mit
Füßen tritt". Das ist genauso beabsichtigt. Die Autoren des Standards
waren schließlich keine Deppen.
Natuerlich ist das "genauso so beabsichtigt", naemlich von dem- oder
denjenigen, die fuer die 'UNIX(*) 7th edition' 'simple, general purpose
memory allocation package' verwantwortlich waren, die auf brk/ sbrk
aufsetzt, was von 'Datentypen' ueberhaupt nichts weiss sondern einfach
das 'Prozessdatensegment' in einem bestimmten Umfang vergroessert.

Soweit sich das mit Hilfe "oeffentlich zugaenglicher Raubkopien" (ca
700km von meinem momentanen Wohnort sollte sich auch noch ein 2. Auflage
K&R befinden, das ich irgendwann mal legal erworben habe) ermitteln
laesst, kennt bzw kannte die korrespondierende C-Definition die
Dichotomie zwischen "magisch typisiertem Speicher, den der
Compiler bereitstellt" und "magisch untypisierten Speicher", den man zur
Laufzeit anfordern kann, allerdings auch noch nicht: Darin heisst es
lediglich lapidar, dass Benutzung eines konvertierten Zeigers, der nicht
den Ausrichtungsanforderungen fuer den neuen Typ entsprich, eine
'address exception' ausloesen koenne.
Post by Peter J. Holzer
Post by Rainer Weikusat
aber 'dynamische Speicherverwaltung' stellt hier einen Sonderfall dar
denn 'normalerweise' gibt sich die C-Norm grosse Muehe, so zu tun als
ob C irgendetwas von 'typisierten Objekten' wuesste und nicht nur
'irgendwie interpretierten Bytefolgen mit einer Addresse'. ZB waere
------------
#include <stdio.h>
struct triple_shorts {
short s[3];
};
static int numbers[128];
static struct triple_shorts *ts = (void *)(numbers + 4);
int main(void)
{
numbers[4] = 0xdeafdad;
ts->s[1] = 0xbee;
Ich nehme an, Du beabsichtigst, dass ts->s[1] und numbers[4] überlappen.
Dann ist das hier undefined behaviour, weil Du ein Objekt mit einem
anderen als seinem effektiven Typ ansprichst (6.5, Absatz 6 und 7).
Das war genau der Grund, den ich im Sinn hatte. Allerdings sind beide
Operationen nur vordringlich 'fundamental verschieden': In beiden
Faellen wird ein Speicherbereich einer bestimmten Groesse reserviert und
mit Nullen gefuellt, einmal macht das der Compiler (bzw erzeugte die
notwendige Information, damit der 'Programmausfuehrungsvorbereiter' das
tun kann), im anderen Fall tut es das Laufzeitsystem. Beide
Speicherbereiche konnten urspruenglich 'legal' fuer beliebige Objekte
benutzt werden. Insofern 'Typ-Information' an die Allokation gebunden
ist, dh fuer den "der Compiler tut es"-Fall, ist dem korrespondieren
Speicherbereich nachtraeglich eine 'Farbe' zugeordnet wurden und ein
Verbot, andersfarbige Dingsbumse darin unterzubringen erlassen wurden
(inwiefern das unter dem Einfluss von "C++-Selbstzwecktypsicherheit"
geschehen ist, waere eine interessante Frage).

Es erscheint mir nicht allzu weit hergeholt, anzunehmen, dass die Leute,
die 'farbigen Speicher' in C eingefuehrt haben, dass auch gerne fuer
malloc & Co getan haetten, wenn es ueberhaupt moeglich gewesen waere.
Peter J. Holzer
2014-05-02 20:19:39 UTC
Permalink
Raw Message
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
weil der Speicherbereich, dessen Addresse zurueckgegeben wird,
'universell benutztbar' sein soll ('suitably aligned for any purpose')
und die C Speicherverwaltungsroutine eben nicht mit 'Objekten' arbeiten,
sondern mit 'farb- und formlosen' Speicherregionen, in denen ein
Benutzer alles unterbringen kann, was hineinpasst,
So ist es. Und ich glaube nicht, dass das "die Intention (kräftig) mit
Füßen tritt". Das ist genauso beabsichtigt. Die Autoren des Standards
waren schließlich keine Deppen.
Natuerlich ist das "genauso so beabsichtigt", naemlich von dem- oder
denjenigen, die fuer die 'UNIX(*) 7th edition' 'simple, general purpose
memory allocation package' verwantwortlich waren, die auf brk/ sbrk
aufsetzt, was von 'Datentypen' ueberhaupt nichts weiss sondern einfach
das 'Prozessdatensegment' in einem bestimmten Umfang vergroessert.
Es ist auch von den Autoren des Standards so beabsichtigt, obwohl denen
brk/sbrk völlig egal war (Zitat aus der Rationale zu C89: "Existing code
is important, existing implementations are not"). malloc/calloc lieferte
einen Speicherblock, den der Programmierer beliebig verwenden konnte -
diese Eigenschaft musste erhalten bleiben. Ob das mit brk/sbrk, mmap
oder sonstwie implementiert wird ist egal.
Post by Rainer Weikusat
Soweit sich das mit Hilfe "oeffentlich zugaenglicher Raubkopien" (ca
700km von meinem momentanen Wohnort sollte sich auch noch ein 2. Auflage
K&R befinden, das ich irgendwann mal legal erworben habe) ermitteln
laesst, kennt bzw kannte die korrespondierende C-Definition die
Dichotomie zwischen "magisch typisiertem Speicher, den der
Compiler bereitstellt" und "magisch untypisierten Speicher", den man zur
K&R II habe ich nicht. Im etwas später erschienenen C89-Standard kommt
das IIRC schon vor. K&R II ist allerdings um einiges dünner als der
Standard und hat nicht den Anspruch, jedes Detail zweifelsfrei zu klären
- der C-Standard schon. Daher wird im C-Standard halt über mehrere
Seiten ein Modell aufgebaut, an Hand dessen man (d.i. der Programmierer
und auch der Compiler-Schreiber) genau bestimmen kann, was gehen soll
und was nicht. Ein Lehrbuch kann das eventuell in ein paar Sätzen
umschreiben.
Post by Rainer Weikusat
Darin heisst es lediglich lapidar, dass Benutzung eines konvertierten
Zeigers, der nicht den Ausrichtungsanforderungen fuer den neuen Typ
entsprich, eine 'address exception' ausloesen koenne.
Das ist etwas anderes.
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
------------
#include <stdio.h>
struct triple_shorts {
short s[3];
};
static int numbers[128];
static struct triple_shorts *ts = (void *)(numbers + 4);
int main(void)
{
numbers[4] = 0xdeafdad;
ts->s[1] = 0xbee;
Ich nehme an, Du beabsichtigst, dass ts->s[1] und numbers[4] überlappen.
Dann ist das hier undefined behaviour, weil Du ein Objekt mit einem
anderen als seinem effektiven Typ ansprichst (6.5, Absatz 6 und 7).
Ich muss mich korrigieren. In Absatz 7 steht

| An object shall have its stored value accessed only by an lvalue
| expression that has one of the following types:

Ich hatte das als "An object shall be accessed ..." gelesen.

Da »ts->s[1] = 0xbee« nicht den *Wert* von numbers[4] anspricht, ist das
nicht undefined (jedenfalls nicht nach dieser Regel), sondern das
undefinierte Verhalten kommt erst weiter unten zustande, wo Du dann
numbers[4] wieder ausliest (den Teil habe ich leider weggeschnippselt).

Mir ist das schon etwas spanisch vorgekommen, aber es hat etwas
gebraucht, bis die Erkenntnis gesickert ist. Meistens kommt man drauf,
dass man den Standard missverstanden hat, wenn man glaubt, dass er etwas
sinnvolles verbietet.
Post by Rainer Weikusat
Das war genau der Grund, den ich im Sinn hatte. Allerdings sind beide
Operationen nur vordringlich 'fundamental verschieden': In beiden
Faellen wird ein Speicherbereich einer bestimmten Groesse reserviert und
mit Nullen gefuellt, einmal macht das der Compiler (bzw erzeugte die
notwendige Information, damit der 'Programmausfuehrungsvorbereiter' das
tun kann), im anderen Fall tut es das Laufzeitsystem. Beide
Speicherbereiche konnten urspruenglich 'legal' fuer beliebige Objekte
benutzt werden. Insofern 'Typ-Information' an die Allokation gebunden
ist, dh fuer den "der Compiler tut es"-Fall, ist dem korrespondieren
Speicherbereich nachtraeglich eine 'Farbe' zugeordnet wurden und ein
Verbot, andersfarbige Dingsbumse darin unterzubringen erlassen wurden
(inwiefern das unter dem Einfluss von "C++-Selbstzwecktypsicherheit"
geschehen ist, waere eine interessante Frage).
Mit Dir zu diskutieren wäre wesentlich angenehmer, wenn Du es schaffen
würdest, einmal ein Posting zu schreiben, in dem keine herablassenden
Seitenhiebe auf Abwesende enthalten wären (die außerdem mit an
Sicherheit grenzender Wahrscheinlichkeit bessere Programmierer sind als
wir beide gemeinsam).

Aber sei's drum. Kann sein, dass C++ da eine Rolle gespielt hat, aber
das ist eher untergeordnet. In C ist auf mehrere Arten möglich
(Pointer-Casts, unions), auf den gleichen Speicherbereich mit
verschiedenen Typen zuzugreifen. Ein Sprach-Standard muss eindeutig
festlegen, unter welchen Bedingungen da ein definiertes Ergebnis
herauskommt (und welches) und unter welchen Bedingungen eben nicht.

Der "farbige Speicher" ist ein Modell, das das Verhalten erklärt, es ist
nicht etwas, das implementiert werden sollte.
Post by Rainer Weikusat
Es erscheint mir nicht allzu weit hergeholt, anzunehmen, dass die Leute,
die 'farbigen Speicher' in C eingefuehrt haben, dass auch gerne fuer
malloc & Co getan haetten, wenn es ueberhaupt moeglich gewesen waere.
Die gleichen Regeln gelten auch für den von malloc zurückgelieferten
Speicher, allerdings wird der erst durch die Benutzung eingefärbt:

int *numbers = calloc(128, sizeof(int));
numbers[4] = 0xdeafdad;
ts->s[1] = 0xbee;
printf("%d", numbers[4]);

zeigt aus genau dem gleichen Grund undefined behaviour: In Zeile zwei
werden die Bytes 16-19 als "int" eingefärbt, in Zeile 3 dann die Bytes
18 und 19 als "short", und schließlich versuchst Du in Zeile 4 die vier
Bytes (die aus einem unvollständigen int und einem short bestehen)
wieder als int anzusprechen. Und was da herauskommt ist undefined.

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Rainer Weikusat
2014-05-04 19:37:45 UTC
Permalink
Raw Message
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
weil der Speicherbereich, dessen Addresse zurueckgegeben wird,
'universell benutztbar' sein soll ('suitably aligned for any purpose')
und die C Speicherverwaltungsroutine eben nicht mit 'Objekten' arbeiten,
sondern mit 'farb- und formlosen' Speicherregionen, in denen ein
Benutzer alles unterbringen kann, was hineinpasst,
So ist es. Und ich glaube nicht, dass das "die Intention (kräftig) mit
Füßen tritt". Das ist genauso beabsichtigt. Die Autoren des Standards
waren schließlich keine Deppen.
Natuerlich ist das "genauso so beabsichtigt", naemlich von dem- oder
denjenigen, die fuer die 'UNIX(*) 7th edition' 'simple, general purpose
memory allocation package' verwantwortlich waren, die auf brk/ sbrk
aufsetzt, was von 'Datentypen' ueberhaupt nichts weiss sondern einfach
das 'Prozessdatensegment' in einem bestimmten Umfang vergroessert.
Es ist auch von den Autoren des Standards so beabsichtigt, obwohl denen
brk/sbrk völlig egal war (Zitat aus der Rationale zu C89: "Existing code
is important, existing implementations are not"). malloc/calloc lieferte
einen Speicherblock, den der Programmierer beliebig verwenden konnte -
diese Eigenschaft musste erhalten bleiben. Ob das mit brk/sbrk, mmap
oder sonstwie implementiert wird ist egal.
Die konnten hier aber in dieser Hinsicht gar nichts mehr beabsichtigen,
denn ein Interface fuer typ-agnostische Speicherallokation gehoerte zu
den Realitaeten, mit denen sie konfrontiert waren, und diese Interface
war deswegen typ-agnostisch, weil es auf einem anderen, typ-agnostischen
Interface aufsetzte, basierend auf dem Konzept, dass ein Prozess ein
'Datensegment' hat, dessen Ursprungsgroesse die Gesamtgroesse der
statischen 'Quelltextallokationen' war, und das bei Bedarf zur Laufzeit
vergroessert werden konnte: Letzten Endes ist das eine Abstraktion des
'mentalen Modells', dessen sich ein Maschinensprachprogrammierer bedient
('Speicher' ist eine passive Resource, in der etwas 'gespeichert' werden
kann, zB Dinge, fuer die man gerade kein Register zur Verfuegung hat
oder benutzen moeche) und die Idee, dass man nicht 'Speicher'
irgendwie organisieren muss, um Daten darin unterbringen zu koennen,
sondern Objekte eines bestimmten Typs zur Laufzeit erzeugt, die
'irgendwo' vermutlich gespeichert sind, aber so genau wissen wir das
nicht und wollen das auch gar nicht wissen, ist auf einer hoeheren
Abstraktionsstufe angesiedelt.

In 'neuerem C' finden sich beide Modelle, denn es gibt sowohl 'irgendwie
angelegte Objekte' als auch 'Speicherallokation'.
Post by Peter J. Holzer
Post by Rainer Weikusat
Soweit sich das mit Hilfe "oeffentlich zugaenglicher Raubkopien" (ca
700km von meinem momentanen Wohnort sollte sich auch noch ein 2. Auflage
K&R befinden, das ich irgendwann mal legal erworben habe) ermitteln
laesst, kennt bzw kannte die korrespondierende C-Definition die
Dichotomie zwischen "magisch typisiertem Speicher, den der
Compiler bereitstellt" und "magisch untypisierten Speicher", den man zur
K&R II habe ich nicht. Im etwas später erschienenen C89-Standard kommt
das IIRC schon vor. K&R II ist allerdings um einiges dünner als der
Standard und hat nicht den Anspruch, jedes Detail zweifelsfrei zu klären
- der C-Standard schon. Daher wird im C-Standard halt über mehrere
Seiten ein Modell aufgebaut, an Hand dessen man (d.i. der Programmierer
und auch der Compiler-Schreiber) genau bestimmen kann, was gehen soll
und was nicht. Ein Lehrbuch kann das eventuell in ein paar Sätzen
umschreiben.
Es gibt darin einen Anhang mit einer Sprachreferenz (der 'R'-Teil), der
diesen Anspruch sehr wohl hat und auch als 'de facto'-Standard galt,
bevor es einen 'de jure'-Standard gab (dh bevor die Telcos die Sprache
in die Finger bekamen und sie mit diesen Fingern durch den Wolf drehten
:->).
Post by Peter J. Holzer
Post by Rainer Weikusat
Darin heisst es lediglich lapidar, dass Benutzung eines konvertierten
Zeigers, der nicht den Ausrichtungsanforderungen fuer den neuen Typ
entsprich, eine 'address exception' ausloesen koenne.
Das ist etwas anderes.
Das ist grundsaetzlich dasselbe denn hier

[...]
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
static int numbers[128];
static struct triple_shorts *ts = (void *)(numbers + 4);
findet eine Zeiger-Konversion statt und diesen Zeiger ...
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Peter J. Holzer
Ich nehme an, Du beabsichtigst, dass ts->s[1] und numbers[4] überlappen.
Dann ist das hier undefined behaviour, weil Du ein Objekt mit einem
anderen als seinem effektiven Typ ansprichst (6.5, Absatz 6 und 7).
Ich muss mich korrigieren. In Absatz 7 steht
| An object shall have its stored value accessed only by an lvalue
Ich hatte das als "An object shall be accessed ..." gelesen.
Da »ts->s[1] = 0xbee« nicht den *Wert* von numbers[4] anspricht, ist das
nicht undefined (jedenfalls nicht nach dieser Regel),
Fehler des Beispiels basierend auf demselben Missverstaendis. Man
koennte

ts->s[1] |= 0xbee

stattdessen benutzen.


... darf man in 'Standard-C' nur sehr eingeschraenkt benutzen, waehrend
die K&R-Sprache lediglich darauf hinweist, das man nicht 'portabel'
annehmen kann, dass der Zeiger beliebig benutzbar sein wird.

[...]
Post by Peter J. Holzer
Post by Rainer Weikusat
"C++-Selbstzwecktypsicherheit"
Mit Dir zu diskutieren wäre wesentlich angenehmer, wenn Du es schaffen
würdest, einmal ein Posting zu schreiben, in dem keine herablassenden
Seitenhiebe auf Abwesende enthalten wären (die außerdem mit an
Sicherheit grenzender Wahrscheinlichkeit bessere Programmierer sind als
wir beide gemeinsam).
Bislang hatte ich C++ fuer eine Programmiersprache gehalten und nicht
fuer eine Person (vielleich gar eine kuenstliche Intelligenz mit boesen
Absichten?)[*]. Und meines Wissen nach verlangt C++ explizite Konvertierung
von void-Zeigern (etc), "weil das typsicher sei, was eine gute Sache
sei" (ohne weiter zu begruenden, warum dem so waere). Deswegen
"Selbstzwecktypsicherheit". Allerdings habe ich mich mit C++ seit bald
fuenfzehn Jahren nicht mehr naeher befasst (groessenteils weil ich
umstaendehalber gezwungen war, einen kleinen Haufen C-Code zu schreiben,
und halb ueberrascht feststellte, dass das auch ging) und das koennte
sich mittlerweile geaendert haben.

[*] Ich wuesste nicht mal genau, welche Person hier gemeint sein
sollte.

<ironie>
Im uebrigen bin ich selbsredend ein besserer Programmierer als jeder,
der das vor mir schon war, denn ich kann alles lernen, was jemand
frueher schon mal richtig gemacht hat, alles vermeiden, was sich
hinterher als falsch herausstellte, und umgekehrt ist das im allgemeinen
nicht der Fall: Wer irgendwann mal etwas richtig gemacht, neigt dazu,
davon auszugehen, alles richtig zu machen und anzunehmen, das andere
immer dann etwas falsch machen, wenn sie nicht das tun, was er selber in
dieser Situation getan haette [**].

[**] Was bemerkenswert kuriose Blueten treiben kann: ZB gibt es nach
Ansicht des Linux-kernels wenigstens zwei unterschiedliche Methoden, wie
man mit blockierende und nicht-blockierende I/O gemeinsam behandeln
kann, die sich gegenseitig widersprechen. Richtig sind allerdings
beide, und falsch ist aber alles, was jemand gegen die sich momentan in Sicht
befindende Methode vorbringt --- sogar dann, wenn es die andere
'jedenfalls richtige' Methode ist.
</ironie>

[...]
Post by Peter J. Holzer
Post by Rainer Weikusat
Es erscheint mir nicht allzu weit hergeholt, anzunehmen, dass die Leute,
die 'farbigen Speicher' in C eingefuehrt haben, dass auch gerne fuer
malloc & Co getan haetten, wenn es ueberhaupt moeglich gewesen waere.
Die gleichen Regeln gelten auch für den von malloc zurückgelieferten
int *numbers = calloc(128, sizeof(int));
numbers[4] = 0xdeafdad;
ts->s[1] = 0xbee;
printf("%d", numbers[4]);
zeigt aus genau dem gleichen Grund undefined behaviour: In Zeile zwei
werden die Bytes 16-19 als "int" eingefärbt, in Zeile 3 dann die Bytes
18 und 19 als "short", und schließlich versuchst Du in Zeile 4 die vier
Bytes (die aus einem unvollständigen int und einem short bestehen)
wieder als int anzusprechen. Und was da herauskommt ist undefined.
Sogar nach den C-Regeln fuer 'dynamische Typ-Assiozation' besteht da ein
Unterschied:

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

static int nn[128];

int main(void)
{
int *n;
short *s;

n = calloc(128, sizeof(int));

s = (short *)n;
printf("%d\n", s[1]); /* fine */

*n = 0;
printf("%d\n", s[1]); /* undefined, effective type is now int */

s = (short *)nn;
printf("%d\n", s[1]); /* always undefined */

return 0;
}
Peter J. Holzer
2014-05-04 21:43:24 UTC
Permalink
Raw Message
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
weil der Speicherbereich, dessen Addresse zurueckgegeben wird,
'universell benutztbar' sein soll ('suitably aligned for any purpose')
und die C Speicherverwaltungsroutine eben nicht mit 'Objekten' arbeiten,
sondern mit 'farb- und formlosen' Speicherregionen, in denen ein
Benutzer alles unterbringen kann, was hineinpasst,
So ist es. Und ich glaube nicht, dass das "die Intention (kräftig) mit
Füßen tritt". Das ist genauso beabsichtigt. Die Autoren des Standards
waren schließlich keine Deppen.
Natuerlich ist das "genauso so beabsichtigt", naemlich von dem- oder
denjenigen, die fuer die 'UNIX(*) 7th edition' 'simple, general purpose
memory allocation package' verwantwortlich waren, die auf brk/ sbrk
aufsetzt, was von 'Datentypen' ueberhaupt nichts weiss sondern einfach
das 'Prozessdatensegment' in einem bestimmten Umfang vergroessert.
Es ist auch von den Autoren des Standards so beabsichtigt, obwohl denen
brk/sbrk völlig egal war (Zitat aus der Rationale zu C89: "Existing code
is important, existing implementations are not"). malloc/calloc lieferte
einen Speicherblock, den der Programmierer beliebig verwenden konnte -
diese Eigenschaft musste erhalten bleiben. Ob das mit brk/sbrk, mmap
oder sonstwie implementiert wird ist egal.
Die konnten hier aber in dieser Hinsicht gar nichts mehr beabsichtigen,
denn ein Interface fuer typ-agnostische Speicherallokation gehoerte zu
den Realitaeten, mit denen sie konfrontiert waren,
Deiner Meinung nach haben sie ja die Semantik bei anderen Operationen
auch geändert (um nicht zu sagen, "kaputtgemacht"), wie Du des langen
und breiten ausführst. Hätten sie bei malloc natürlich auch gekonnt.

Wollten sie aber nicht, denn sie wollten die Semantik (ganz generell,
nicht nur bei malloc) nicht ändern, sondern präzisieren. Es gab damals
schon Dutzende C-Implementationen auf unterschiedlichsten Plattformen,
und da wollte man eben festlegen, worauf man sich als Programmierer
verlassen kann und worauf nicht.

Aber mit Deinem Beißreflex gegen - wogegen eigentlich? Gegen alles, was
irgendwie "formell" riecht? - wirst Du das sicher für Unsinn halten.
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
Soweit sich das mit Hilfe "oeffentlich zugaenglicher Raubkopien" (ca
700km von meinem momentanen Wohnort sollte sich auch noch ein 2. Auflage
K&R befinden, das ich irgendwann mal legal erworben habe) ermitteln
laesst, kennt bzw kannte die korrespondierende C-Definition die
Dichotomie zwischen "magisch typisiertem Speicher, den der
Compiler bereitstellt" und "magisch untypisierten Speicher", den man zur
K&R II habe ich nicht. Im etwas später erschienenen C89-Standard kommt
das IIRC schon vor. K&R II ist allerdings um einiges dünner als der
Standard und hat nicht den Anspruch, jedes Detail zweifelsfrei zu klären
- der C-Standard schon. Daher wird im C-Standard halt über mehrere
Seiten ein Modell aufgebaut, an Hand dessen man (d.i. der Programmierer
und auch der Compiler-Schreiber) genau bestimmen kann, was gehen soll
und was nicht. Ein Lehrbuch kann das eventuell in ein paar Sätzen
umschreiben.
Es gibt darin einen Anhang mit einer Sprachreferenz (der 'R'-Teil), der
diesen Anspruch sehr wohl hat und auch als 'de facto'-Standard galt,
bevor es einen 'de jure'-Standard gab (dh bevor die Telcos die Sprache
in die Finger bekamen und sie mit diesen Fingern durch den Wolf drehten
:->).
Dir ist aber schon klar, dass K&R II ziemlich zeitgleich mit dem
Standard entstand und die Autoren sehr gut über den Stand der
Standardisierung informiert waren? K&R II beschreibt im wesentlichen
C89, aber viel weniger detailliert (der Standard hat ca. 200 A4-Seiten.
Wie lang ist der Anhang in K&R II?)
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
Darin heisst es lediglich lapidar, dass Benutzung eines konvertierten
Zeigers, der nicht den Ausrichtungsanforderungen fuer den neuen Typ
entsprich, eine 'address exception' ausloesen koenne.
Das ist etwas anderes.
Das ist grundsaetzlich dasselbe denn hier
[...]
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
static int numbers[128];
static struct triple_shorts *ts = (void *)(numbers + 4);
findet eine Zeiger-Konversion statt und diesen Zeiger ...
[...]
Post by Rainer Weikusat
... darf man in 'Standard-C' nur sehr eingeschraenkt benutzen, waehrend
die K&R-Sprache lediglich darauf hinweist, das man nicht 'portabel'
annehmen kann, dass der Zeiger beliebig benutzbar sein wird.
Naja, so vage formuliert, ist es wieder das gleiche. Im einen Fall darf
man es "nur eingeschränkt" nutzen und im anderen "nicht beliebig". Da
kann ich nun wirklich keinen Unterschied mehr feststellen.

Aber Du hast oben von "Ausrichtungsanforderungen" geschrieben, und das
ist ein konkretes Kriterium, über das man in der Praxis stolpern kann
(nicht auf x86, aber auf allen RISC-Maschinen, für die ich bisher C
geschrieben habe), aber es ist *nicht* das Kriterium, über das wir hier
diskutieren.

Wenn Du das nicht auseinanderhalten kannst, wird die Diskussion ein
bisschen schwierig.
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
"C++-Selbstzwecktypsicherheit"
Mit Dir zu diskutieren wäre wesentlich angenehmer, wenn Du es schaffen
würdest, einmal ein Posting zu schreiben, in dem keine herablassenden
Seitenhiebe auf Abwesende enthalten wären (die außerdem mit an
Sicherheit grenzender Wahrscheinlichkeit bessere Programmierer sind als
wir beide gemeinsam).
Bislang hatte ich C++ fuer eine Programmiersprache gehalten und nicht
fuer eine Person (vielleich gar eine kuenstliche Intelligenz mit boesen
Absichten?)[*].
Diese Programmiersprache ist aber von einer Person erfunden worden (und
später von vielen weiterentwickelt). Es spielt keine Rolle, ob Du die
Person kennst, Du lässt immer anklingen, dass Du die Leute, die an
diversen Programmiersprachen gearbeitet haben, für praxisfremde Idioten
hältst.
Post by Rainer Weikusat
<ironie>
Im uebrigen bin ich selbsredend ein besserer Programmierer als jeder,
der das vor mir schon war,
Das Problem ist, dass ich Dir jederzeit glauben würde, dass Du das
glaubst, wenn du das Ironie-Tag weglässt.
Post by Rainer Weikusat
Sogar nach den C-Regeln fuer 'dynamische Typ-Assiozation' besteht da ein
Tell news. Genau diesen Unterschied habe ich bereits beschrieben.

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Rainer Weikusat
2014-05-04 22:00:48 UTC
Permalink
Raw Message
"Peter J. Holzer" <hjp-***@hjp.at> writes:

[Groesstenteils ignoriert, weil mir diese hartnaeckigen Versuche,
irrational, aggressive Handlungen in etwas hineinzulesen, die sich
darin nicht befanden, auf den Sender geht]
Post by Peter J. Holzer
Post by Rainer Weikusat
<ironie>
Im uebrigen bin ich selbsredend ein besserer Programmierer als jeder,
der das vor mir schon war,
Das Problem ist, dass ich Dir jederzeit glauben würde, dass Du das
glaubst, wenn du das Ironie-Tag weglässt.
In der Tat glaube ich das (was ich geschrieben habe, nicht das, was Du
davon uebrig zu lassen geruhtest): 99.99% aller Programmierprobleme
haben sattsam bekannte Standard-Loesungen und es ist nichts dabei, diese
anwenden zu koennen, und - in der Tat - (horribile dictu!) bin auch davon
ueberzeugt, dass auf diesem Gebiet soetwas wie 'Fortschritt'
stattfindet: ZB hat Dennis Ritchie fuer Strukturelement urspruenglich
einen globalen Namensraum vorgesehen. Das war keine gute Idee, aber
wohl nicht von vorneherein selbstverstaendlich. Dieser Fehler wurde
spaeter korrigiert und nochmal muss ihn keiner machen.
Rainer Weikusat
2014-05-04 22:02:45 UTC
Permalink
Raw Message
"Peter J. Holzer" <hjp-***@hjp.at> writes:

[Groesstenteils ignoriert, weil mir diese hartnaeckigen Versuche,
irrationale, aggressive Intentionen in einen Text hineinzulesen, die sich
darin nicht befanden, auf den Sender geht]
Post by Peter J. Holzer
Post by Rainer Weikusat
<ironie>
Im uebrigen bin ich selbsredend ein besserer Programmierer als jeder,
der das vor mir schon war,
Das Problem ist, dass ich Dir jederzeit glauben würde, dass Du das
glaubst, wenn du das Ironie-Tag weglässt.
In der Tat glaube ich das (was ich geschrieben habe, nicht das, was Du
davon uebrig zu lassen geruhtest): 99.99% aller Programmierprobleme
haben sattsam bekannte Standard-Loesungen und es ist nichts dabei, diese
anwenden zu koennen, und - in der Tat - (horribile dictu!) bin auch davon
ueberzeugt, dass auf diesem Gebiet soetwas wie 'Fortschritt'
stattfindet: ZB hat Dennis Ritchie fuer Strukturelemente urspruenglich
einen globalen Namensraum vorgesehen. Das war keine gute Idee, aber
wohl nicht von vorneherein selbstverstaendlich. Dieser Fehler wurde
spaeter korrigiert und nochmal muss ihn keiner machen.
Thomas Koenig
2014-05-05 20:11:41 UTC
Permalink
Raw Message
Post by Rainer Weikusat
In der Tat glaube ich das (was ich geschrieben habe, nicht das, was Du
davon uebrig zu lassen geruhtest): 99.99% aller Programmierprobleme
haben sattsam bekannte Standard-Loesungen
85% aller Statistiken sind frei erfunden.

104,3% aller Benutzer sind der Meinung, das Statistikprogarmm
sei fehlerhaft.
Rainer Weikusat
2014-05-05 21:11:23 UTC
Permalink
Raw Message
Post by Thomas Koenig
Post by Rainer Weikusat
In der Tat glaube ich das (was ich geschrieben habe, nicht das, was Du
davon uebrig zu lassen geruhtest): 99.99% aller Programmierprobleme
haben sattsam bekannte Standard-Loesungen
85% aller Statistiken sind frei erfunden.
104,3% aller Benutzer sind der Meinung, das Statistikprogarmm
sei fehlerhaft.
Das ist in diesem Fall keine Statistisk sondern eine Metapher fuer
"wirklich neue Probleme sind extrem selten" (die Faelle, bei denen ich
in den letzten zehn Jahren gezwungen war, tatsaechlich einen neuen
'technischen Algorithmus' (im Gegensatz zu einem problem-spezifischen
Kontrollalgorithmus, der auf Standardloesungen zurueckgreift) zu
entwickeln/ implementieren lassen sich problemlos an der Fingern einer
Hand abzaehlen. Insofern halte ich den Begriff "besserer Programmierer",
wenigstens insofern es um sogenannte 'hoehere Programmiersprache' geht,
fuer wenig sinnvoll: Es gibt zB mit Sicherheit auch 'bessere' oder
'schlechtere' Fabrikarbeiter aber im allgemeinen ist lediglich die
Faehigkeit gefordert, 'solide' mit einem akzeptablen Tempo zu arbeiten.
Das gilt aehnlich auch fuer Software: Wenn der Code das Problem, das er
loesen sollte, auf eine unter den gegebenen Umstaenden akzetable Weise
loest, ist er "gut genug". Wuenschenswert ist auch noch, das er
moeglichst einfach ist, was normalerweise im Gegensatz zu 'maximaler
technischer Perfektion' steht.
Georg Bauhaus
2014-05-06 08:23:27 UTC
Permalink
Raw Message
Post by Rainer Weikusat
Wuenschenswert ist auch noch, das er
moeglichst einfach ist, was normalerweise im Gegensatz zu 'maximaler
technischer Perfektion' steht.
Da nicht selten geäußert wird, Einfachheit gehöre zu den möglichen
Eigenschaft des Perfekten, erscheint der Normalfall komplexer Systeme
als unwirklich. Dabei verstellt der verträumte Begriff des "Vollendeten"
(des "Perfekten") den nüchternen Blick auf die Eigenschaften der
vergleichsweise besseren Programmier-Lösungen. Vielleicht soll er
vestellen: es geht um Darstellung. Mir scheint Perfektion eine
Verwechslung mit (Selbst-)Zufriedenheit.

Nützlich wäre das nüchterne Eingeständnis, dass Programmierung mit Hilfe
der Güte der Adaption existierender Lösungen gewertet werden kann.

Freilich hilft so ein Grad nicht bei der Werbung (die darf nicht
vergleichen) und bremst die Selbstdarstellung aus (zu ehrlich). Und da
liegt wohl ein Ursprung des sonderlichen Widerspruchs "maximaler
Perfektion", eine fragliche, aber folgenreiche Zusammenstellung von
Worten, die sich schon logisch widersprechen, sich aber toll anhören.
Vielleicht taugt die Ausrede der situativen Abhängigkeit des Maximums
von Perfektion.

Insofern ist auch jede Programmier-Normierung nie perfekt. Sie gibt
nur einen Rahmen vor, erlaubte Mittel, mit denen Kombattanten,
die die Zeit haben, ihre Lösungen gegen einander antreten lassen
können. Zum Beispiel auf höhere Regelhaftigkeit (wie "Undefiniertheit")
vergleichen.

Stefan Reuther
2014-04-27 17:02:09 UTC
Permalink
Raw Message
Hallo,

Rainer Weikusat wrote:
[...]
Post by Rainer Weikusat
Post by Peter J. Holzer
Das ist implementation defined, nicht undefined. Das, worum es hier
geht, ist eine ganz anderere Kategorie, und ich möchte ein Beispiel
sehen, wo Zugriffe mittels Pointer auf zufällig benachbarte Objekte
sinnvoll sind.
Das kommt darauf an, wie man 'zufaellig benachbart' definiert. Spontan
struct vchar_t {
size_t l;
char *s;
};
vchar_t *
vmalloc(size)
size_t size;
{
vchar_t *var;
size_t want;
want = alloc_size(size); /* round to multiple of 64 */
var = calloc(want, 1);
if (!var) return NULL;
var->l = size;
var->v = size ? (void *)(var + 1) : NULL;
return var;
}
Das ist etwas, das sich durchaus mit "fat pointers" abbilden ließe:
'calloc' liefert einen Pointer zurück, der als Größe "'want' Bytes"
eingetragen hat. Alle folgenden Operationen bleiben innerhalb dieses
Bereiches.

Allerdings hilft das auch nicht, wenn das Programm intern mit Pool-
Allokatoren arbeitet, weil dann aus Sicht der C-Implementierung alle
allokierten Blöcke hinreichend groß sind.


Stefan
Florian Weimer
2014-04-26 10:35:59 UTC
Permalink
Raw Message
Post by Peter J. Holzer
* Der Compiler hätte trotzdem die Möglichkeit, entsprechende Checks zu
generieren. Der C-Standard schreibt nämlich nicht vor, wie ein
"simpler Pointer" zu implementieren ist. Der kann durchaus
Information über die Arraygrenzen beinhalten. Er muss aber auch
nicht, denn Zugriff außerhalb des Arrays ist "undefined behaviour".
Und aus Performance- und Kompatibilitätsgründen ("fat pointers"
würden das ABI ändern) wird das nicht gemacht.
Compiler-Checks helfen aber nicht, wenn Puffer mit unterschiedlich
weit beschriebenen Bereichen wiederverwendet werden. Das Problem war
nur teilweise der fehlenden Typsicherheit von C geschuldet.
Peter J. Holzer
2014-04-26 12:42:13 UTC
Permalink
Raw Message
Post by Florian Weimer
Post by Peter J. Holzer
* Der Compiler hätte trotzdem die Möglichkeit, entsprechende Checks zu
generieren. Der C-Standard schreibt nämlich nicht vor, wie ein
"simpler Pointer" zu implementieren ist. Der kann durchaus
Information über die Arraygrenzen beinhalten. Er muss aber auch
nicht, denn Zugriff außerhalb des Arrays ist "undefined behaviour".
Und aus Performance- und Kompatibilitätsgründen ("fat pointers"
würden das ABI ändern) wird das nicht gemacht.
Compiler-Checks helfen aber nicht, wenn Puffer mit unterschiedlich
weit beschriebenen Bereichen wiederverwendet werden. Das Problem war
nur teilweise der fehlenden Typsicherheit von C geschuldet.
Die Verwendung großer Puffer-Segmente verschärft das Problem hier
natürlich, aber trotzdem würde ein Angreifer (der ja nicht wissen kann,
wieviel Platz hinter seinem Request noch im Puffer ist), ziemlich sicher
früher oder später über das Ende hinauslesen und damit eine Exception
auslösen. Da für einen erfolgreichen Angriff ziemlich viele Versuche
notwendig sind (der Gewinner der Cloudflare-Challenge hat 2.5 Millionen
Requests gebraucht) fällt das vielleicht doch auf, wenn der Webserver
zigtausendmal crasht.

(In diesem speziellen Fall hätte man das natürlich auch ganz ohne
Compiler-Support haben können: Einfach nach jedem 64k-Segment eine (4k)
Page ungemappt lassen. Aber wer daran denkt, denkt wahrscheinlich auch
daran, einen entsprechenen Check einzubauen. Trotzdem wäre das sinnvoll:
Es muss dann nämlich nicht die gleiche Person drandenken: Defense in
depth.)

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Florian Weimer
2014-04-26 17:45:18 UTC
Permalink
Raw Message
Post by Peter J. Holzer
Post by Florian Weimer
Compiler-Checks helfen aber nicht, wenn Puffer mit unterschiedlich
weit beschriebenen Bereichen wiederverwendet werden. Das Problem war
nur teilweise der fehlenden Typsicherheit von C geschuldet.
Die Verwendung großer Puffer-Segmente verschärft das Problem hier
natürlich, aber trotzdem würde ein Angreifer (der ja nicht wissen kann,
wieviel Platz hinter seinem Request noch im Puffer ist), ziemlich sicher
früher oder später über das Ende hinauslesen und damit eine Exception
auslösen. Da für einen erfolgreichen Angriff ziemlich viele Versuche
notwendig sind (der Gewinner der Cloudflare-Challenge hat 2.5 Millionen
Requests gebraucht) fällt das vielleicht doch auf, wenn der Webserver
zigtausendmal crasht.
Diese Massenversuche waren nur erforderlich, weil es darum ging, das
Schlüsselmaterial abzugreifen. Mehr Speichersicherheit hätte das
vielleicht verhindert.

Schwieriger ist es, wenn derselbe Puffer (hoffentlich nacheinander)
für unterschiedliche Verbindungen genutzt wird, dann können ohne
weitere Maßnahmen Daten trotz Speichersicherheit zwischen Verbindungen
lecken. Die Größe des Puffers dürfte dem Angreifer bekannt sein, so
daß er "Crashes" (eher: Ausnahmen) verhindern kann und einfach im
Stillen Daten abgreift.

Seltsamerweise werden Schlüsselmaterial mystische Qualitäten
zugeschrieben, die es in dieser Form (zumindest bei einem normalen
Webserver) nicht hat. Einen kompromittierten Schlüssel kann man
ersetzen (und ein Ausnutzen des Schlüssels erfordert zudem
Manipulation des Internet-Routings, für sich ist er unbrauchbar).
Benutzerdaten sind dagegen für immer weg, manchmal ohne daß sich das
jemals wieder vollständig heilen läßt.
Juergen Ilse
2014-04-26 18:34:42 UTC
Permalink
Raw Message
Hallo,
Post by Florian Weimer
Seltsamerweise werden Schlüsselmaterial mystische Qualitäten
zugeschrieben, die es in dieser Form (zumindest bei einem normalen
Webserver) nicht hat. Einen kompromittierten Schlüssel kann man
ersetzen (und ein Ausnutzen des Schlüssels erfordert zudem
Manipulation des Internet-Routings, für sich ist er unbrauchbar).
Das ausnutzen eines komprmottierten Schluessels erfordert nicht
zwingend eine Manipulation des Routings; eine DNS-Manipulation
waere ebenso geeignet, da im Zertifikat ueblicherweise der Name
des Rechners, nicht aber seine IP-Adresse referenziert ist.
Post by Florian Weimer
Benutzerdaten sind dagegen für immer weg, manchmal ohne daß sich das
jemals wieder vollständig heilen läßt.
Gelingt es, ein kompromittiertes Serverzertifikat aunzunutzen, kann man
mit diesem ggfs. sehr viele Nutzerdaten abgreifen. Umgekehrt gilt dies
jedoch nicht. Insofern kann das abgreifen eines Server-Schluessels manch-
mal tatsaechlich erheblich kritischer als das abgreifen von Userdaten
sein ...

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
--
Ein Domainname ist nur ein Name, nicht mehr und nicht weniger.
Wer mehr hineininterpretiert, hat das Domain-Name-System nicht
verstanden.
Florian Weimer
2014-04-26 19:32:19 UTC
Permalink
Raw Message
Post by Juergen Ilse
Das ausnutzen eines komprmottierten Schluessels erfordert nicht
zwingend eine Manipulation des Routings; eine DNS-Manipulation
waere ebenso geeignet, da im Zertifikat ueblicherweise der Name
des Rechners, nicht aber seine IP-Adresse referenziert ist.
Ja, aber wenn ich das machen kann, ...
Post by Juergen Ilse
Post by Florian Weimer
Benutzerdaten sind dagegen für immer weg, manchmal ohne daß sich das
jemals wieder vollständig heilen läßt.
Gelingt es, ein kompromittiertes Serverzertifikat aunzunutzen, kann man
mit diesem ggfs. sehr viele Nutzerdaten abgreifen.
... dann kann ich mir sowieso nahezu beliebig viele Serverzertifikate
für die Domain ausstellen lassen.

Davon abgesehen merken viele Benutzer nicht, wenn https:// plötzlich
fehlt (oder klicken Warnungen weg). Wenn man also Kontrolle über den
Netzverkehr hat, muß man sich nicht einmal um das Zertifikat kümmern.
Post by Juergen Ilse
Umgekehrt gilt dies
jedoch nicht. Insofern kann das abgreifen eines Server-Schluessels manch-
mal tatsaechlich erheblich kritischer als das abgreifen von Userdaten
sein ...
Bei einem gewöhnlichen Webserver, der am Internet hängt, bezweifle ich
das, weil immer noch ein unabhängiger Angriff erforderlich ist. Die
Benutzerdaten können sehr direkt nutzbar sein.
Juergen Ilse
2014-04-27 00:08:27 UTC
Permalink
Raw Message
Hallo,
Post by Florian Weimer
Post by Juergen Ilse
Das ausnutzen eines komprmottierten Schluessels erfordert nicht
zwingend eine Manipulation des Routings; eine DNS-Manipulation
waere ebenso geeignet, da im Zertifikat ueblicherweise der Name
des Rechners, nicht aber seine IP-Adresse referenziert ist.
Ja, aber wenn ich das machen kann, ...
... dann kann ich mir sowieso nahezu beliebig viele Serverzertifikate
für die Domain ausstellen lassen.
Das kommt darauf an, wie die Pruefung erfolgt, ob jemand berechtigt idt,
sich auf den Namen ein Zertifikat ausstellen zu lassen (das ist nicht
bei jeder CA gleich, und bei der selben CA u.U. auch nicht bei jedem
Typ von Zertifikat, z.B. bei "EV-Zertifikaten" anders als bei einfachen
Webserverzertifikaten). Ausserdem muss der "Boesewicht" u.U. nicht den
authoritativen DNS fuer die Domain kompromittieren: gelingt es ihm,
einen rekursiven DNS eines grossen Providers zu manipulieren (ggfs.
mittels cache-poisoning), so reicht das u.U. schon aus, um richtig
Schaden anzurichten, wenn auch nur bei einem kleinen Teil des Internets
und auch dann, wenn das nicht ausreicht, um sich ein falsches Zertifikat
auf den Namen ausstellen zu lassen ...
Post by Florian Weimer
Davon abgesehen merken viele Benutzer nicht, wenn https:// plötzlich
fehlt (oder klicken Warnungen weg). Wenn man also Kontrolle über den
Netzverkehr hat, muß man sich nicht einmal um das Zertifikat kümmern.
Das waere aber auch ein voellig anderes Angriffsszenario, dass mit
Heardbleed nun wirklich nicht das geringste zu tun hat.

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
--
Ein Domainname ist nur ein Name, nicht mehr und nicht weniger.
Wer mehr hineininterpretiert, hat das Domain-Name-System nicht
verstanden.
Peter J. Holzer
2014-04-27 10:41:49 UTC
Permalink
Raw Message
Post by Florian Weimer
Post by Peter J. Holzer
Post by Florian Weimer
Compiler-Checks helfen aber nicht, wenn Puffer mit unterschiedlich
weit beschriebenen Bereichen wiederverwendet werden. Das Problem war
nur teilweise der fehlenden Typsicherheit von C geschuldet.
Die Verwendung großer Puffer-Segmente verschärft das Problem hier
natürlich, aber trotzdem würde ein Angreifer (der ja nicht wissen kann,
wieviel Platz hinter seinem Request noch im Puffer ist), ziemlich sicher
früher oder später über das Ende hinauslesen und damit eine Exception
auslösen. Da für einen erfolgreichen Angriff ziemlich viele Versuche
notwendig sind (der Gewinner der Cloudflare-Challenge hat 2.5 Millionen
Requests gebraucht) fällt das vielleicht doch auf, wenn der Webserver
zigtausendmal crasht.
Diese Massenversuche waren nur erforderlich, weil es darum ging, das
Schlüsselmaterial abzugreifen.
Hauptsächlich waren sie erforderlich, weil es darum ging, eine bestimmte
Information abzugreifen. Wäre es nur darum gegangen, irgendwelche
sensible Daten zu bekommen, wären sicher weniger Versuche notwendig
gewesen. Das ändert allerdings nur das Zahlenverhältnis. Auch wenn man
auf Passwörter, Kreditkartennummern, etc. losgeht, machen die nur einen
winzigen Teil des Traffics aus und man braucht viele Versuche, um sie zu
bekommen.
Post by Florian Weimer
Schwieriger ist es, wenn derselbe Puffer (hoffentlich nacheinander)
für unterschiedliche Verbindungen genutzt wird, dann können ohne
weitere Maßnahmen Daten trotz Speichersicherheit zwischen Verbindungen
lecken. Die Größe des Puffers dürfte dem Angreifer bekannt sein, so
daß er "Crashes" (eher: Ausnahmen) verhindern kann und einfach im
Stillen Daten abgreift.
Nicht bei der aktuellen Implementation. Der Angreifer kennt zwar die
Größe (64 kB), aber er weiß nicht, wo in diesem Puffer sein Request
steht und damit auch nicht, wieweit er über das Ende seines Requests
hinauslesen kann. Wenn er Glück hat, steht sein Request ganz am Anfang
und er kann fast 64 k lesen. Wenn er Pech hat, steht er ganz am Ende und
bereits der Versuch, 1 Byte zu lesen, führt zu einer Exception. Da die
Position im Puffer weitgehend zufällig ist, ist die Wahrscheinlichkeit,
eine Exception auszulösen, ungefähr proportional zur Länge, die der
Angreifer auslesen will. Er kann sie pro Request also beliebig
verringern. Allerdings schrumpft auch die Wahrscheinlichkeit, brauchbare
Daten zu erhalten, im gleichen Maß, er braucht also entsprechend mehr
Requests, und die Wahrscheinlichkeit, eine Exception auszulösen, bleibt
ziemlich konstant.

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Juergen Ilse
2014-04-26 10:52:18 UTC
Permalink
Raw Message
Hallo,
Post by Peter J. Holzer
C. C kennt nämlich gar keine echten Arrays; das ist alles nur
Pointerarithmetik (sonst würde der Code in meiner .sig nämlich gar nicht
funktionieren).
Das ist Unfug. C kennt sehr wohl arrays, nur kann man diese z.B. nicht per
value an Funktionen uebergeben, und ueberall dort, wo das array nicht
direkt verwendet werden kann, wird es implizit durch einen Pointer auf
das erste array-Element ersetzt (ohne jegliche Warnung oder Hinweis zur
compile-Time).
Post by Peter J. Holzer
Das stimmt so nicht. C kennt sehr wohl Arrays. Allerdings kannst Du
keine Arrays zuweisen (und damit auch nicht an Funktionen übergeben oder
aus diesen zurückgeben).
Das eine wuerde das andere noch nicht zwangslaeufig ausschliessen, aber
du hast recht damit, dass die Zuweisung auch einer der Faelle ist, wo
ein array durch einen Pointer auf das erste array-Element ersetzt wird.
Post by Peter J. Holzer
Wenn Du z.B. eine Bibliotheksfunktion aufrufst, die ein Array
zurückliefert,
In C kann eine Funktion kein array zurueckliefern, die Sprache bietet
dafuer einfach keine Moeglichkeit, so etwas ist in C schlicht nicht
vorgesehen.
Post by Peter J. Holzer
hast Du keine Möglichkeit zu überprüfen, ob Du wirklich ein Array von
der gewünschten Größe bekommen hast. Alles was Du hast, ist nämlich
ein simpler Pointer.
Man kann nicht ueberpruefen, ob man ein array bestimmter Groesse zurueck-
bekommen hat, weil man das nicht hat. Man bekommt eben kein array sondern
einen Pointer zurueck.
Post by Peter J. Holzer
* Als Programmierer hat man natürlich die Möglichkeit, seine
Schnittstellen so zu gestalten, dass diese Informationen mitgegeben
werden. Muss man halt tun und ist lästig.
Das ist richtig. C nimmt einem in vielen Faellen Pruefungen *nicht* ab,
die einem in manch anderen Sprachen abgenommen werden. C bietet dem
Programmierer Freiheiten, die einem in vielen anderen Sprachen nicht
geboten werden, es liegt in der Verantwortung des Programmierers, mit
diesen Freiheiten verantwortungsvoll umzugehen. Aus diesem Grund halte
ich C auch nur bedingt fuer Programmieranfaenger geeignet ...

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
--
Ein Domainname ist nur ein Name, nicht mehr und nicht weniger.
Wer mehr hineininterpretiert, hat das Domain-Name-System nicht
verstanden.
Peter J. Holzer
2014-04-26 13:05:46 UTC
Permalink
Raw Message
Post by Juergen Ilse
Post by Peter J. Holzer
C. C kennt nämlich gar keine echten Arrays; das ist alles nur
Pointerarithmetik (sonst würde der Code in meiner .sig nämlich gar nicht
funktionieren).
[...]
Post by Juergen Ilse
Post by Peter J. Holzer
Das stimmt so nicht. C kennt sehr wohl Arrays. Allerdings kannst Du
keine Arrays zuweisen (und damit auch nicht an Funktionen übergeben oder
aus diesen zurückgeben).
Das eine wuerde das andere noch nicht zwangslaeufig ausschliessen,
Theoretisch nicht, aber

* Ein Funktionswert, den ich danach keiner Variable zuweisen kann, ist
nutzlos.
* In call-by-value Sprachen ist eine Parameterübergabe konzeptionell
nur ein Spezialfall einer Zuweisung.

Daher würde es mich überraschen, wenn in einer Sprache mit CBV die
Mengen der Typen, die eine Funktion als Parameter haben bzw. als
Ergebnis liefern kann, eine Übermenge der Typen wäre, die man direkt
zuweisen kann. Aber es gibt sicher irgendeine obskure Sprache, wo das
der Fall ist.

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Jens Schmidt
2014-04-27 21:11:03 UTC
Permalink
Raw Message
Post by Peter J. Holzer
Daher würde es mich überraschen, wenn in einer Sprache mit CBV die
Mengen der Typen, die eine Funktion als Parameter haben bzw. als
Ergebnis liefern kann, eine Übermenge der Typen wäre, die man direkt
zuweisen kann. Aber es gibt sicher irgendeine obskure Sprache, wo das
der Fall ist.
Function- und Procedure-Parameter in Pascal. Obskur genug?
--
Viele Grüße,
Jens Schmidt
Peter J. Holzer
2014-04-29 19:03:23 UTC
Permalink
Raw Message
Post by Jens Schmidt
Post by Peter J. Holzer
Daher würde es mich überraschen, wenn in einer Sprache mit CBV die
Mengen der Typen, die eine Funktion als Parameter haben bzw. als
Ergebnis liefern kann, eine Übermenge der Typen wäre, die man direkt
zuweisen kann. Aber es gibt sicher irgendeine obskure Sprache, wo das
der Fall ist.
Function- und Procedure-Parameter in Pascal. Obskur genug?
Ja, obskur genug. Ich wusste nicht, dass es die gibt (in UCSD-Pascal gab
es sie nicht). In Modula-2 gibt es sie, aber dort gibt es auch reguläre
Variablen vom Procedure-Typ, nicht nur Parameter. Offenbar hat Wirth
bemerkt, dass das eine ohne das andere etwas inkonsistent ist.

Hmm, Fortran scheint eine ähnliche Evolution durchgemacht zu haben: In
Fortran 90 gibt nur Procedure-Parameter, in Fortran 2003 allgemeine
Function Pointer, wenn mich die allwissende Müllhalde nicht narrt (meine
praktischen Fortran-Erfahrungen sind glücklicherweise minimal).

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Stefan Ram
2014-04-26 20:44:29 UTC
Permalink
Raw Message
Post by Juergen Ilse
Man kann nicht ueberpruefen, ob man ein array bestimmter Groesse zurueck-
bekommen hat, weil man das nicht hat. Man bekommt eben kein array sondern
einen Pointer zurueck.
Ja, aber das Typsystem erlaubt es, es als Fehler zu
erkennen, wenn ein Zeiger auf eine Reihung mit der Größe 65
einem Zeiger auf eine Reihung mit der Größe 999 zugewiesen
werden soll.

Quellcode:

typedef int( *a65_t )[ 65 ]; a65_t a65(){ static int a[ 65 ]; return &a; }
int main( void ){ { int( *a )[ 65 ]= a65(); }{ int( *a )[ 999 ]= a65(); }}

Fehlermeldung (Beispiel):

error: initialization from incompatible pointer type
int main( void ){ { int( *a )[ 65 ]= a65(); }{ int( *a )[ 999 ]= a65(); }}
^

Und in der aufrufenden Funktion wird es als Fehler erkannt,
wenn ein Zeiger auf eine Reihung mit 999 Einträgen als
Zeiger auf einer Reihung mit 65 Einträgen zurückgegeben
werden soll.

Quellcode:

int main( void ){}
typedef int( *a65_t )[ 65 ]; a65_t a65(){ static int a[ 999 ]; return &a; }

Fehlermeldung (Beispiel):

error: return from incompatible pointer type
typedef int( *a65_t )[ 65 ]; a65_t a65(){ static int a[ 999 ]; return &a; }
^
Damit kann ich also bei Verwendung von »a65_t« in der
aufgerufenen Funktion nur einen Zeiger auf einer Reihung
zurückgeben, die wirklich 65 Einträge hat, und in der
aufrufenden Funktion kann ich das Funktionsergebnis auch nur
an einen Zeiger auf eine Reihung mit 65 Einträgen übergeben.
Man kann also so sicherstellen, daß die Größen zusammenpassen.
Juergen Ilse
2014-04-27 00:19:43 UTC
Permalink
Raw Message
Hallo,
Post by Stefan Ram
Post by Juergen Ilse
Man kann nicht ueberpruefen, ob man ein array bestimmter Groesse zurueck-
bekommen hat, weil man das nicht hat. Man bekommt eben kein array sondern
einen Pointer zurueck.
Ja, aber das Typsystem erlaubt es, es als Fehler zu
erkennen, wenn ein Zeiger auf eine Reihung mit der Größe 65
einem Zeiger auf eine Reihung mit der Größe 999 zugewiesen
werden soll.
Das array "zerfaellt" aber nicht zu einem Pointer auf das grdamte array
sondern zu einem Pointer auf das erste array-Element, und diese Pointer
haben bei einem 65-elementigen array exakt den selben Typ wie bei einem
999-elementigen array. In deinem Beispiel wird (so wie ich es verstanden
habe) die "Unvertraeglichkeit ja nur durch die unterschiedlichen Typen
der Pointer er kannt ...
Post by Stefan Ram
typedef int( *a65_t )[ 65 ]; a65_t a65(){ static int a[ 65 ]; return &a; }
int main( void ){ { int( *a )[ 65 ]= a65(); }{ int( *a )[ 999 ]= a65(); }}
error: initialization from incompatible pointer type
int main( void ){ { int( *a )[ 65 ]= a65(); }{ int( *a )[ 999 ]= a65(); }}
^
Eine andere Situation, als wenn im Source "return array;" mit array einem
array irgend eines Typs staende, denn dann wuerde im Gegensatz zu deinem
Beispiel ein Pointer auf das erste array-Element statt einem Pointer auf
das array zurueckgegeben.

Tschuess,
Juergen Ilse (***@usenet-verwaltung.de)
--
Ein Domainname ist nur ein Name, nicht mehr und nicht weniger.
Wer mehr hineininterpretiert, hat das Domain-Name-System nicht
verstanden.
Stefan Reuther
2014-04-26 11:11:43 UTC
Permalink
Raw Message
Post by Peter J. Holzer
[Fup2 de.comp.lang.c]
Emm, mit welchen Programmiersprachen ist denn eine Plausibilitätsprüfung
unmöglich? - Die sollte man tatsächlich meiden wie die Pest!
C. C kennt nämlich gar keine echten Arrays; das ist alles nur
Pointerarithmetik (sonst würde der Code in meiner .sig nämlich gar nicht
funktionieren).
Das stimmt so nicht. C kennt sehr wohl Arrays. Allerdings kannst Du
keine Arrays zuweisen (und damit auch nicht an Funktionen übergeben oder
aus diesen zurückgeben).
Man kann allerdings sehr wohl sowas machen:
struct byte_array {
uint8_t* p;
size_t size;
};
und dann mit Funktionen wie
// statt malloc
struct byte_array array_allocate(size_t size);

// statt memcpy
void array_copy(struct byte_array dest, struct byte_array src);

// statt Pointerzugriff
void array_set(struct byte_array p, size_t index, uint8_t value);
arbeiten. Zur Prüfung, ob diese Funktionen auch verwendet werden, gibt
es dann statische Analysetools, in denen man z.B. memcpy auf eine
Blacklist setzen kann.

C ist zugegebenermaßen nicht die schönste Sprache für sowas. In C++ kann
man diese Funktionsaufrufe z.B. viel besser verstecken und den
Nutzercode so kompakter halten.


Stefan
Peter J. Holzer
2014-04-26 13:10:09 UTC
Permalink
Raw Message
Post by Stefan Reuther
Post by Peter J. Holzer
[Fup2 de.comp.lang.c]
Emm, mit welchen Programmiersprachen ist denn eine Plausibilitätsprüfung
unmöglich? - Die sollte man tatsächlich meiden wie die Pest!
C. C kennt nämlich gar keine echten Arrays; das ist alles nur
Pointerarithmetik (sonst würde der Code in meiner .sig nämlich gar nicht
funktionieren).
Das stimmt so nicht. C kennt sehr wohl Arrays. Allerdings kannst Du
keine Arrays zuweisen (und damit auch nicht an Funktionen übergeben oder
aus diesen zurückgeben).
struct byte_array {
uint8_t* p;
size_t size;
};
Das ist aber eben kein Array, sondern ein struct. Nebenbei noch nicht
mal eines, das ein Array enthält, sondern nur einen Pointer.

Man kann auch sowas definieren:

struct lochkarte {
char c[80];
};

und das zuweisen. Aber es ist eben auch kein Array, sondern ein struct,
das ein Array enthält.

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Loading...