Forum: Mikrocontroller und Digitale Elektronik char Array für ultoa()


von Dieter B. (Gast)


Lesenswert?

Nabend,

mir lässt eine Frage keine Ruhe..
Wieso kann
1
ultoa( 2323232 , NULL , 10 );
An der Stelle auch ohne "array" arbeiten?
Wenn ich das dann so in eine Ausgaberoutine rein hauen würde, würde er 
mir den String raus hauen..

Legt er die Daten auf dem Stack ab bei "NULL" Übergabe?
Wird der Speicher etwa mit der Funktion "malloc()" geschaffen?

von S. R. (svenska)


Lesenswert?

Ich sehe keine Sonderbehandlung für NULL in der Dokumentation (IBM, MS, 
avrlibc).

Die Funktion wird einfach das Ergebnis an den Adressraumbeginn 
schreiben. Wenn da was stand, isses danach weg. Und wenn du eine 
ordentliche Hardware hast, kriegst du einen Seg- oder Hardfault für den 
Versuch.

von Stefan F. (Gast)


Lesenswert?

Der zweite Parameter ist ein Zeiger, was ein Integer mit der Adresser 
der Speicherzelle ist.

NULL ist die Zahl 0. Wird also als Zeiger auf die Speicherzelle mit 
Adresse 0 interpretiert.

von Dieter B. (Gast)


Lesenswert?

S. R. schrieb:
> Und wenn du eine ordentliche Hardware hast, kriegst du einen Seg- oder
> Hardfault für den Versuch.

Das heißt? Was bewirken diese Features?  Was lösen die aus?

Also gehe ich davon aus, dass es zufall ist das es Funktioniert.

von Stefan F. (Gast)


Lesenswert?

Das sind Interrupts. Was sie bewirken, hängt davon ab, was der 
Programmierer programmiert hat.

von foobar (Gast)


Lesenswert?

>> Und wenn du eine ordentliche Hardware hast, kriegst du einen Seg- oder
>> Hardfault für den Versuch.
>
> Das heißt? Was bewirken diese Features?  Was lösen die aus?

Sie beenden dein Programm, resetten den Controller, ... kommt auf die 
konkrete Hardware an. Gemeinsam ist, dass sie das als fatalen 
Programmierfehler ansehen und das Programm nicht mehr weiterlaufen 
lassen wollen.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Stefanus F. schrieb:
> Das sind Interrupts.

Nicht bei jeder Architektur liegen die Interruptvektoren am unteren Ende 
des Adressraums.

von Stefan F. (Gast)


Lesenswert?

Ist doch egal wo die Interrupts liegen.

Es geht doch darum, dass bei Zugriff auf eine nicht erlaubte Adresse (in 
diesem Fall 0) ein Hard Fault ausgelöst wird.

von Rolf M. (rmagnus)


Lesenswert?

Stefanus F. schrieb:
> Der zweite Parameter ist ein Zeiger, was ein Integer mit der Adresser
> der Speicherzelle ist.
>
> NULL ist die Zahl 0. Wird also als Zeiger auf die Speicherzelle mit
> Adresse 0 interpretiert.

Nein.

Rufus Τ. F. schrieb:
> Stefanus F. schrieb:
>> Das sind Interrupts.
>
> Nicht bei jeder Architektur liegen die Interruptvektoren am unteren Ende
> des Adressraums.

Da hast du was missverstanden. Es ging um:

S. R. schrieb:
> einen Seg- oder Hardfault

und nicht um die Lage der Interruptvektortabelle.

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

> Nein

Wenn du das begründen bzw. korrigieren würdest, wäre es hilfreicher.

von Markus F. (mfro)


Lesenswert?

Ein C99-konformer Compiler wird einen statischen Zugrif auf NULL nicht 
ausführen, sondern mit einem abort() quittieren. Schwachsinn, meiner 
Meinung nach, aber so sind nun mal die Regeln...

Um tatsächlich auf *0L zuzugreifen, muss man (z.B.) gcc ein 
-no-delete-null-pointer-checks mitgeben.

von Markus F. (mfro)


Lesenswert?

sollte -fno-delete-null-pointer-checks heissen

von S. R. (svenska)


Lesenswert?

Markus F. schrieb:
> Ein C99-konformer Compiler wird einen statischen Zugrif auf NULL nicht
> ausführen, sondern mit einem abort() quittieren.

Ernsthaft? Das ist so vorgeschrieben? Was soll das abort() denn tun?
Ich dachte, eine NULL-Dereferenz wäre schlicht implementation-defined.

Dieter B. schrieb:
> Also gehe ich davon aus, dass es zufall ist das es Funktioniert.

Ein Programm, was nur zufällig funktioniert, funktioniert nicht.

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

> Ein C99-konformer Compiler wird einen statischen Zugriff auf NULL nicht
> ausführen, sondern mit einem abort() quittieren.

Sicher?
1
#include <stdio.h>
2
3
int main(int argc,char** argv)
4
{
5
   char* ptr=0;
6
   puts(ptr);
7
   puts(0);
8
   puts(NULL);
9
}

Kein Abbruch:
1
stefan@stefanspc:~$ gcc -std=c99 test.c
2
stefan@stefanspc:~$

Zweiter Versuch:
1
stefan@stefanspc:~$ gcc -std=c99 -Wall test.c
2
test.c: In function ‘main’:
3
test.c:7:4: warning: null argument where non-null required (argument 1) [-Wnonnull]
4
    puts(0);
5
    ^~~~
6
test.c:8:4: warning: null argument where non-null required (argument 1) [-Wnonnull]
7
    puts(NULL);
8
    ^~~~
9
stefan@stefanspc:~$

Es wird also höchstens eine Warnung ausgegeben.

von Dr. Sommer (Gast)


Lesenswert?

In C darf NULL "((void*) 0)" oder "0" sein, in C++ "0" oder "nullptr". 
Die Zahl "0" in einen Pointer-Typ wie "void*" zu konvertieren resultiert 
in einem Null-Pointer, d.h. ein Pointer der ungleich jedem gültigen 
Pointer ist. Das heißt aber nicht, dass die tatsächliche Adresse 
wirklich 0 sein muss - in "(void*) 0" könnte auch 42 stehen, je nachdem 
was der Compiler daraus macht. Somit greift "*((char*)NULL)" nicht 
notwendigerweise auf Adresse 0 zu. Tatsächlich machen die meisten 
Compiler das aber so; m.W. gibt es z.B. im GCC gar nicht die 
Möglichkeit, Null-Pointer auf etwas anderes al 0 zu setzen. Das könnte 
aber gerade bei Cortex-M praktisch sein, weil man Null-Pointer dann auf 
eine Adresse setzen könnte, die bei Zugriffen garantiert immer zum 
Absturz führt, was bei der Adresse "0" nicht immer der Fall ist.

von Stefan F. (Gast)


Lesenswert?

Ich habe in der C99 Spezifikation keinen Hinweis gefunden, das null 
pointer irgendwo verboten seien.
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf

Nur das hier:
"If an argument to a function has an invalid value (such as a value 
outside the domain of the function, or a pointer outside the address 
space of the program, or a null pointer, or a pointer to non-modifiable 
storage when the corresponding parameter is not const-qualified) or a 
type (after promotion) not expected by a function with variable number 
of arguments, the behavior is undefined."

von Markus F. (mfro)


Lesenswert?

Hier:

https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html

mal die Option -fdelete-null-pointer-checks (besser mehrfach) studieren 
(die Auswirkungen sind - bzw. waren zumindest mir - nicht auf den ersten 
Blick offensichtlich).

Das Verhalten ist eine Optimierung (?) und schlägt nur bei optimierten 
Builds zu (ist allerdings wohl nicht bei allen Compilern aktiv). Basiert 
auf dem Kausalsschluss, dass NULL-Pointer-Dereferenzierungen undefined 
behaviour sind und deswegen wegoptimiert werden können. Macht man's 
doch, gibt's ein abort().

Jedenfalls war ich erstaunt, als ein altes, neu compiliertes 
ColdFire-Programm plötzlich nicht mehr lief, das eigentlich nur seinen 
Reset-Vektor (auf Adresse 0) setzen wollte und stattdessen in einen trap 
#7 lief (obwohl noch gar keine Vektortabelle da war).

von Rolf M. (rmagnus)


Lesenswert?

Dr. Sommer schrieb:
> Somit greift "*((char*)NULL)" nicht notwendigerweise auf Adresse 0 zu.

Genau. Das meinte ich vorher mit meinem zugegebenermaßen etwas kurz 
geratenen "Nein" (ich war etwas in Eile).
Siehe z.B. http://c-faq.com/null/index.html .

Anders wäre es bei
1
int i = 0;
2
char* c = (char*)i;

denn die Regel gilt nur für konstante Ausdrücke, und i ist kein 
konstanter Ausdruck. Allerdings bleibt die Konvertierung eines int in 
einen Zeiger zumindest mal implementationsabhängig.

S. R. schrieb:
> Ich dachte, eine NULL-Dereferenz wäre schlicht implementation-defined.

Sie ruft "undefined behavior" hervor.

Stefanus F. schrieb:
> Ich habe in der C99 Spezifikation keinen Hinweis gefunden, das null
> pointer irgendwo verboten seien.

6.5.3.2 Absatz 4:
If an invalid value has been assigned to the pointer, the behavior of 
the  unary * operator is undefined. 84)

und in Fußnote 84:

Among the invalid values for dereferencing a pointer by the unary * 
operator are a null pointer, an address inappropriately aligned for 
the type of object pointed to, and the address of an object after the 
end of its lifetime.

von Heiko L. (zer0)


Lesenswert?

> If an argument to a function has an invalid value
> (such as ... a null-pointer) ... the behavior is undefined.
Den Satz darf man glaube ich nicht so verstehen, wie ich ihn verstünde, 
wenn ich nicht wüsste, dass man ihn so nicht verstehen darf.

von Stefan F. (Gast)


Lesenswert?

> Das meinte ich vorher mit meinem zugegebenermaßen etwas kurz
> geratenen "Nein"

Danke, so kann ich auch was damit anfangen. Also NULL ist per 
Spezifikation nicht zwingend gleich 0.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Dr. Sommer schrieb:
> Das heißt aber nicht, dass die tatsächliche Adresse wirklich 0 sein muss
> - in "(void*) 0" könnte auch 42 stehen, je nachdem was der Compiler
> daraus macht.

Hast Du schon mal einen real existierenden Compiler gesehen, der das 
macht?

von Rolf M. (rmagnus)


Lesenswert?

Stefanus F. schrieb:
>> Das meinte ich vorher mit meinem zugegebenermaßen etwas kurz
>> geratenen "Nein"
>
> Danke, so kann ich auch was damit anfangen. Also NULL ist per
> Spezifikation nicht zwingend gleich 0.

Auf Quellcode-Ebene schon. Also
1
char* c = 0;
und
1
char* c = NULL;
werden beide zu einem Nullzeiger führen. Aber dieser muss nicht zwingend 
komplett aus 0-Bits bestehen. Es erfolgt eine Konvertierung von der 
Integer-Konstanten 0 in das Bit-Pattern, das einen Nullzeiger darstellt. 
Im Prinzip muss es nicht mal sein, dass es nur genau einen Wert für 
einen Nullzeiger gibt. Es könnte auch mehrere geben, aber ein Vergleich 
zweier Nullzeiger muss immer Gleichheit ergeben.

Rufus Τ. F. schrieb:
> Dr. Sommer schrieb:
>> Das heißt aber nicht, dass die tatsächliche Adresse wirklich 0 sein muss
>> - in "(void*) 0" könnte auch 42 stehen, je nachdem was der Compiler
>> daraus macht.
>
> Hast Du schon mal einen real existierenden Compiler gesehen, der das
> macht?

In meinem obigen Link auf die comp.lang.c-FAQ findet man das in Kapitel 
5.17 ("Seriously, have any actual machines really used nonzero null 
pointers, or different representations for pointers to different 
types?")

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

Jetzt bin ich aber verwirrt.

NULL ist nicht unbedingt gleich 0, könnte auch 42 sein.
Aber wenn ich im Quelltext 0 schreibe, dann ist das immer gleich NULL?
Also wenn ich im Quelltext 0 schreibe, könnte daraus 42 werden.

Hä? Echt jetzt? Was nehmen die Leute ein, die sich so etwas ausdenken?

von Dr. Sommer (Gast)


Lesenswert?

Rufus Τ. F. schrieb:
> Hast Du schon mal einen real existierenden Compiler gesehen, der das
> macht?

Leider nein, aber wie gesagt wäre es durchaus hilfreich. An Adresse 0 
steht bei ARM gerne mal der Flash, oder ein ROM mit Bootloader oder so, 
sodass ein Zugriff wie bereits erwähnt manchmal sogar erwünscht ist. 
Stattdessen könnte man sich theoretisch eine andere Adresse suchen an 
der kein Speicher oder Peripherie zu finden ist, diese per MMU/MPU 
schützen und für Null-Pointer verwenden, sodass jeder Zugriff zu einer 
Exception führt. Nur leider können die Compiler das nicht. Dann ginge so 
etwas:
1
extern char BootROM [1024];
2
3
int main () {
4
  // Addresse 0xFFFFFFF0 per MMU/MPU schützen ...  
5
6
  char* pNull = 0; // oder NULL oder nullptr - wird vom Compiler auf 0xFFFFFFF0 konvertiert
7
  *pNull; // Exception (Data Abort)
8
  *BootROM [0]; // OK, Zugriff auf 0
9
}
Und per Linkerscript BootROM auf Adresse 0 legen.

von Dr. Sommer (Gast)


Lesenswert?

... setzt natürlich voraus, dass man den Compiler irgendwie so 
konfigurieren könnte, dass er 0xFFFFFFF0 für Null-Pointer verwendet.

von Markus F. (mfro)


Lesenswert?

Rolf M. schrieb:
> In meinem obigen Link auf die comp.lang.c-FAQ findet man das in Kapitel
> 5.17 ("Seriously, have any actual machines really used nonzero null
> pointers, or different representations for pointers to different
> types?")

eine dort nicht aufgeführte Plattform, bei der das meiner Erinnerung 
nach auch so ist (war), sind die INMOS Transputer.

Die hatten signed pointer. Die Adresse 0 war also eine gültige und lag 
mitten im erlaubten Adressraum. Welchen Wert NULL da hatte, weiss ich 
allerdings nicht.

von Rolf M. (rmagnus)


Lesenswert?

Stefanus F. schrieb:
> Jetzt bin ich aber verwirrt.
>
> NULL ist nicht unbedingt gleich 0, könnte auch 42 sein.
> Aber wenn ich im Quelltext 0 schreibe, dann ist das immer gleich NULL?
> Also wenn ich im Quelltext 0 schreibe, könnte daraus 42 werden.

Auf Quelltext-Ebene bedeutet eine Zuweisung von 0 an einen Zeiger, dass 
du einen NULL-Zeiger draus machst. Ein Vergleich mit 0 ergibt auch 
wieder Gleichheit. Ein Vergleich mit einem beliebigen gültigen 
Objekt-Zeiger gibt keine Gleichheit. Wie das hinter den Kulissen 
umgesetzt ist, spielt dabei für dich keine Rolle. Das ist eben 
Abstraktion.

> Hä? Echt jetzt? Was nehmen die Leute ein, die sich so etwas ausdenken?

Sie nehmen an, dass der Compiler-Bauer möglichst viele Freiheiten zu 
schätzen weiß, um die Sprache möglichst gut auf jeder nur erdenklichen 
Plattform umsetzen zu können. Daher wird eben nicht hart vorgegeben, 
dass ein NULL-Zeiger auch im Speicher nur aus 0-Bits besteht, denn es 
könnte Systeme geben, auf denen das große Nachteile hätte.
Oder sieh es mal so. Wenn du schreibst:
1
int i = 5;
2
float f = i;
würdest du erwarten, dass das Bit-Pattern, das in i steht und das in f 
exakt gleich sind? Eher nicht, oder? Ist schließlich ein anderer 
Datentyp, bei dem die 5 ganz anders repräsentiert wird. Warum sollte das 
bei einem Nullzeiger dann verboten sein?

: Bearbeitet durch User
von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Rolf M. schrieb:
> In meinem obigen Link auf die comp.lang.c-FAQ findet man das in Kapitel
> 5.17

Effektiv also uralte obskure und abgesehen von Informatikvorlesungen 
völlig irrelevante Systeme.

von Markus F. (mfro)


Lesenswert?

Rufus Τ. F. schrieb:
> Rolf M. schrieb:
>> In meinem obigen Link auf die comp.lang.c-FAQ findet man das in Kapitel
>> 5.17
>
> Effektiv also uralte obskure und abgesehen von Informatikvorlesungen
> völlig irrelevante Systeme.

Anscheinend gibt's auch einigermassen aktuelle Systeme, die das 
betrifft.
Z.B. der STi5517 von ST. Scheinbar ein Transputer-Nachfolger, der auch 
die signed-Adressen geerbt hat. Auch da liegt 0 mitten im gültigen 
Addressraum.

von Peter D. (peda)


Lesenswert?

Dieter B. schrieb:
> Also gehe ich davon aus, dass es zufall ist das es Funktioniert.

Funktioniert es denn auf Deinem unbekannten System mit Deinem 
unbekannten Compiler?

von A. S. (Gast)


Lesenswert?

Markus F. schrieb:
> sondern mit einem abort() quittieren

Abgesehen davon, dass dies nicht vorgeschrieben ist, wäre es auch 
unschön, jede, aber auch wirklich jede Dereferenzierung erstmal auf 
Verschiedenheit von 0 Testen zu müssen. Genau dafür gibt es ja UB & Co, 
dass man (hier) den Pointer einfach nutzen kann, ohne Wrapper.

von S. R. (svenska)


Lesenswert?

Rolf M. schrieb:
> Auf Quelltext-Ebene bedeutet eine Zuweisung von 0 an einen Zeiger, dass
> du einen NULL-Zeiger draus machst. Ein Vergleich mit 0 ergibt auch
> wieder Gleichheit.

Das wiederum heißt, dass es per Spezifikation keine Möglichkeit gibt, 
einen Zeiger auf die Adresse 0 von einem NULL-Zeiger zu unterscheiden. 
Daraus folgt ebenfalls, dass die Adresse "0" per Spezifikation nicht mit 
C nutzbar ist, unabhängig davon, wie der Compiler seine NULL-Zeiger 
definiert, da es grundsätzlich undefined behaviour ist.

Gut, hätten wir das mal geklärt.

von Heiko L. (zer0)


Lesenswert?

Es sei denn, man ruft eine Funktion auf, die der Compiler nicht selbst 
übersetzt hat. Ein Pointer, den eine unbekannte Funktion zurückliefert 
kann, wie ich das sehe, dem Standard folgend nicht positiv als 
Nullpointer bestimmt werden - [i]ist[/i] also niemals ein Nullpointer. 
Es kann höchstens sein, dass die binäre Repräsentation seines Wertes mit 
der eines Nullpointers [i]identisch[/i] ist.

von Markus F. (mfro)


Lesenswert?

S. R. schrieb:
> Das wiederum heißt, dass es per Spezifikation keine Möglichkeit gibt,
> einen Zeiger auf die Adresse 0 von einem NULL-Zeiger zu unterscheiden.
> Daraus folgt ebenfalls, dass die Adresse "0" per Spezifikation nicht mit
> C nutzbar ist, unabhängig davon, wie der Compiler seine NULL-Zeiger
> definiert, da es grundsätzlich undefined behaviour ist.

weder, noch.

Ein konstanter NULL-Zeiger kann nicht dereferenziert werden und zeigt 
"irgendwohin" (tatsächlich aber meist doch auf 0L).

Eine auf 0L gesetzte Variable, die ein Pointer ist, zeigt (auf jeder 
Plattform, schliesslich könnte das ja eine valide Adresse sein) auf die 
Adresse 0.

Wurde aber oben schon mal gesagt.

von Rolf M. (rmagnus)


Lesenswert?

S. R. schrieb:
> Rolf M. schrieb:
>> Auf Quelltext-Ebene bedeutet eine Zuweisung von 0 an einen Zeiger, dass
>> du einen NULL-Zeiger draus machst. Ein Vergleich mit 0 ergibt auch
>> wieder Gleichheit.
>
> Das wiederum heißt, dass es per Spezifikation keine Möglichkeit gibt,
> einen Zeiger auf die Adresse 0 von einem NULL-Zeiger zu unterscheiden.
> Daraus folgt ebenfalls, dass die Adresse "0" per Spezifikation nicht mit
> C nutzbar ist, unabhängig davon, wie der Compiler seine NULL-Zeiger
> definiert, da es grundsätzlich undefined behaviour ist.

Nö, eigentlich heißt es das nicht. Aus Sicht von C gibt es eigentlich 
nur Zeiger auf existierende Objekte (die also per Operator & mit der 
Adresse einer noch existierenden Variable befüllt worden sind oder von 
malloc kommen) und ungültige Zeiger, die man nicht dereferenzieren darf. 
Der Nullzeiger ist ein ungültiger Zeiger, der sich von den anderen nur 
darin unterscheidet, dass man auf ihn prüfen kann, weil ein Vergleich 
zweier Nullzeiger immer Gleichheit ergibt. Ein Vergleich eines 
Nullzeigers mit einem Zeiger, in dem nur 0-Bits stehen, muss nicht 
zwingend Gleichheit ergeben.
Warum sollte man nun also die Adresse 0 nicht nutzen können? Wenn der 
Compiler ein Objekt dort ablegt und mir die Adresse zurückgibt, kann ich 
den  Zeiger auch dereferenzieren.

: Bearbeitet durch User
von Heiko L. (zer0)


Lesenswert?

Rolf M. schrieb:
> Aus Sicht von C gibt es eigentlich
> nur Zeiger auf existierende Objekte (die also per Operator & mit der
> Adresse einer noch existierenden Variable befüllt worden sind oder von
> malloc kommen) und ungültige Zeiger, die man nicht dereferenzieren darf.

Das stimmt für C zumindest nicht ganz:
1
An address constant is a null pointer, a pointer to an lvalue designating an object of static storage duration, or a pointer to a function designator; it shall be created explicitly using the unary & operator or an integer constant cast to pointer type ...

von Rolf M. (rmagnus)


Lesenswert?

Heiko L. schrieb:
> Rolf M. schrieb:
>> Aus Sicht von C gibt es eigentlich
>> nur Zeiger auf existierende Objekte (die also per Operator & mit der
>> Adresse einer noch existierenden Variable befüllt worden sind oder von
>> malloc kommen) und ungültige Zeiger, die man nicht dereferenzieren darf.
>
> Das stimmt für C zumindest nicht ganz:
> An address constant

Ich sprach von Zeigern, nicht von Adresskonstanten.

von Heiko L. (zer0)


Lesenswert?

Rolf M. schrieb:
> Ich sprach von Zeigern, nicht von Adresskonstanten.

"cast to pointer type" - verstehe ich nicht, den unterschied.

von Rolf M. (rmagnus)


Lesenswert?

Hmm, ich glaube, ich habe missverstanden, was du mit dem Zitat sagen 
willst.
Ja, ich kann natürlich auch einen Integer in einen Zeiger konvertieren, 
wobei das Ergebnis der Konvertierung dann auch wieder implementation 
defined ist:

"An integer may be converted to any pointer type. Except as previously 
specified, the result is implementation-defined, might not be correctly 
aligned, might not point to an entity of the referenced type, and might 
be a trap representation."

Dein Zitat sagt ja letztendlich nur, dass eine Integerkonstante, die in 
einen Zeigertyp konvertiert wird, eine Adreesskonstante ergibt. Ob ein 
Zeiger, dem ich diese zuweise, dann gültig ist, ist ja wieder eine 
andere Frage.

von Heiko L. (zer0)


Lesenswert?

Rolf M. schrieb:
> Ja, ich kann natürlich auch einen Integer in einen Zeiger konvertieren,
> wobei das Ergebnis der Konvertierung dann auch wieder implementation
> defined ist

Richtig. Der C++-Standard scheint da strikter zu sein: Da ist der Cast 
von beliebigen Zahlen auf Pointer nicht positiv erlaubt, also undefined. 
Definiert ist da nur &object und ((Type*)0) sowie Arithmetik.
(oder ich habe den Passus nicht gefunden)

von S. R. (svenska)


Lesenswert?

Rolf M. schrieb:
> Warum sollte man nun also die Adresse 0 nicht nutzen können?

Weil ich keinen Zeiger auf Adresse 0 konstruieren kann, der nicht 
gleichzeitig ein NULL-Zeiger ist. Da der Vergleich eines NULL-Zeigers 
und der Adresse 0 immer wahr sein muss, gilt das sogar unabhängig vom 
Bitmuster des NULL-Zeigers.

Damit ist jeder Versuch, einen beliebigen Zeiger auf Adresse 0 zu 
dereferenzieren, per Definition undefined behaviour.

Falls ich unrecht habe, bitte ich um ein Gegenbeispiel. Bevorzugt eines, 
welches auf Nicht-Transputer-Systemen funktioniert.

von Heiko L. (zer0)


Lesenswert?

S. R. schrieb:
> Da der Vergleich eines NULL-Zeigers
> und der Adresse 0 immer wahr sein muss, gilt das sogar unabhängig vom
> Bitmuster des NULL-Zeigers.

Er könnte aber auch für Zeiger ein Bit mehr verwenden, als der 
Addressraum eigentlich braucht, welches signalisiert, dass ein Zeiger 
ein Nullzeiger ist. Zum Beispiel ein Paritätsbit, mit dem man dann auch 
prüfen kann, ob irgendwo eine Röhre oder ein Relais durch ist.

von Heiko L. (zer0)


Lesenswert?

Spaß beiseite: Wenn der Compiler nicht zur Kompilierzeit nachweisen 
kann, dass etwas ein Nullpointer ist, wird er kaum Runtime-Checks dafür 
einbauen, es sei denn, man kompiliert mit einem Sanitizer.

von Rolf M. (rmagnus)


Lesenswert?

S. R. schrieb:
> Rolf M. schrieb:
>> Warum sollte man nun also die Adresse 0 nicht nutzen können?
>
> Weil ich keinen Zeiger auf Adresse 0 konstruieren kann, der nicht
> gleichzeitig ein NULL-Zeiger ist. Da der Vergleich eines NULL-Zeigers
> und der Adresse 0 immer wahr sein muss, gilt das sogar unabhängig vom
> Bitmuster des NULL-Zeigers.

Nein, das hab ich doch oben extra geschrieben: Zwei Nullzeiger müssen 
immer Gleichheit ergeben. Ein Nullzeiger und ein Zeiger auf die Adresse 
0 nicht unbedingt.
Du musst hier immer unterscheiden zwischen der C-Ebene, also dem, was im 
Quellcode steht, und dem, was der Compiler daraus macht, also was 
nachher für ein Bitmuster im Speicher steht. Man muss sich von dem 
Gedanken lösen, dass die Konstante 0 auf C-Ebene im Zeigerkontext 
bedeutet, dass das Adresse 0 wäre. Das sind zwei verschiedene 
Abstraktionsebenen.
Mit "Adresse 0" meine ich hier eine Adresse, die ausschließlich aus 
0-Bits besteht, nicht die, die man bekommt, wenn man in C die 
Integer-Konstante 0 in einen Zeiger konvertiert.

> Damit ist jeder Versuch, einen beliebigen Zeiger auf Adresse 0 zu
> dereferenzieren, per Definition undefined behaviour.

Ich sehe da keinen Grund dafür in dem Fall, dass ein Nullzeiger nicht 
auf die Adresse 0 zeigt.

> Falls ich unrecht habe, bitte ich um ein Gegenbeispiel. Bevorzugt eines,
> welches auf Nicht-Transputer-Systemen funktioniert.

Stellen wir uns mal vor, der Compiler definiert, dass ein NULL-Zeiger im 
Speicher mit dem Wert 0x1234 abgelegt wird. Ich schreibe NULL in einen 
Zeiger, dann steht also 0x1234 drin. Ich vergleiche ihn mit NULL, dann 
wird daraus auf Assembler-Ebene ein Vergleich mit 0x1234 -> gleich. Mit 
der Adresse 0 hat das gar nichts zu tun. Wenn wir z.B. nun weiterhin 
annehmen, dass der Compiler den Heap direkt am Anfang des Adressraums 
beginnen lässt, könnte der erste Aufruf von malloc die Adresse 0 
zurückgeben. Die kann ich dann einfach dereferenzieren und benutzen. Ein 
Vergleich mit NULL, also dem internen Wert 0x1234 ergibt auch keine 
Gleichheit. Wo soll da undefiniertes Verhalten herkommen?

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

Ist das Philosophie?

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Rolf M. schrieb:
> Ich vergleiche ihn mit NULL, dann wird daraus auf Assembler-Ebene ein
> Vergleich mit 0x1234 -> gleich.

Das würde bedeuten, daß der Compiler auch bei diesem Code diesen 
magischen Vergleich einbauen würde:
1
int *p;
2
3
p = NULL;
4
5
if (p)  // <--
6
{
7
  machwas();
8
}
9
10
if (!p) // <--
11
{
12
  lasswas();
13
}

Hmm.

Stefanus F. schrieb:
> Ist das Philosophie?

Mindestens. Mir scheint ein Teil der Diskussion akademisch abgehoben zu 
sein.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Rufus Τ. F. schrieb:
> Das würde bedeuten, daß der Compiler auch bei diesem Code diesen
> magischen Vergleich einbauen würde:

Für das zweite if ganz klar, denn da gilt:
"The expression !E is equivalent to (0==E)."

Für das erste konnte ich eine passende Regel auf die Schnelle allerdings 
nicht finden.

Rufus Τ. F. schrieb:
> Stefanus F. schrieb:
>> Ist das Philosophie?
>
> Mindestens. Mir scheint ein Teil der Diskussion akademisch abgehoben zu
> sein.

Ich mag solche Diskussionen, bin mir aber bewusst, dass nicht jeder 
diese Begeisterung teilt. ;-)

: Bearbeitet durch User
von Dr .Sommer (Gast)


Lesenswert?

Rufus Τ. F. schrieb:
> Das würde bedeuten, daß der Compiler auch bei diesem Code diesen
> magischen Vergleich einbauen würde:

Definitiv.

Rufus Τ. F. schrieb:
> Mindestens. Mir scheint ein Teil der Diskussion akademisch abgehoben zu
> sein.
Na und? Programmiersprachen, und insbesondere C und C++, sind abstrakt 
definiert um alle möglichen Umgebungen abzudecken. Nur weil jetzt gerade 
keine Architektur "in" ist, bei der diese Feinheiten wichtig sind, würde 
ich das nicht als abgehoben abtun. C gibt ja z.B. nichtmal das 
Zweier-Komplement vor, d.h. es ist nicht garantiert dass "-32768" mit 
"int" darstellbar ist. Das sind Dinge die man im Hinterkopf behalten 
sollte, und daher auch diskutiert werden können.

Man sollte sich vom Begriff "Null-Pointer" lösen (der ist von C blöd 
gewählt), und stattdessen an "Ungültigen Pointer" o.ä. denken. Wenn man 
etwas ala "(char*) 0" sieht, sollte man die 0 ignorieren und denken 
"Hier wird ein plattformspezifischer Wert für ungültige Pointer 
genommen". Wenn du in Java "null" schreibst, weißt du ja auch nicht ob 
da wirklich nur 0-Bits in die Referenz geschrieben werden, oder doch 
irgendwas anderes.

Da Standard-C sowieso den Zugriff auf beliebige Adressen nicht 
unterstützt, ist es auch keine Einschränkung so ggf. nicht direkt auf 
die tatsächliche Adresse 0 zugreifen zu können. So Dinge wie "*((char*) 
0x1234)=1;" sind eh nicht standardisiert.

von A. S. (Gast)


Lesenswert?

S. R. schrieb:
> Daraus folgt ebenfalls, dass die Adresse "0" per Spezifikation nicht mit
> C nutzbar ist, unabhängig davon, wie der Compiler seine NULL-Zeiger
> definiert, da es grundsätzlich undefined behaviour ist.

S. R. schrieb:
> Damit ist jeder Versuch, einen beliebigen Zeiger auf Adresse 0 zu
> dereferenzieren, per Definition undefined behaviour.
>
> Falls ich unrecht habe, bitte ich um ein Gegenbeispiel. Bevorzugt eines,
> welches auf Nicht-Transputer-Systemen funktioniert.


Ich sehe (für ANSI-C) keinen Hinweis, dass dereferenzieren des 0-Zeigers 
UB ist. Nur für die Library-Funktionen, was ja verständlich ist.

> 4.6.1 Use of library functions
> If an argument to a function has an invalid value (such as a value
> outside the domain of the function, or a pointer outside the address
> space of the program, or a null pointer), the behavior is undefined.

Und funktionieren tut es auf allen Systemen bisher, wer sollte dafür 
extra-Code einbauen? Also egal ob auf 0 was interessantes liegt (was ich 
problemlos auslese), oder ob die Maschine dann abstürzt weil es 
Spezialregister sind.

von Peter D. (peda)


Lesenswert?

Man könnte eine Stringfunktion so implementieren, daß bei einem 
Nullpointer als Argument die Ausgabe nach stdout oder stderr erfolgt.

von A. S. (Gast)


Lesenswert?

Peter D. schrieb:
> Man könnte eine Stringfunktion so implementieren, daß bei einem
> Nullpointer als Argument die Ausgabe nach stdout oder stderr erfolgt.

Naja, da es UB ist, könnte man (um das gängige Beispiel zu nehmen) 
innerhalb der Spezifikation auch WW3 auslösen.

von Dirk B. (dirkb2)


Lesenswert?

Peter D. schrieb:
> Man könnte eine Stringfunktion so implementieren, daß bei einem
> Nullpointer als Argument die Ausgabe nach stdout oder stderr erfolgt.

Es gibt ja mittlerweile auch die strxxx_s-Funktionen, die u.a. den 
Nullzeiger abfangen,

Nicht jeder Computer muss ein stdout haben. Dafür ist C zu allgemein.

Aber, C ist eigentlich nicht für Anfänger gedacht, sondern für Profis, 
die wissen was sie tun.

von Markus F. (mfro)


Lesenswert?

Achim S. schrieb:
> Ich sehe (für ANSI-C) keinen Hinweis, dass dereferenzieren des 0-Zeigers
> UB ist. Nur für die Library-Funktionen, was ja verständlich ist.

§6.5.3.2:
If an invalid  value  has  been  assigned  to  the  pointer, the 
behavior  of  the  unary * operator  is undefined. 87)

Fußnote 87 zu §6.5.3.2:

Among  the  invalid  values  for  dereferencing  a  pointer  by  the 
unary * operator  are  a  null  pointer, an address inappropriately 
aligned for the type of object pointed to, and the address of an object 
after the end of its lifetime.

: Bearbeitet durch User
von Heiko L. (zer0)


Lesenswert?

Markus F. schrieb:
> Achim S. schrieb:
>> Ich sehe (für ANSI-C) keinen Hinweis, dass dereferenzieren des 0-Zeigers
>> UB ist. Nur für die Library-Funktionen, was ja verständlich ist.
>
> §6.5.3.2:
> If an invalid  value  has  been  assigned  to  the  pointer, the
> behavior  of  the  unary * operator  is undefined. 87)
>
> Fußnote 87 zu §6.5.3.2:
>
> Among  the  invalid  values  for  dereferencing  a  pointer  by  the
> unary * operator  are  a  null  pointer, an address inappropriately
> aligned for the type of object pointed to, and the address of an object
> after the end of its lifetime.

Und was heißt das?

x = (void*)0 -> is(x, Nullpointer), x = nullptr -> is(x, Nullpointer)

Ich sehe nicht, wie irgendetwas ein Nullpointer sein könnte, wo nicht
"(void*)0" oder "nullptr" steht.

Das gibt -fno-delete-null-pointer-checks eine ganz neue Bedeutung.

: Bearbeitet durch User
von Markus F. (mfro)


Lesenswert?

Das ist die Antwort auf die Frage, wo definiert sei, dass das 
Dereferenzieren eines Nullpointers UB ist.

Ansonsten gilt: eine Rose ist eine Rose ist eine Rose...

von Heiko L. (zer0)


Lesenswert?

Markus F. schrieb:
> Ansonsten gilt: eine Rose ist eine Rose ist eine Rose...

Nein, das zeigt die Notwendigkeit zwischen den Ebenen des Quelltextes 
und den generierten Anweisungen sauber zu trennen.
Einen Nullpointer zu dereferenzieren ist genau dann UB, wenn der 
Quelltext den Schluß zulässt, dass es einer ist.

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Ich hatte mal einen 8051 Code, der sich merkwürdig verhielt. Ich hab 
dann gesehen, daß der Autor an einigen Stellen Pointerargumente auf 0 
testet. Nun fängt aber beim 8051 XDATA bei 0 an, d.h. das ist eine 
gültige Datenadresse. Ich hab dann in der Linkerzeile XDATA an 0x0001 
anfangen lassen und das Programm lief wieder einwandfrei.

von Heiko L. (zer0)


Lesenswert?

@Peter Du klingst wie die Vernunft in Person.

von S. R. (svenska)


Lesenswert?

Rolf M. schrieb:
>>> Warum sollte man nun also die Adresse 0 nicht nutzen können?
>>
>> Weil ich keinen Zeiger auf Adresse 0 konstruieren kann, der nicht
>> gleichzeitig ein NULL-Zeiger ist. Da der Vergleich eines NULL-Zeigers
>> und der Adresse 0 immer wahr sein muss, gilt das sogar unabhängig vom
>> Bitmuster des NULL-Zeigers.
>
> Nein, das hab ich doch oben extra geschrieben: Zwei Nullzeiger müssen
> immer Gleichheit ergeben. Ein Nullzeiger und ein Zeiger auf die Adresse
> 0 nicht unbedingt.

Das ist korrekt. Andererseits ist ein (char*)0 per Definition ein 
Nullzeiger, lässt sich also nicht von einem solchen unterscheiden. 
Unabhängig davon, welches Bitmuster ein (char*)0 hat.

Auf einen Nullzeiger kann man mit if(!ptr) testen, äquivalent zu 
if(ptr==0), also einem Vergleich mit der Zahl 0. Auch das ist unabhängig 
vom Bitmuster des Nullzeigers.

> Du musst hier immer unterscheiden zwischen der C-Ebene, also dem, was im
> Quellcode steht, und dem, was der Compiler daraus macht, also was
> nachher für ein Bitmuster im Speicher steht.

Nein, ich habe doch mehrfach ausgeführt, dass diese Argumentation vom 
Bitmuster des Zeigers unabhängig ist. Ein Vergleich eines Nullzeigers 
mit der Zahl 0 ist per Definition wahr.

> Man muss sich von dem Gedanken lösen, dass die Konstante 0
> auf C-Ebene im Zeigerkontext bedeutet, dass das Adresse 0 wäre.

Ein Nullzeiger hat das gleiche Bitmuster wie ein Zeiger auf die Adresse 
0. Daraus folgt nicht, dass ein Zeiger auf die Adresse 0 nur aus 
Nullbits besteht.

> Mit "Adresse 0" meine ich hier eine Adresse, die ausschließlich aus
> 0-Bits besteht, nicht die, die man bekommt, wenn man in C die
> Integer-Konstante 0 in einen Zeiger konvertiert.

Sicherlich, wenn ich mir "Adresse 0" als etwas bestimmtes definiere, 
während ich "das, was ich bekomme, wenn ich einen Zeiger auf Adresse 0" 
als etwas unbestimmtes definiere, dann kann ich mir auch immer einen 
Fall konstruieren, indem sie unterschiedlich sind.

Der Punkt ist, dass ein Zeiger auf Adresse 0 (egal, wo sie im Speicher 
liegt und wie ihr Bitmuster aussieht) immer ein Nullzeiger ist, damit 
per Definition ein ungültiger Zeiger, und daraus folgt, dass die Adresse 
0 nicht nutzbar ist.

>> Damit ist jeder Versuch, einen beliebigen Zeiger auf Adresse 0 zu
>> dereferenzieren, per Definition undefined behaviour.
>
> Ich sehe da keinen Grund dafür in dem Fall, dass ein Nullzeiger nicht
> auf die Adresse 0 zeigt.

Er muss auf die Adresse 0 zeigen, aber kein Bitmuster aus Nullen haben.

>> Falls ich unrecht habe, bitte ich um ein Gegenbeispiel. Bevorzugt eines,
>> welches auf Nicht-Transputer-Systemen funktioniert.
>
> Stellen wir uns mal vor, der Compiler definiert, dass ein NULL-Zeiger im
> Speicher mit dem Wert 0x1234 abgelegt wird. Ich schreibe NULL in einen
> Zeiger, dann steht also 0x1234 drin.

Ich schreibe (char*)0 in den Zeiger, dann steht da auch 0x1234 drin.
Sonst wäre ja ein Vergleich mit 0 (bzw. auf dessen Ungleichheit) niemals 
wahr.

> Ich vergleiche ihn mit NULL, dann
> wird daraus auf Assembler-Ebene ein Vergleich mit 0x1234 -> gleich. Mit
> der Adresse 0 hat das gar nichts zu tun.

Doch, weil du Adresse 0 als 0x1234 definiert hast.

> Wenn wir z.B. nun weiterhin annehmen, dass der Compiler
> den Heap direkt am Anfang des Adressraums beginnen lässt,
> könnte der erste Aufruf von malloc die Adresse 0 zurückgeben.

Wenn ich diese in einem intptr_t wandle, steht da also eine 0 drin.
Ein Vergleich dieses intptr_t mit dem Zeiger muss wahr sein, weil if(!x) 
äquivalent zu if(x==0) ist.

> Die kann ich dann einfach dereferenzieren und benutzen. Ein
> Vergleich mit NULL, also dem internen Wert 0x1234 ergibt auch keine
> Gleichheit. Wo soll da undefiniertes Verhalten herkommen?

Die einzige Möglichkeit, dass das funktioniert, wäre, wenn malloc() zwar 
einen Zeiger auf die Adresse 0 zurückgibt, dieser aber ungleich 0 ist.

Das ändert übrigens nichts daran, dass ich nach einem Beispiel gefragt 
habe, was irgendwo funktioniert (bevorzugt auf Nicht-Transputern). Deins 
war nur rein theoretisch. :-)

Heiko L. schrieb:
> Einen Nullpointer zu dereferenzieren ist genau dann UB, wenn der
> Quelltext den Schluß zulässt, dass es einer ist.

Nein, das ist noch viel schlimmer.

Einen Nullpointer zu dereferenzieren, ist immer UB, und zwar schon 
bevor das Programm überhaupt gestartet wurde. Es gibt keine Garantien, 
dass vor einem UB auftretende Effekte stattfinden.

Darum ist das Verhalten, dass ein Segfault auch die vor dem Segfault 
stattfindenden printf()'s frisst, legal.

: Bearbeitet durch User
von Heiko L. (zer0)


Lesenswert?

S. R. schrieb:
> Darum ist das Verhalten, dass ein Segfault auch die vor dem Segfault
> stattfindenden printf()'s frisst, legal.

Das hat aber nichts mit der Sprache, sondern mit dem Betriebssystem zu 
tun. Und auch eigentlich nichts mit dem Nullpointer. Immer, wenn das 
Betriebssystem einen Fault generiert kann das passieren. Auch, wenn die 
Sprache da einen garantiert zulässigen Zugriff sähe.
1
static int global;
2
....
3
printf("test");
4
global=1;
kann zu so einer Situation führen, wenn man das Linker-Script ein 
bisschen tweaked.

von Heiko L. (zer0)


Lesenswert?

S. R. schrieb:
> Einen Nullpointer zu dereferenzieren, ist immer UB, und zwar schon
> bevor das Programm überhaupt gestartet wurde.

Und genau das gilt nur, wenn sich das aus dem Quelltext herleiten lässt.
1
((char*)0) = 1;
ist UB.
1
char *x = my_magic_function();
2
*x = 1;
ist NIEMALS ub.
Ganz einfach deshalb, weil x nie ein Nullpointer per Definition sein 
kann. Er kann nur wie einer aussehen...

: Bearbeitet durch User
von A. S. (Gast)


Lesenswert?

S. R. schrieb:
> Er muss auf die Adresse 0 zeigen,

Gibt es dafür einen Beleg? "char *p=0;" ist halt etwas ganz anderes als 
"char *p=4;", schalt mal Warnungen ein.

Prinzipiell ist ((int *) 0) etwas anderes als 0, das erste ist ein 
int-Pointer auf die Adress 0, das zweite ein Nullpointer. Und die müssen 
nichtmal gleich sein! 0 muss nur mit ((void*) 0) gleich sein!

Zwar ist es weit verbreitet, dass der Nullpointer genauso aussieht wie 
ein Pointer auf Adress 0, aber nur weil sie gleich aussehen (und deshalb 
z.B. Lib-Funktionen die Freiheit UB haben) sind sie nicht das selbe.

von Rolf M. (rmagnus)


Lesenswert?

S. R. schrieb:
>> Nein, das hab ich doch oben extra geschrieben: Zwei Nullzeiger müssen
>> immer Gleichheit ergeben. Ein Nullzeiger und ein Zeiger auf die Adresse
>> 0 nicht unbedingt.
>
> Das ist korrekt. Andererseits ist ein (char*)0 per Definition ein
> Nullzeiger, lässt sich also nicht von einem solchen unterscheiden.

Dass man einen Nullzeiger nicht von einem Nullzeiger unterscheiden kann, 
ist offensichtlich.

> Auf einen Nullzeiger kann man mit if(!ptr) testen, äquivalent zu
> if(ptr==0), also einem Vergleich mit der Zahl 0. Auch das ist unabhängig
> vom Bitmuster des Nullzeigers.

Richtig.

>> Du musst hier immer unterscheiden zwischen der C-Ebene, also dem, was im
>> Quellcode steht, und dem, was der Compiler daraus macht, also was
>> nachher für ein Bitmuster im Speicher steht.
>
> Nein, ich habe doch mehrfach ausgeführt, dass diese Argumentation vom
> Bitmuster des Zeigers unabhängig ist. Ein Vergleich eines Nullzeigers
> mit der Zahl 0 ist per Definition wahr.

Ja eben. Genau deshalb muss man das ja sauber trennen.

>> Man muss sich von dem Gedanken lösen, dass die Konstante 0
>> auf C-Ebene im Zeigerkontext bedeutet, dass das Adresse 0 wäre.
>
> Ein Nullzeiger hat das gleiche Bitmuster wie ein Zeiger auf die Adresse
> 0.

C definiert einen Nullzeiger nicht als einen Zeiger auf Adresse 0. Es 
sagt lediglich, dass eine Integerkonstante mit dem Wert 0 im 
Zeigerkontext eine Spezialbedeutung hat, nämlich dass sie zu einem 
Nullzeiger wird.
Wenn ich (char*)1 - 1 schreibe, muss dabei kein Nullzeiger rauskommen. 
Und wie ich oben schon geschrieben habe, darf selbst
1
int i = 0;
2
char* c = (char*)i;
einen anderen Zeigerwert ergeben als ein
1
char* c = (char*)0;
denn nur die Konvertierung einer Integer-*Konstanten* mit dem Wert 0 in 
einen Zeigertyp muss zu einem Nullzeiger führen. Da i keine Konstante 
ist, gilt das dafür nicht.

> Daraus folgt nicht, dass ein Zeiger auf die Adresse 0 nur aus
> Nullbits besteht.

Naja, dann hast du eine andere Definition des Begriffs "Adresse 0" als 
ich. Deshalb hab ich im letzten Post dazugeschrieben, welche ich meine:

>> Mit "Adresse 0" meine ich hier eine Adresse, die ausschließlich aus
>> 0-Bits besteht, nicht die, die man bekommt, wenn man in C die
>> Integer-Konstante 0 in einen Zeiger konvertiert.
>
> Sicherlich, wenn ich mir "Adresse 0" als etwas bestimmtes definiere,
> während ich "das, was ich bekomme, wenn ich einen Zeiger auf Adresse 0"
> als etwas unbestimmtes definiere, dann kann ich mir auch immer einen
> Fall konstruieren, indem sie unterschiedlich sind.

Adresse 0 als die Adresse, die aus dem Nullzeiger kommt, zu definieren, 
ist ja auch nicht wirklich sinnvoll. Wenn z.B. der Nullzeiger den Wert 
0x1000 hat, dann wäre also Adresse 0 bei 0x1000, Adresse 1 dann 
plötzlich bei 1.
Ich (bzw. ISO C) definiere, dass eine Integerkonstante mit dem Wert 0 
bei Konvertierung in einen Zeigertyp eine Sonderrolle einnimmt, die aber 
nur für genau diesen einen Fall gilt. Statt den Wert 0 hier zu 
verwenden, hätte man lieber ein eigenes Schlüsselwort "nullptr" 
spendieren sollen, so wie es C++ inzwischen getan hat. Dann wäre klarer, 
dass das nichts, aber auch gar nichts mit Adresse 0 zu tun haben muss.

> Der Punkt ist, dass ein Zeiger auf Adresse 0 (egal, wo sie im Speicher
> liegt und wie ihr Bitmuster aussieht) immer ein Nullzeiger ist, damit
> per Definition ein ungültiger Zeiger, und daraus folgt, dass die Adresse
> 0 nicht nutzbar ist.

Ja gut, für deine Definition von "Adresse 0" gilt das.

>>> Damit ist jeder Versuch, einen beliebigen Zeiger auf Adresse 0 zu
>>> dereferenzieren, per Definition undefined behaviour.
>>
>> Ich sehe da keinen Grund dafür in dem Fall, dass ein Nullzeiger nicht
>> auf die Adresse 0 zeigt.
>
> Er muss auf die Adresse 0 zeigen, aber kein Bitmuster aus Nullen haben.

Die Adresse ist doch das Bitmuster.

>>> Falls ich unrecht habe, bitte ich um ein Gegenbeispiel. Bevorzugt eines,
>>> welches auf Nicht-Transputer-Systemen funktioniert.
>>
>> Stellen wir uns mal vor, der Compiler definiert, dass ein NULL-Zeiger im
>> Speicher mit dem Wert 0x1234 abgelegt wird. Ich schreibe NULL in einen
>> Zeiger, dann steht also 0x1234 drin.
>
> Ich schreibe (char*)0 in den Zeiger, dann steht da auch 0x1234 drin.

Ja natürlich. NULL ist das selbe wie (char*)0. Es ist nicht weiter als 
ein Makro, das vom Präprozessor entweder durch (char*)0 oder durch 0 
ersetzt wird.

> Sonst wäre ja ein Vergleich mit 0 (bzw. auf dessen Ungleichheit) niemals
> wahr.

Ja.

>> Wenn wir z.B. nun weiterhin annehmen, dass der Compiler
>> den Heap direkt am Anfang des Adressraums beginnen lässt,
>> könnte der erste Aufruf von malloc die Adresse 0 zurückgeben.
>
> Wenn ich diese in einem intptr_t wandle, steht da also eine 0 drin.

Ja.

> Ein Vergleich dieses intptr_t mit dem Zeiger muss wahr sein, weil if(!x)
> äquivalent zu if(x==0) ist.

Man kann in C einen Zeiger nicht direkt mit einem Integer vergleichen. 
Zunächst muss eins davon in den Typ des anderen konvertiert werden.

>> Die kann ich dann einfach dereferenzieren und benutzen. Ein
>> Vergleich mit NULL, also dem internen Wert 0x1234 ergibt auch keine
>> Gleichheit. Wo soll da undefiniertes Verhalten herkommen?
>
> Die einzige Möglichkeit, dass das funktioniert, wäre, wenn malloc() zwar
> einen Zeiger auf die Adresse 0 zurückgibt, dieser aber ungleich 0 ist.

Ja, ein Vergleich mit (void*)0 ergibt Ungleichheit, da (void*)0 eben 
nicht unbedingt auf Adresse 0 zeigen muss, auch wenn hier im Quelltext 
eine 0 steht.
Übrigens stehe ich mit der Definition nicht ganz alleine da. Siehe 
https://en.wikipedia.org/wiki/Null_pointer , wo folgender Satz steht:
"The C standard does not say that the null pointer is the same as the 
pointer to memory address 0, though that may be the case in practice."

> Das ändert übrigens nichts daran, dass ich nach einem Beispiel gefragt
> habe, was irgendwo funktioniert (bevorzugt auf Nicht-Transputern). Deins
> war nur rein theoretisch. :-)

Dieses Beispiel existiert prinzipbedingt natürlich nur für Compiler, auf 
denen ein Nullzeiger nicht nur aus 0-Bits besteht. Also was erwartest 
du?

> Einen Nullpointer zu dereferenzieren, ist immer UB, und zwar schon
> bevor das Programm überhaupt gestartet wurde.

Sagen wir mal: So gut wie immer. Für den Operator sizeof ist garantiert, 
dass er bis auf VLAs den übergebenen Operanden nicht evaluiert und damit 
auch im Falle eines Zeiger nicht dereferenziert.
Für
1
char* p = 0;
2
sizeof *p;
ist also garantiert, dass es kein UB auslöst.

> Es gibt keine Garantien, dass vor einem UB auftretende Effekte
> stattfinden.

Wenn UB im Spiel ist, gibt es überhaupt keine Garantieren mehr. Nicht 
eine einzige. Man google mal nach "nasal demons".

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Heiko L. schrieb:
> ((char*)0) = 1;
> ist UB.

Das ist kein UB, sondern löst einen Compiler-Fehler aus.
Du meintest vermutlich
1
*((char*)0) = 1;

> char *x = my_magic_function();
> *x = 1;
> ist NIEMALS ub.
> Ganz einfach deshalb, weil x nie ein Nullpointer per Definition sein
> kann. Er kann nur wie einer aussehen...

Das kann ich nicht nachvollziehen. Wenn my_magic_function() so aussieht:
1
char* my_magic_function()
2
{
3
    return NULL;
4
}
dann ist x selbstverständlich ein Nullzeiger. In beiden Beispielen hast 
du einen Nullzeiger.

Achim S. schrieb:
> Prinzipiell ist ((int *) 0) etwas anderes als 0, das erste ist ein
> int-Pointer auf die Adress 0, das zweite ein Nullpointer.

Das ist falsch.

> Und die müssen nichtmal gleich sein! 0 muss nur mit ((void*) 0) gleich
> sein!

Nein. Der Zieltyp des Zeigers ist egal. Das ergibt sich aus folgender 
Passage aus Kapitel 6.3.2.3:

********************************************
An integer constant expression with the value 0, or such an expression 
cast to type void *, is called a null pointer constant. If a null 
pointer constant is converted to a pointer type, the resulting pointer, 
called a null pointer, is guaranteed to compare unequal to a pointer to 
any object or function.

Conversion of a null pointer to another pointer type yields a null 
pointer  of that type. Any two null pointers shall compare equal.
********************************************

(int*)0 ist also keine Nullzeigerkonstante, aber dennoch ein Nullzeiger, 
und  ein Vergleich mit einem beliebigen anderen Nullzeiger muss 
Gleichheit ergeben.

: Bearbeitet durch User
von Heiko L. (zer0)


Lesenswert?

Rolf M. schrieb:
> Das kann ich nicht nachvollziehen.
Steht weiter oben, und auch in dem Post, was gemeint ist.

>Wenn my_magic_function() so aussieht:
Weitere Fakten hinzufügen verändert selbstverständlich die Menge an 
zulässig erschließlichen Aussagen, meinst du nicht?
Es wird eine Funktion aufgerufen, deren Implementierung nicht bekannt 
ist.

von Rolf M. (rmagnus)


Lesenswert?

Heiko L. schrieb:
> Rolf M. schrieb:
>> Das kann ich nicht nachvollziehen.
> Steht weiter oben, und auch in dem Post, was gemeint ist.
>
>>Wenn my_magic_function() so aussieht:
> Weitere Fakten hinzufügen verändert selbstverständlich die Menge an
> zulässig erschließlichen Aussagen, meinst du nicht?

Es kann sie verringern, aber nicht erweitern.

> Es wird eine Funktion aufgerufen, deren Implementierung nicht bekannt
> ist.

Wenn die Funktion einen Nullzeiger zurückliefern kann, wenn ihre 
Implementation bekannt ist, dann kann sie es auch, wenn die 
Implementation unbekannt ist.

: Bearbeitet durch User
von Heiko L. (zer0)


Lesenswert?

Rolf M. schrieb:
> Heiko L. schrieb:
>> Rolf M. schrieb:
>>> Das kann ich nicht nachvollziehen.
>> Steht weiter oben, und auch in dem Post, was gemeint ist.
>>
>>>Wenn my_magic_function() so aussieht:
>> Weitere Fakten hinzufügen verändert selbstverständlich die Menge an
>> zulässig erschließlichen Aussagen, meinst du nicht?
>
> Es kann sie verringern, aber nicht erweitern.
Unsinn.

>> Es wird eine Funktion aufgerufen, deren Implementierung nicht bekannt
>> ist.
>
> Wenn die Funktion einen Nullzeiger zurückliefern kann, wenn ihre
> Implementation bekannt ist, dann kann sie es auch, wenn die
> Implementation unbekannt ist.

Keinen Nullpointer per Definition - das ist der Punkt. Der Pointer 
könnte nur später zu einem werden
1
void* p = my_magic_function();
2
if(!p) {
3
  // jetzt ist p ein Nullpointer
4
} else {
5
  // hier nicht
6
}
:)

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Heiko L. schrieb:
>> Es kann sie verringern, aber nicht erweitern.
> Unsinn.

Wenn ich mehr Wissen habe, gibt es weniger Möglichkeiten.


Heiko L. schrieb:
> Keinen Nullpointer per Definition - das ist der Punkt.

Ich hab ehrlich gesagt keine Idee, was du damit meinst. Er wird doch 
nicht zum Nullpointer dadurch, dass ich prüfe, ob er einer ist.
Die Definition ist, dass ein Nullzeiger durch Konvertierung einer 
Nullzeigerkonstante in einen Zeigertyp entsteht. Wenn ich nicht weiß, ob 
die Funktion eine solche Konvertierung macht, weiß ich auch nicht, ob 
ein Nullzeiger zurückkommt.

> Der Pointer könnte nur später zu einem werden
> void* p = my_magic_function();
> if(!p) {
>   // jetzt ist p ein Nullpointer

Wenn du hier reinkommst, war er es aber vorher auch schon.

> } else {
>   // hier nicht
> }
> :)

von S. R. (svenska)


Lesenswert?

Heiko L. schrieb:
>> Darum ist das Verhalten, dass ein Segfault auch die vor dem Segfault
>> stattfindenden printf()'s frisst, legal.
>
> Das hat aber nichts mit der Sprache, sondern mit dem Betriebssystem zu
> tun. Und auch eigentlich nichts mit dem Nullpointer. Immer, wenn das
> Betriebssystem einen Fault generiert kann das passieren.

Wenn die Programmiersprache garantiert, dass ein Effekt auftreten muss 
(printf, fwrite), bevor ein folgender Effekt auftreten darf 
(Segfault), dann darfst du dich darauf auch verlassen.

In C ist es legal, dass ein zur Laufzeit auftretendes UB sämtliche 
vorherigen Ergebnisse vernichten darf, einschließlich aller offenen 
Dateien, dem Leben, dem Universum und dem ganzen Rest. Das hat nichts 
mit dem Betriebssystem zu tun, das nutzt diesen Umstand nur aus.

Heiko L. schrieb:
>> Einen Nullpointer zu dereferenzieren, ist immer UB, und zwar schon
>> bevor das Programm überhaupt gestartet wurde.
>
> Und genau das gilt nur, wenn sich das aus dem Quelltext herleiten lässt.

Nein, Nullzeiger können auch zur Laufzeit auftreten. Dann ist es 
trotzdem UB, sie zu dereferenzieren. Ungültige Zeiger sind immer 
ungültig, nicht nur zur Compilezeit.

Rolf M. schrieb:
> Dieses Beispiel existiert prinzipbedingt natürlich nur für Compiler, auf
> denen ein Nullzeiger nicht nur aus 0-Bits besteht.

Also doch eine rein theoretische Diskussion mit garantiert keiner 
praktischen Relevanz. ;-)

Rolf M. schrieb:
>> Einen Nullpointer zu dereferenzieren, ist immer UB, und zwar schon
>> bevor das Programm überhaupt gestartet wurde.
>
> Sagen wir mal: So gut wie immer. Für den Operator sizeof ist garantiert,
> dass er bis auf VLAs den übergebenen Operanden nicht evaluiert und damit
> auch im Falle eines Zeiger nicht dereferenziert.

Womit das Dereferenzieren also doch immer UB ist (und sizeof nicht 
dereferenziert, obwohl man's hinschreibt). ;-)

Deinen restlichen Ausführungen kann ich folgen. Danke dafür.

von Rolf M. (rmagnus)


Lesenswert?

S. R. schrieb:
> Rolf M. schrieb:
>> Dieses Beispiel existiert prinzipbedingt natürlich nur für Compiler, auf
>> denen ein Nullzeiger nicht nur aus 0-Bits besteht.
>
> Also doch eine rein theoretische Diskussion mit garantiert keiner
> praktischen Relevanz. ;-)

Naja, wenn du die real existierenden Systeme, auf denen es das gibt, 
ausschließt, bleiben natürlich nur hypothetische übrig.
Aber ja, bei nahezu allen gängigen Compilern wird soweit ich weiß NULL 
als Zeiger ausschließlich aus 0-Bits dargestellt, da es viele Dinge 
vereinfacht und vermutlich die meisten Programmierer ihre Programme in 
der Annahme schreiben, dass es so ist. Es ist also gewissermaßen auch 
ein "Teufelskreis": Das Thema ist eher akademischer Natur, da praktisch 
keine Plattform es anders macht, was aber mit daran liegt, dass viele 
gar nicht wissen, dass es auch anders möglich wäre und die, die es 
wissen, es als "akademisches Thema" abtun. ;-)

von S. R. (svenska)


Lesenswert?

Rolf M. schrieb:
> Naja, wenn du die real existierenden Systeme, auf denen es das gibt,
> ausschließt, bleiben natürlich nur hypothetische übrig.

Stimmt. Wobei das mit dem Einerkomplement genauso ist... es ist halt 
nachteilig, von der optimalen (für gegebenes Problem) Lösung 
abzuweichen, also macht das keiner mehr.

von Heiko L. (zer0)


Lesenswert?

Rolf M. schrieb:
> Wenn ich mehr Wissen habe, gibt es weniger Möglichkeiten.

Von nichts kommt nichts.

Rolf M. schrieb:
> Wenn du hier reinkommst, war er es aber vorher auch schon.
Das kann man vor dem Vergleich aber nicht wissen. Für mich ist
1
void* p = my_magic_function();
nur irgendein pointer. Um es anders zu formulieren: Nach der Zeile dort 
ist die Aussage "p ist ein Nullpointer" nicht positiv aus definierten 
Begriffen deduzierbar. Im if-case ist das nicht mehr der Fall.
1
void* p = my_magic_function();
2
if(!p) {
3
 *p = "UB";
4
}

: Bearbeitet durch User
von Heiko L. (zer0)


Lesenswert?

S. R. schrieb:
> Wenn die Programmiersprache garantiert, dass ein Effekt auftreten muss
> (printf, fwrite), bevor ein folgender Effekt auftreten darf
> (Segfault), dann darfst du dich darauf auch verlassen.

Nur in Sonderfällen. Das Betriebssystem kann den Thread oder Prozess 
beenden in dem ein Programm läuft, man kann die Hardware anweisen, einen 
Reset durchzuführen, man kann den Instruction-Pointer sonstwohin setzen 
lassen.
Der einzige Fall, in dem der C-Standard eine hinreichende Garantie 
abgiebt, ist die Theorie in einem Internet-Forum.

S. R. schrieb:
> Nein, Nullzeiger können auch zur Laufzeit auftreten. Dann ist es
> trotzdem UB, sie zu dereferenzieren.

Nur wenn durch einen Vergleich gezeigt wurde, dass es ein Nullpointer 
ist.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Heiko L. schrieb:
> Rolf M. schrieb:
>> Wenn du hier reinkommst, war er es aber vorher auch schon.
> Das kann man vor dem Vergleich aber nicht wissen.

Ja eben, das sage ich ja. Man kann nicht wissen, ob es ein Nullzeiger 
ist. Du hattest aber oben im Gegensatz dazu behauptet, dass es keiner 
sein kann.

> Um es anders zu formulieren: Nach der Zeile dort ist die Aussage "p ist
> ein Nullpointer" nicht positiv aus definierten Begriffen deduzierbar.

Das lässt aber nicht den Schluss zu, dass p definitiv kein Nullzeiger 
sein kann.

Heiko L. schrieb:
>> Wenn du hier reinkommst, war er es aber vorher auch schon.
> Das kann man vor dem Vergleich aber nicht wissen.

Dein Wissen ändert aber nichts daran, was in dem Zeiger steht. Der 
Compiler macht natürlich das Verhalten des Programms nicht davon 
abhängig, ob du weißt, was in dem Zeiger steht. Das ist völlig 
unabhängig davon.

Heiko L. schrieb:
> S. R. schrieb:
>> Wenn die Programmiersprache garantiert, dass ein Effekt auftreten muss
>> (printf, fwrite), bevor ein folgender Effekt auftreten darf
>> (Segfault), dann darfst du dich darauf auch verlassen.
>
> Nur in Sonderfällen. Das Betriebssystem kann den Thread oder Prozess
> beenden in dem ein Programm läuft, man kann die Hardware anweisen, einen
> Reset durchzuführen, man kann den Instruction-Pointer sonstwohin setzen
> lassen.

Ich würde den Normalbetrieb nicht unbedingt als Sonderfall betrachten.

> S. R. schrieb:
>> Nein, Nullzeiger können auch zur Laufzeit auftreten. Dann ist es
>> trotzdem UB, sie zu dereferenzieren.
>
> Nur wenn durch einen Vergleich gezeigt wurde, dass es ein Nullpointer
> ist.

Nein, eine Dereferenzierung eines Nullzeigers ist immer UB, unabhängig 
davon, ob du vorher geprüft hast, ob es einer ist.

von Heiko L. (zer0)


Lesenswert?

Rolf M. schrieb:
> Ja eben, das sage ich ja. Man kann nicht wissen, ob es ein Nullzeiger
> ist. Du hattest aber oben im Gegensatz dazu behauptet, dass es keiner
> sein kann.

Ja, richtig. Die Aussage zu machen "Es ist ein Nullpointer" ist 
unzulässig, deshalb darf der Compiler nicht davon ausgehen :)

Rolf M. schrieb:
> Das lässt aber nicht den Schluss zu, dass p definitiv kein Nullzeiger
> sein kann.

Naja, doch: Der Wert der Expression in einem AST ist nicht NULL zu der 
Zeit, also kann der Compiler nicht anfangen, undefinierten Code zu 
erzeugen.
1
void *p = my_func();
2
*p = "This assignment will be translated normally";
Wenn man keinen Runtime-Checker mit einkompilieren lässt, ist das oben 
ein normal laufendes Programm, auch dann, wenn p == NULL wäre. Das ist 
in dem Beispiel, wo 0 (oder was immer) eine gültige Addresse ist, 
durchaus drin.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Heiko L. schrieb:
> Rolf M. schrieb:
>> Ja eben, das sage ich ja. Man kann nicht wissen, ob es ein Nullzeiger
>> ist. Du hattest aber oben im Gegensatz dazu behauptet, dass es keiner
>> sein kann.
>
> Ja, richtig. Die Aussage zu machen "Es ist ein Nullpointer" ist
> unzulässig, deshalb darf der Compiler nicht davon ausgehen :)

Soweit richtig. Der Compiler darf nicht davon ausgehen, was aber immer 
noch nicht bedeutet, dass es keiner ist.

> Rolf M. schrieb:
>> Das lässt aber nicht den Schluss zu, dass p definitiv kein Nullzeiger
>> sein kann.
>
> Naja, doch: Der Wert der Expression in einem AST ist nicht NULL zu der
> Zeit, also kann der Compiler nicht anfangen, undefinierten Code zu
> erzeugen.

Der Compiler erzeugt keinen undefinierten Code, sondern der Code erzeugt 
undefiniertes Verhalten, weil die Vorbedingung "Zeiger ist nicht NULL", 
die für seine Ausführung erforderlich wäre, nicht gegeben ist. Dazu muss 
der Compiler gar nicht wissen, was in dem Zeiger steht, also geht das 
auch komplett zur Laufzeit.
Das ist genau wie z.B. bei einer Division durch 0. Der Compiler muss da 
auch nicht wissen, dass der Divisor 0 ist damit das in die Hose geht.
Undefiniertes Verhalten ist in der Regel auch nicht etwas, das der 
Compiler mit Absicht erzeugt. Also es steht im Compiler selbst nirgends 
sowas wie
1
if zeiger is NULL then invoke_undefined_behavior();

Der Compiler darf, wenn ich den Zeiger dereferenziere, davon ausgehen, 
dass er nicht null ist. Das zeichnet undefiniertes Verhalten ja aus: Der 
Compiler darf bestimmte Annahmen über das Programm treffen, und der 
Programmierer ist dafür verantwortlich, dass das auch stimmt. Wenn er 
das nicht tut und somit das Programm gegen diese Annahmen verstößt, 
gibt's undefiniertes Verhalten, aber nicht, weil der Compiler dies 
bewusst herbeiführt, sondern weil er einfach den Fall nicht 
berücksichtigt hat. Wobei er natürlich nicht die Pflicht hat, den Fall 
nicht zu berücksichtigen, aber er hat die Erlaubnis dazu.

> void *p = my_func();
> *p = "This assignment will be translated normally";
> Wenn man keinen Runtime-Checker mit einkompilieren lässt, ist das oben
> ein normal laufendes Programm, auch dann, wenn p == NULL wäre.

Nein. Der Compiler erzeugt ein Programm, das unter der Annahme 
ausgeführt wird, dass p nicht NULL ist. Genau deshalb gibt das ja 
undefiniertes Verhalten, wenn es doch NULL ist. Es passiert irgendwas 
mehr oder weniger zufälliges, weil die Eingangsdaten des Code nicht der 
Vereinbarung entsprechen.

> Das ist in dem Beispiel, wo 0 (oder was immer) eine gültige Addresse ist,
> durchaus drin.

Undefiniertes Verhalten heißt nicht, dass das Programm zwingend 
abstürzen muss. Es kann auch das gewünschte Verhalten zeigen. 
Undefiniert heißt, dass aus Sicht von ISO C alles, und zwar wirklich 
alles passieren kann.

: Bearbeitet durch User
von Heiko L. (zer0)


Lesenswert?

Rolf M. schrieb:
>> Die Aussage zu machen "Es ist ein Nullpointer" ist unzulässig...
> Soweit richtig. Der Compiler darf nicht davon ausgehen, was aber immer
> noch nicht bedeutet, dass es keiner ist.

Doch das heißt es. Das folgt aus "tertium non datur".


Rolf M. schrieb:
> Der Compiler erzeugt keinen undefinierten Code, sondern der Code erzeugt
> undefiniertes Verhalten, weil die Vorbedingung "Zeiger ist nicht NULL",
> die für seine Ausführung erforderlich wäre, nicht gegeben ist.

1. Es gibt keine Vorbedingungen für Zuweisungen sondern nur 
vorbedingungen für undefiniertes Verhalten.
2. Sind diese Vorbedingungen nicht erfüllt. (s.o.)

Rolf M. schrieb:
> Dazu muss der Compiler gar nicht wissen, was in dem Zeiger steht, also
> geht das auch komplett zur Laufzeit.
Nope. Nirgendwo im Standard steht, dass eine Zuweisung einen 
gespeicherten Wert ändert. Nicht, dass sie einen Vergleich durchführt. 
Das könnte höchstens die abstrakte Maschine machen. Deren Eigenschaften 
sind aber nicht Gegenstand der Definition.

Rolf M. schrieb:
> Das ist genau wie z.B. bei einer Division durch 0. Der Compiler muss da
> auch nicht wissen, dass der Divisor 0 ist damit das in die Hose geht.
Das muss es ja auch nicht.

Rolf M. schrieb:
> Undefiniertes Verhalten ist in der Regel auch nicht etwas, das der
> Compiler mit Absicht erzeugt.

Wenn etwas erwiesen undefiniertes Verhalten ist kann er machen was er 
will. Ansonsten ist es idR ziemlich genau durch die konkreten 
Eigenschaften der abstrakten Maschine bestimmt.

Rolf M. schrieb:
> Also es steht im Compiler selbst nirgends
> sowas wie "if zeiger is NULL then invoke_undefined_behavior();"
Hmm, vielleicht nicht buchstabengetreu...

Rolf M. schrieb:
> Der Compiler darf, wenn ich den Zeiger dereferenziere, davon ausgehen, dass er 
nicht null ist.

Nein, das muss er in diesem Fall - Die Nullpointer-Eigenschaft ist 
positiv bestimmt.

Rolf M. schrieb:
> Der Compiler darf bestimmte Annahmen über das Programm treffen, und der 
Programmierer ist dafür verantwortlich, dass das auch stimmt.

Ja, richtig. Deshalb ist
1
void *p = my_func();
2
*p = "niemals NULL";
weshalb er ein folgendes
1
if(!p) {
2
 printf("gar nicht mehr auswerten muss");
3
}

von Heiko L. (zer0)


Lesenswert?

Heiko L. schrieb:
> Nirgendwo im Standard steht, dass eine Zuweisung einen
> gespeicherten Wert ändert.

Das soll natürlich "Irgendwo" heißen.

von Rolf M. (rmagnus)


Lesenswert?

Heiko L. schrieb:
> Rolf M. schrieb:
>>> Die Aussage zu machen "Es ist ein Nullpointer" ist unzulässig...
>> Soweit richtig. Der Compiler darf nicht davon ausgehen, was aber immer
>> noch nicht bedeutet, dass es keiner ist.
>
> Doch das heißt es. Das folgt aus "tertium non datur".

Der Compiler ist nicht die allwissende Macht. Nur weil der Compiler 
etwas annimmt, ist das nicht automatisch eine Tatsache.

> Rolf M. schrieb:
>> Der Compiler erzeugt keinen undefinierten Code, sondern der Code erzeugt
>> undefiniertes Verhalten, weil die Vorbedingung "Zeiger ist nicht NULL",
>> die für seine Ausführung erforderlich wäre, nicht gegeben ist.
>
> 1. Es gibt keine Vorbedingungen für Zuweisungen

Doch, natürlich gibt es die.

> sondern nur vorbedingungen für undefiniertes Verhalten.
> 2. Sind diese Vorbedingungen nicht erfüllt. (s.o.)
>
> Rolf M. schrieb:
>> Dazu muss der Compiler gar nicht wissen, was in dem Zeiger steht, also
>> geht das auch komplett zur Laufzeit.
> Nope. Nirgendwo im Standard steht, dass eine Zuweisung einen
> gespeicherten Wert ändert. Nicht, dass sie einen Vergleich durchführt.

Ein Vergleich hat damit nichts zu tun. Du scheinst zwar zu glauben, dass 
es irgendwie notwendig ist, einen Vergleich durchzuführen, damit das 
Verhalten undefiniert wird. Das ist aber Unsinn. Nirgendwo in der 
C-Definition steht etwas davon, dass das erforderlich wäre.

> Das könnte höchstens die abstrakte Maschine machen. Deren Eigenschaften
> sind aber nicht Gegenstand der Definition.

Doch, selbstverständlich sind sie das. Die sind sogar der zentrale 
Gegenstand der Definition. Was auch so drin steht (Kapitel 5.1.2.3 
"Program Execution"):
"The semantic descriptions in this International Standard describe the 
behavior of an abstract machine in which issues of optimization are 
irrelevant."

> Rolf M. schrieb:
>> Das ist genau wie z.B. bei einer Division durch 0. Der Compiler muss da
>> auch nicht wissen, dass der Divisor 0 ist damit das in die Hose geht.
> Das muss es ja auch nicht.

Doch, zumindest für Integers. Denn da gibt es keinen korrekten Wert für 
das Ergebnis.

> Rolf M. schrieb:
>> Undefiniertes Verhalten ist in der Regel auch nicht etwas, das der
>> Compiler mit Absicht erzeugt.
>
> Wenn etwas erwiesen undefiniertes Verhalten ist kann er machen was er
> will. Ansonsten ist es idR ziemlich genau durch die konkreten
> Eigenschaften der abstrakten Maschine bestimmt.

Ja, und laut ISO C darf dann die machen, was sie will. -> undefiniertes 
Verhalten.

> Rolf M. schrieb:
>> Also es steht im Compiler selbst nirgends
>> sowas wie "if zeiger is NULL then invoke_undefined_behavior();"
> Hmm, vielleicht nicht buchstabengetreu...

Wäre ja auch Unsinn. Warum sollte der Compiler, wenn er doch weiß, dass 
der Zeiger NULL ist, absichtlich dafür sorgen, dass das Verhalten 
undefiniert ist? Die ganze Idee von undefiniertem Verhalten zielt genau 
auf den Fall ab, in dem der Compiler sowas nicht weiß. Sie gibt ihm die 
Erlaubnis, sich auch nicht darum zu kümmern, was dann möglicherweise 
passieren könnte.

von Heiko L. (zer0)


Lesenswert?

Rolf M. schrieb:
> Der Compiler ist nicht die allwissende Macht. Nur weil der Compiler
> etwas annimmt, ist das nicht automatisch eine Tatsache.

Das hat mit Allwissenheit nichts zu tun, sondern ist nur eine Frage 
simpler Logik.

Rolf M. schrieb:
> Ein Vergleich hat damit nichts zu tun. Du scheinst zwar zu glauben, dass
> es irgendwie notwendig ist, einen Vergleich durchzuführen, damit das
> Verhalten undefiniert wird. Das ist aber Unsinn. Nirgendwo in der
> C-Definition steht etwas davon, dass das erforderlich wäre.

Das ist genau falsch herum gedacht. Undefiniertes Verhalten hat 
Vorbedingungen, die erfüllt sein können oder auch nicht.

Rolf M. schrieb:
> Doch, selbstverständlich sind sie das. Die sind sogar der zentrale
> Gegenstand der Definition.

Und was steht dort, wie ein "update of the value" stattzufinden habe?
Ich finde da nichts von Belang. Vielleicht heißt das Ding deswegen 
abstrakt.
Sonst würde in der Praxis ja nicht viel passieren.

Rolf M. schrieb:
> Doch, zumindest für Integers. Denn da gibt es keinen korrekten Wert für
> das Ergebnis.

Und was heißt das? UB, also überhaupt nichts.

Rolf M. schrieb:
> Wäre ja auch Unsinn. Warum sollte der Compiler, wenn er doch weiß, dass
> der Zeiger NULL ist, absichtlich dafür sorgen, dass das Verhalten
> undefiniert ist?

Ja, das Frag mal, wenn der Wert von NULL außerhalb des C-Kosmos auf eine 
gültige Addresse verwiese.

Rolf M. schrieb:
> Die ganze Idee von undefiniertem Verhalten zielt genau
> auf den Fall ab, in dem der Compiler sowas nicht weiß. Sie gibt ihm die
> Erlaubnis, sich auch nicht darum zu kümmern, was dann möglicherweise
> passieren könnte.

Das stimmt so einfach nicht. Positiv definiert undefiniertes Verhalten 
hat den Sinn, dem Compiler optimierungen zu ermöglichen. Warum sollte 
man Use-Cases durch überflüssige Definitionen ausschließen? Wenn 0 eine 
normale Addresse ist, ist sie das halt.

von S. R. (svenska)


Lesenswert?

Heiko L. schrieb:
>> Wenn die Programmiersprache garantiert, dass ein Effekt auftreten muss
>> (printf, fwrite), bevor ein folgender Effekt auftreten darf
>> (Segfault), dann darfst du dich darauf auch verlassen.
>
> Nur in Sonderfällen. Das Betriebssystem kann den Thread oder Prozess
> beenden in dem ein Programm läuft, man kann die Hardware anweisen, einen
> Reset durchzuführen, man kann den Instruction-Pointer sonstwohin setzen
> lassen.

Wenn mir die Programmiersprache für zwei Ereignisse X und Y garantiert, 
dass Ereignis X immer vor Ereignis Y auftritt, dann darf ich davon 
ausgehen, dass Ereignis X eingetreten ist, wann immer ich Ereignis Y 
sehe.

Es gibt Programmiersprachen, die mir garantieren, dass ein Ereignis X 
(Daten werden in eine Datei geschrieben) vor Ereignis Y (Programm stürzt 
wegen Division durch Null ab) eintritt, und diese Garantien dürfen nicht 
"mal eben so" vom Betriebssystem außer kraft gesetzt werden.

Wenn mir ein reales Betriebssystem jederzeit den Prozess aus beliebigen 
Gründen wegschießen darf (OOM-Killer, Chaos-Monkey), ist es schlicht 
unzuverlässig - und zwar genau so unzuverlässig, wie mir ein kaputtes 
Auto keine vom Hersteller garantierte Funktion bereitstellt.

> Der einzige Fall, in dem der C-Standard eine hinreichende Garantie
> abgiebt, ist die Theorie in einem Internet-Forum.

Nun, im Gegensatz zu so ziemlich allen anderen Programmiersprachen 
garantiert C das oben beschriebene Verhalten nicht. Wenn irgendwann zur 
Laufzeit ein UB auftritt, dann war das Programm schon vor dem Start 
inkorrekt, also ist jegliches Verhalten des Programms dem Standard 
entsprechend.

Heiko L. schrieb:
> Das ist genau falsch herum gedacht. Undefiniertes Verhalten hat
> Vorbedingungen, die erfüllt sein können oder auch nicht.

Definiertes Verhalten hat Vorbedingungen. Undefiniertes Verhalten 
tritt genau dann auf, wenn diese nicht erfüllt sind.

Analogie: Eine mathematische Funktion wird durch ihren 
Definitionsbereich definiert, nicht durch "alle Bereiche, wo sie nicht 
definiert ist".

Heiko L. schrieb:
>> Doch, zumindest für Integers. Denn da gibt es keinen
>> korrekten Wert für das Ergebnis.
>
> Und was heißt das? UB, also überhaupt nichts.

Doch: Es heißt, dass du genau keine Annahme über das gesamte 
Programm treffen darfst. /Und es auch niemals durftest./ Auch dann, wenn 
die Division erst zur Laufzeit eingetreten ist, weil der Benutzerdepp 
eine Null ins falsche Formular eingetragen hat.

von Heiko L. (zer0)


Lesenswert?

S. R. schrieb:
> Wenn mir die Programmiersprache für zwei Ereignisse X und Y garantiert,
> dass Ereignis X immer vor Ereignis Y auftritt, dann darf ich davon
> ausgehen, dass Ereignis X eingetreten ist, wann immer ich Ereignis Y
> sehe.

Klar - soweit die C-Insel betroffen ist. Wenn da steht
1
__asm_set_instruction_pointer();
2
printf("1");
3
printf("2");
sollte man über eine Erweiterung des Horizonts nachdenken.


S. R. schrieb:
> Es gibt Programmiersprachen, die mir garantieren, dass ein Ereignis X
> (Daten werden in eine Datei geschrieben) vor Ereignis Y (Programm stürzt
> wegen Division durch Null ab) eintritt, und diese Garantien dürfen nicht
> "mal eben so" vom Betriebssystem außer kraft gesetzt werden.
Für C trifft das jedenfalls nicht zu. Was soll das überhaupt heißen "in 
eine Datei geschrieben"? Und was ist eine Datei? Wenn durch den 
Fault-Handler ein Hardware-Reset ausgelöst wird, können die Daten dann 
fehlerfrei wieder gelesen werden?
Also ich kenne keine Programmiersprache die so etwas garantieren könnte. 
Ist auch nicht Thema der formalen Definition der Sprache.
Für C steht da halt sinngemäß: Wenn man flush aufgerufen hat oder 
Buffering  abgeschaltet hat sind die Daten an das "external file" 
übergeben worden. Zur richtigen Zeit den Stecker ziehen wäre da ein 
Härtetest, ob dein Ereignis X da echt so eingetreten ist, wie dein 
(schlampiger) Wortlaut es zu implizieren versucht.

S. R. schrieb:
> Wenn mir ein reales Betriebssystem jederzeit den Prozess aus beliebigen
> Gründen wegschießen darf (OOM-Killer, Chaos-Monkey), ist es schlicht
> unzuverlässig - und zwar genau so unzuverlässig, wie mir ein kaputtes
> Auto keine vom Hersteller garantierte Funktion bereitstellt.
Für Aufgaben, wo das wichtig wäre, ist es so dann jedenfalls ungeeignet: 
Dann gehört der Absturz eben in's Kalkül.

S. R. schrieb:
> Definiertes Verhalten hat Vorbedingungen. Undefiniertes Verhalten
> tritt genau dann auf, wenn diese nicht erfüllt sind.

Das stimmt so nicht. Er listet positive Bedingungen für UB auf. 
Beispiel:
1
If  any other  characters  are  encountered  in  a  source  file  (except  in  an  identifier,  a  character constant, a string literal, a header name, a comment, or a preprocessing token that is never converted to a token), the behavior is undefined.
Das heißt "Bedingungen => UB", oder auch "UB oder not(Bedingungen)". 
Hier müsste man allerdings erst einmal sauber erschließen, was die 
Negation einer Bedingung bedeutet. In diesem einfachem Beispiel wäre es 
keinesfalls genug zu sagen "no other characters were found" sei ein 
Garant für definiertes Verhalten eines Programms. Man müsste alle 
Bedingungen auflisten können. Das kann der Standard nicht, da er auf 
"implementation defined behaviour" verweist, welches seinerseits einfach 
sagen könnte: "undefined".
Zu diesem Problem siehe auch "schwache Negation".
In unserem einfachen Beispiel ist
1
char **p = my_func();
2
*p = "";
ein gültiger Ausschnitt eines C-Programs, soweit es den Compiler 
betrifft. Schluß aus.

S. R. schrieb:
> Heiko L. schrieb:
>>> Doch, zumindest für Integers. Denn da gibt es keinen
>>> korrekten Wert für das Ergebnis.
>>
>> Und was heißt das? UB, also überhaupt nichts.
>
> Doch: Es heißt, dass du genau keine Annahme über das gesamte
> Programm treffen darfst.

Tue ich ja nicht. Meine Aussage in dem Kontext war: Kann laufen, kann 
nicht laufen. Rolf meinte, dass es schief gehen müsse.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Heiko L. schrieb:
> Rolf M. schrieb:
>> Der Compiler ist nicht die allwissende Macht. Nur weil der Compiler
>> etwas annimmt, ist das nicht automatisch eine Tatsache.
>
> Das hat mit Allwissenheit nichts zu tun, sondern ist nur eine Frage
> simpler Logik.

Richtig. Und die Logik sagt: Die Wahrheit entsteht nicht durch Annahmen. 
vielmehr können Annahmen der Wahrheit entsprechen - oder eben auch 
nicht.

> Rolf M. schrieb:
>> Doch, selbstverständlich sind sie das. Die sind sogar der zentrale
>> Gegenstand der Definition.
>
> Und was steht dort, wie ein "update of the value" stattzufinden habe?
> Ich finde da nichts von Belang.

Ein "update of the value" ist dazu nicht nötig. Das reine 
Dereferenzieren reicht. In deinem obigen Beispiel wäre folgendes schon 
ausreichend für undefiniertes Verhalten:
1
char *p = my_func();
2
*p;

"If an invalid value has been assigned to the pointer, the behavior of 
the  unary * operator is undefined."

> Vielleicht heißt das Ding deswegen abstrakt.

Sie heißt abstrakt wegen der so genannten as-if-Regel. Das Verhalten der 
abstrakten Maschine ist in C genau definiert (abgesehen von den Stellen, 
wo es undefiniert ist ;-) ), aber das physische System, auf dem das dann 
läuft, muss nicht exakt das selbe tun, solange bestimmte Teil des 
Verhaltens gleich bleiben. Durch Optimierungen können z.B. unnötige 
Speicherzugriffe oder Berechnungen von Werten, die nachher nicht benutzt 
werden, entfernt werden, die die abstrakte Maschine laut ISO C aber 
durchführen müsste. Das ist in ISO C leider nicht so ausdrücklich und 
einfach beschrieben wie in ISO C++.

> Rolf M. schrieb:
>> Doch, zumindest für Integers. Denn da gibt es keinen korrekten Wert für
>> das Ergebnis.
>
> Und was heißt das? UB, also überhaupt nichts.

Hab ich ja geschrieben: Genau wie bei Zeigern ist das Verhalten 
undefiniert. Und zwar immer, egal ob vorher irgendwas geprüft worden ist 
oder ob der Compiler den Wert kennt.

> Rolf M. schrieb:
>> Wäre ja auch Unsinn. Warum sollte der Compiler, wenn er doch weiß, dass
>> der Zeiger NULL ist, absichtlich dafür sorgen, dass das Verhalten
>> undefiniert ist?
>
> Ja, das Frag mal, wenn der Wert von NULL außerhalb des C-Kosmos auf eine
> gültige Addresse verwiese.

Ach, das hatten wir doch schon abgehakt.

> Rolf M. schrieb:
>> Die ganze Idee von undefiniertem Verhalten zielt genau
>> auf den Fall ab, in dem der Compiler sowas nicht weiß. Sie gibt ihm die
>> Erlaubnis, sich auch nicht darum zu kümmern, was dann möglicherweise
>> passieren könnte.
>
> Das stimmt so einfach nicht. Positiv definiert undefiniertes Verhalten
> hat den Sinn, dem Compiler optimierungen zu ermöglichen.

Optimierungen werden dadurch auch ermöglicht, ja. Aber wenn der Compiler 
eh schon weiß, dass der Zeiger NULL ist und damit das Programm nicht wie 
vorgesehen funktionieren wird,  was soll er dann noch optimieren? Den 
Absturz?
Die Optimierung besteht da eher darin, dass er den Zugriff eben einfach 
machen kann, ohne vorher prüfen zu müssen, ob der Zeiger auch wirklich 
gültig ist. Um die Konsequenzen, wenn das nicht der Fall ist, muss er 
sich nicht kümmern. Diese Fall muss nicht abgefangen werden.

Ich würde ich aber nochmal zu einer früheren Aussage fragen:

Heiko L. schrieb:
> char *x = my_magic_function();
> *x = 1;
> ist NIEMALS ub.

Wenn das Verhalten wie du behauptest nicht undefiniert ist, wie sieht 
denn dann das nach ISO C genau definierte Verhalten aus, wenn 
my_magic_function() da einen Nullzeiger returniert?

Heiko L. schrieb:
> Was soll das überhaupt heißen "in eine Datei geschrieben"? Und was ist
> eine Datei?

Siehe Kapitel 7.9.13 "Files".

> Wenn durch den Fault-Handler ein Hardware-Reset ausgelöst wird, können
> die Daten dann fehlerfrei wieder gelesen werden?

Das garantiert C natürlich nicht. Es gibt da zwei Möglichkeiten:

1 - Der Fehler wurde durch das Hervorrufen von undefiniertem Verhalten 
des Programms ausgelöst - dann garantiert C sowieso gar nichts mehr.

2 - Der Fehler kam durch irgendetwas außerhalb des Programms zustande - 
dann hat sich die Implementation nicht an die Regeln von ISO C gehalten. 
Das sieht nämlich nicht vor, dass das System aufgrund von z.B. einem 
Hardware-Fehler was anderes tut als es eigentlich sollte.

> Ist auch nicht Thema der formalen Definition der Sprache.
> Für C steht da halt sinngemäß: Wenn man flush aufgerufen hat oder
> Buffering  abgeschaltet hat sind die Daten an das "external file"
> übergeben worden.

Sie sind in einen Datenstrom geschrieben worden. Soweit ich weiß, wird 
nirgends garantiert, dass das bedeutet, dass sie auf einem physischen 
Gerät gespeichert worden sind.

S. R. schrieb:
> Wenn mir ein reales Betriebssystem jederzeit den Prozess aus beliebigen
> Gründen wegschießen darf (OOM-Killer, Chaos-Monkey), ist es schlicht
> unzuverlässig - und zwar genau so unzuverlässig, wie mir ein kaputtes
> Auto keine vom Hersteller garantierte Funktion bereitstellt.

Das ist ungefähr so unzuverlässig, wie ein Auto, das, nachdem ich es in 
den Graben gefahren habe, nicht mehr funktioniert.

> S. R. schrieb:
>> Definiertes Verhalten hat Vorbedingungen. Undefiniertes Verhalten
>> tritt genau dann auf, wenn diese nicht erfüllt sind.
>
> Das stimmt so nicht. Er listet positive Bedingungen für UB auf.
> Beispiel:If  any other  characters

"any other" heißt hier soviel wie: Wenn alle vorher genannten 
Bedingungen nicht zutreffen. Man hätte den Satz auch weglassen können 
(da alles, was nicht explizit definiert wurde, natürlich undefiniert 
ist), aber er wurde zur Klarheit noch dazu geschrieben.

> Das kann der Standard nicht, da er auf "implementation defined behaviour"
> verweist, welches seinerseits einfach sagen könnte: "undefined".

Nein, kann es nicht. "implementation defined" heißt, dass ISO C der 
Implementation mehrere Möglichkeiten gibt und die dann definiert, welche 
davon sie nutzt. Das Verhalten muss aber reproduzierbar, wohldefiniert 
und auch dokumentiert sein.

> In unserem einfachen Beispiel ist
> char **p = my_func();
> *p = "";
> ein gültiger Ausschnitt eines C-Programs, soweit es den Compiler
> betrifft. Schluß aus.

Für den Compiler ja, für die abstrakte Maschine nicht unbedingt.

> S. R. schrieb:
>> Heiko L. schrieb:
>>>> Doch, zumindest für Integers. Denn da gibt es keinen
>>>> korrekten Wert für das Ergebnis.
>>>
>>> Und was heißt das? UB, also überhaupt nichts.
>>
>> Doch: Es heißt, dass du genau keine Annahme über das gesamte
>> Programm treffen darfst.
>
> Tue ich ja nicht. Meine Aussage in dem Kontext war: Kann laufen, kann
> nicht laufen. Rolf meinte, dass es schief gehen müsse.

Ja, wie oben steht: Ein gültiger Wert für das Ergebnis existiert in 
diesem Fall nicht, also kann hier selbstverständlich kein gültiges 
Ergebnis rauskommen.

von S. R. (svenska)


Lesenswert?

Heiko L. schrieb:
>> Wenn mir die Programmiersprache für zwei Ereignisse X und Y garantiert,
>> dass Ereignis X immer vor Ereignis Y auftritt, dann darf ich davon
>> ausgehen, dass Ereignis X eingetreten ist, wann immer ich Ereignis Y
>> sehe.
>
> Klar - soweit die C-Insel betroffen ist.

Das ist Unfug. Wenn mir eine Maschine garantiert, dass Lichtschranke A 
vor Lichtschranke B unterbrochen wurde, dann darf ich die Steuerung 
davon ausgehen lassen, dass "Lichtschranke B unterbrochen" bedeutet, 
dass Lichtschranke A vorher unterbrochen war.

Und wenn dem nicht so ist, dann ist die Maschine schlicht kaputt (bzw. 
nicht mehr standardkonform), was wiederum UB ist und keine Aussage über 
die Funktion zulässt.

Das hat mit C nichts zu tun.

Heiko L. schrieb:
> Für C trifft das jedenfalls nicht zu. Was soll das überhaupt heißen "in
> eine Datei geschrieben"? Und was ist eine Datei? Wenn durch den
> Fault-Handler ein Hardware-Reset ausgelöst wird, können die Daten dann
> fehlerfrei wieder gelesen werden?

Für C trifft das nicht zu. Das schrieb ich bereits.

Was "in eine Datei geschrieben" bedeutet, ist genau definiert, und zwar 
an der gleichen Stelle, wo "was ist eine Datei" definiert wird. Das kann 
in der Programmiersprache, im Laufzeitsystem oder in einer 
unterstützenden Bibliothek der Fall sein.

Du hackst da auf einer Strohkrähe rum.

Heiko L. schrieb:
> Also ich kenne keine Programmiersprache die so etwas garantieren könnte.

Java? C#? Rust? Kotlin? ObjC?

Heiko L. schrieb:
> Zur richtigen Zeit den Stecker ziehen wäre da ein Härtetest,
> ob dein Ereignis X da echt so eingetreten ist, wie dein
> (schlampiger) Wortlaut es zu implizieren versucht.

Eine Grundvoraussetzung für die Funktion eines Computerprogramms ist, 
dass der Computer fehlerfrei läuft. Im Übrigen sind gerade Datenbanken 
darauf angewiesen, dass Ereignisse zumindest an bestimmten 
Sequenzpunkten in der garantierten Reihenfolge aufgetreten sind. Das 
Betriebssystem springt durch einige brennende Reifen, um genau dieses 
Verhalten ebenfalls zu garantieren. Lies mal die Probleme, die ein neues 
Dateisystem lösen muss, um performant und zuverlässig zu sein.

Heiko L. schrieb:
> Das kann der Standard nicht, da er auf
> "implementation defined behaviour" verweist,
> welches seinerseits einfach sagen könnte: "undefined".

Das ist falsch. C definiert den Unterschied zwischen "implementation 
defined" und "undefined" sogar sehr genau, im Gegensatz zu so ziemlich 
allen anderen Sprachen.

Heiko L. schrieb:
>>> Und was heißt das? UB, also überhaupt nichts.
>>
>> Doch: Es heißt, dass du genau keine Annahme über das gesamte
>> Programm treffen darfst.
>
> Tue ich ja nicht. Meine Aussage in dem Kontext war: Kann laufen, kann
> nicht laufen. Rolf meinte, dass es schief gehen müsse.

"UB" heißt, dass jedes mögliche Ergebnis im Sinne des Standards korrekt 
ist. Wenig überraschend zählt auch "intended behaviour" dazu.

Ein kaputtes Auto erfüllt die Herstellergarantien nicht. Daraus 
abzuleiten, dass und wie es noch fährt, erfordert zusätzliche Annahmen. 
Die sind aber weder garantiert (sonst würde der Hersteller darüber eine 
Aussage treffen) noch konstant.

Im Allgemeinen läuft es nicht.
Im Speziellen kann es laufen, unter gewissen Bedingungen 
(Optimierungen abgeschaltet, bestimmte Compilerversion, etc).

von Heiko L. (zer0)


Lesenswert?

Rolf M. schrieb:
> Richtig. Und die Logik sagt: Die Wahrheit entsteht nicht durch Annahmen.
> vielmehr können Annahmen der Wahrheit entsprechen - oder eben auch
> nicht.

Und wenn für eine Annahme kein Grund besteht, nimmt man sie nicht an.

Rolf M. schrieb:
> Siehe Kapitel 7.9.13 "Files".

Ja - und dieser Text heißt so viel wie "Also wir sind dann hier an 
dieser Stelle fertig."

Rolf M. schrieb:
> Wenn das Verhalten wie du behauptest nicht undefiniert ist, wie sieht
> denn dann das nach ISO C genau definierte Verhalten aus, wenn
> my_magic_function() da einen Nullzeiger returniert?

Also in genau dem Fall würde evtl. noch ein volatile oder eine 
Speicherbarriere fehlen, weil der Code sonst ggf. entfernt werden wird.
Ich würde aber sagen, dass das Kompilat in dem Fall das enthalten muss, 
was ein Werteupdate der abstrakten Maschine repräsentiert. Das wäre 
nicht unbedingt der Fall, ließe sich a-priori auf undefiniertes 
Verhalten schließen.

Rolf M. schrieb:
> Aber wenn der Compiler
> eh schon weiß, dass der Zeiger NULL ist und damit das Programm nicht wie
> vorgesehen funktionieren wird,  was soll er dann noch optimieren? Den
> Absturz?

Wenn er schließen kann, dass er NULL ist, kann er "if(p)..."-Passagen 
entfernen. Und Warnungen ausgeben, falls er auf UB schließen kann.

Rolf M. schrieb:
>> Wenn durch den Fault-Handler ein Hardware-Reset ausgelöst wird, können
>> die Daten dann fehlerfrei wieder gelesen werden?
>
> Das garantiert C natürlich nicht. Es gibt da zwei Möglichkeiten:
>
> 1 - Der Fehler wurde durch das Hervorrufen von undefiniertem Verhalten
> des Programms ausgelöst - dann garantiert C sowieso gar nichts mehr.

Das ist a-priori korrekt.

Rolf M. schrieb:
> 2 - Der Fehler kam durch irgendetwas außerhalb des Programms zustande -
> dann hat sich die Implementation nicht an die Regeln von ISO C gehalten.
> Das sieht nämlich nicht vor, dass das System aufgrund von z.B. einem
> Hardware-Fehler was anderes tut als es eigentlich sollte.

Und das ist das Argument, warum man ein Programm selbstverständlich 
inhaltlich betrachten muss, um einen "korrekten" Ablauf vorherzusagen. 
Wenn in einem Windows Programm etwas á la
1
  ExitProcess(0);
2
  printf("Never get here!");
steht, ist nicht damit zu rechnen, dass das printf erreicht wird.

S. R. schrieb:
> Das ist Unfug. Wenn mir eine Maschine garantiert, dass Lichtschranke A
> vor Lichtschranke B unterbrochen wurde, dann darf ich die Steuerung
> davon ausgehen lassen, dass "Lichtschranke B unterbrochen" bedeutet,
> dass Lichtschranke A vorher unterbrochen war.

Nicht, wenn du weißt, dass du die Drähte vertauscht hast. ;)
Die Integration eines Softwaresystems in ein größeres und das 
Zusammenwirken der Komponenten ist natürlich nichts, was sich innerhalb 
einer Komponente bestimmen ließe. Wenn ich per Debugger mit den Werten 
von Variablen herumspiele darf man nicht unbedingt davon ausgehen, das 
Ergebnis würde sich nicht ändert. Außer man meint, eine Deduktion 
a-priori aus einem unvollständigen wäre mit der Realität deckungsgleich.

S. R. schrieb:
> Das hat mit C nichts zu tun.

Nee, richtig. C ist nur die Form eines Inhalts. In dem 
ExitProcess-Beispiel oben ist nichts kaputt.

S. R. schrieb:
> Eine Grundvoraussetzung für die Funktion eines Computerprogramms ist,
> dass der Computer fehlerfrei läuft. Im Übrigen sind gerade Datenbanken
> darauf angewiesen, dass Ereignisse zumindest an bestimmten
> Sequenzpunkten in der garantierten Reihenfolge aufgetreten sind. Das
> Betriebssystem springt durch einige brennende Reifen, um genau dieses
> Verhalten ebenfalls zu garantieren. Lies mal die Probleme, die ein neues
> Dateisystem lösen muss, um performant und zuverlässig zu sein.

Ja - und das sind Sachen, die der C-Standard nicht löst, lösen kann und 
lösen will. Nach einem fflush sind die Daten jedenfalls nicht mehr in 
einem Buffer der libc, sondern in einem "external File", was immer das 
auch sein mag. A-priori ist auch eine Speicherbarriere kein Garant, dass 
ein Event vor einem anderen auftritt, wenn danach noch UB auftritt - 
geschenkt. Allderfings muss dieses UB dann seinerseits a-priori 
deduzierbar sein. Ansonsten wäre das eine Sache der abstrakten Maschine. 
Und da gibt es, ebenso wie bei Dateisystemen, solche und solche. Ganz zu 
schweigen von Hardwaretreibern und was da noch alles so auf dem Weg 
liegen mag.

von Peter D. (peda)


Lesenswert?


von S. R. (svenska)


Lesenswert?

Heiko L. schrieb:
> Nicht, wenn du weißt, dass du die Drähte vertauscht hast. ;)

Du erwartest also ernsthaft, dass ein fehlerhaftes System korrekt 
funktioniert? Mann, musst du ein Gottvertrauen haben...

Heiko L. schrieb:
> Nach einem fflush sind die Daten jedenfalls nicht mehr in
> einem Buffer der libc, sondern in einem "external File",
> was immer das auch sein mag.

Und wenn ich nach dem fflush ein UB hervorrufe, ist das nicht mehr 
garantiert, denn dann war es nie garantiert.

Ich weiß ja nicht, warum du ständig auf dem a-priori rumreitest. Ein UB 
zur Laufzeit invalidiert rückwirkend sämtliche Ergebnisse, die du 
hättest haben können.

Heiko L. schrieb:
> Und das ist das Argument, warum man ein Programm selbstverständlich
> inhaltlich betrachten muss, um einen "korrekten" Ablauf vorherzusagen.

Das wäre dann äquialent zum Halteproblem.

> Wenn in einem Windows Programm etwas á la
>   ExitProcess(0);
>   printf("Never get here!");
> steht, ist nicht damit zu rechnen, dass das printf erreicht wird.

Doch, damit ist zu rechnen (z.B. könnte ExitProcess fehlschlagen oder 
eine schlecht benamte MsgBox sein). Der Standard kennt ExitProcess 
nicht, also ist der Aufruf keine hinreichende Bedingung für "der Code 
endet hier".

von Rolf M. (rmagnus)


Lesenswert?

Heiko L. schrieb:
> Rolf M. schrieb:
>> Richtig. Und die Logik sagt: Die Wahrheit entsteht nicht durch Annahmen.
>> vielmehr können Annahmen der Wahrheit entsprechen - oder eben auch
>> nicht.
>
> Und wenn für eine Annahme kein Grund besteht, nimmt man sie nicht an.

Es gibt ja einen: Der Programmierer hat garantiert, dass der Zeiger 
nicht NULL ist. Da so ein Programmierer allerdings ein inhärent 
fehlerbehaftetes Element ist, kann die Annahme eben trotzdem falsch 
sein.
Wenn man immer nur Sachen annehmen würde, die garantiert wahr sind, 
wären es ja auch keine Annahmen mehr.

> Rolf M. schrieb:
>> Wenn das Verhalten wie du behauptest nicht undefiniert ist, wie sieht
>> denn dann das nach ISO C genau definierte Verhalten aus, wenn
>> my_magic_function() da einen Nullzeiger returniert?
>
> Also in genau dem Fall würde evtl. noch ein volatile oder eine
> Speicherbarriere fehlen, weil der Code sonst ggf. entfernt werden wird.
> Ich würde aber sagen, dass das Kompilat in dem Fall das enthalten muss,
> was ein Werteupdate der abstrakten Maschine repräsentiert. Das wäre
> nicht unbedingt der Fall, ließe sich a-priori auf undefiniertes
> Verhalten schließen.

Es gibt in ISO C vier Möglichkeiten, was passieren kann:

1. Exakt das, was in ISO C selbst definiert ist.
2. "unspecified". Dann muss der Compiler das Verhalten aus einer von 
mehreren Möglichkeiten festlegen.
3. "implementation defined". Wie 2, nur dass es auch dokumentiert sein 
muss.
4. "undefined". Irgendwas in ISO C nicht weiter beschriebenes kann 
passieren.

Mehr Möglichkeiten lässt die Sprachdefinition nicht offen.
Variante 1 ist natürlich genau in ISO C beschrieben, Variante 2 und 3 
werden auch explizit erwähnt. Ansonsten gilt Variante 4 (übrigens auch, 
wenn nichts anderes explizit angegeben ist, denn was nicht definiert 
ist, ist eben undefiniert).
Wenn das Verhalten nicht undefined ist, dann muss es eine der drei 
anderen Varianten sein. Meine Frage wäre also, welche davon es deiner 
Meinung nach ist und in welcher Passage der C-Definition das steht.

> S. R. schrieb:
>> Das ist Unfug. Wenn mir eine Maschine garantiert, dass Lichtschranke A
>> vor Lichtschranke B unterbrochen wurde, dann darf ich die Steuerung
>> davon ausgehen lassen, dass "Lichtschranke B unterbrochen" bedeutet,
>> dass Lichtschranke A vorher unterbrochen war.
>
> Nicht, wenn du weißt, dass du die Drähte vertauscht hast. ;)

Auch hier ändert sich das Verhalten der Maschine nicht abhängig davon, 
ob du weißt, dass sie falsch verdrahtet ist oder nicht. Sie arbeitet 
basierend auf der Annahme, dass alles richtig verdrahtet ist. So wie der 
Compiler basierend auf der Annahme arbeitet, dass du ihm keinen 
Nullzeiger zum dereferenzieren gibst.

> Die Integration eines Softwaresystems in ein größeres und das
> Zusammenwirken der Komponenten ist natürlich nichts, was sich innerhalb
> einer Komponente bestimmen ließe. Wenn ich per Debugger mit den Werten
> von Variablen herumspiele darf man nicht unbedingt davon ausgehen, das
> Ergebnis würde sich nicht ändert.

Wenn die abstrakte Maschine sich nicht so verhält, wie es die 
Sprachdefinition verlangt, wird das Programm natürlich auch nicht das 
Verhalten zeigen, das von ihr sonst garantiert wird.
Ob jetzt kaputter Computer, OOM-Kill oder das Verändern von Variablen 
per Debugger, spielt da keine Rolle.

von Heiko L. (zer0)


Lesenswert?

Rolf M. schrieb:
> Wenn man immer nur Sachen annehmen würde, die garantiert wahr sind,
> wären es ja auch keine Annahmen mehr.

Deswegen ist die Negation ja auch schwach. Dennoch ist das der Schluß, 
den die Sachlage zulässt:

Rolf M. schrieb:
> Meine Frage wäre also, welche davon es deiner
> Meinung nach ist und in welcher Passage der C-Definition das steht.

Da steht, dass der Zuweisungsoperator ein Update des Wertes der linken 
Seite durchführt. Und genau das wird ein Compiler an der Stelle auch als 
Maschinencode emitieren.

Rolf M. schrieb:
> Auch hier ändert sich das Verhalten der Maschine nicht abhängig davon,
> ob du weißt, dass sie falsch verdrahtet ist oder nicht.

Was heißt hier falsch? Wir sind doch gerade dabei, die Steuerlogik zu 
schreiben... Da sollte man davon ausgehen, dass das einen Unterschied 
macht - oder pflegst Du Anmerkungen von Kollegen a lá "Wir haben da 
übrigens die Pinbelegung geändert" zu ignorieren, weil dein Wissen darum 
"ja sowieso keinen Unterschied macht"?

Rolf M. schrieb:
> Wenn die abstrakte Maschine sich nicht so verhält, wie es die
> Sprachdefinition verlangt, wird das Programm natürlich auch nicht das
> Verhalten zeigen, das von ihr sonst garantiert wird.

Selbstverständlich nicht. Und wenn man solche Sachen weiß, muss man die 
natürlich berücksichtigen. Dass die Zeile nach dem ExitProcess nicht 
ausgeführt werden sollte, lässt sich aus dem Inhalt des Programms 
schließen. Wenn der OOM-Killer ein Programm beenden soll, läuft alles 
nach Plan, wenn das geschieht.

von Rolf M. (rmagnus)


Lesenswert?

> Rolf M. schrieb:
>> Meine Frage wäre also, welche davon es deiner
>> Meinung nach ist und in welcher Passage der C-Definition das steht.
>
> Da steht, dass der Zuweisungsoperator ein Update des Wertes der linken
> Seite durchführt. Und genau das wird ein Compiler an der Stelle auch als
> Maschinencode emitieren.

Ich hab nicht gefragt, welchen Code der Compiler generiert, sondern was 
das System macht (bzw. die abstrakte Maschine). Denn das ist es, was ISO 
C definiert (oder in dem Fall eben auch nicht).

> Rolf M. schrieb:
>> Auch hier ändert sich das Verhalten der Maschine nicht abhängig davon,
>> ob du weißt, dass sie falsch verdrahtet ist oder nicht.
>
> Was heißt hier falsch? Wir sind doch gerade dabei, die Steuerlogik zu
> schreiben...

Naja, irgendwas ist falsch. Ob nun falsch verdrahtet ist oder der Rest 
des Systems von einer falschen Verdrahtung ausgeht, ist doch egal. Es 
passt jedenfalls nicht zusammen.

> Da sollte man davon ausgehen, dass das einen Unterschied
> macht - oder pflegst Du Anmerkungen von Kollegen a lá "Wir haben da
> übrigens die Pinbelegung geändert" zu ignorieren, weil dein Wissen darum
> "ja sowieso keinen Unterschied macht"?

Ich hab nicht gesagt, dass es globalgalaktisch gesehen keinen 
Unterschied macht, sondern dass das Verhalten der Maschine sich nicht 
abhängig von deinem Wissenstand verändert. Das tut es nur, wenn du 
entweder die Verdrahtung oder die Logik der Maschine an das jeweils 
andere anpasst.

von Heiko L. (zer0)


Lesenswert?

Rolf M. schrieb:
> Ich hab nicht gefragt, welchen Code der Compiler generiert, sondern was
> das System macht (bzw. die abstrakte Maschine). Denn das ist es, was ISO
> C definiert (oder in dem Fall eben auch nicht).

In welchem "Fall"? Der C Standard definiert genau 2 Möglichkeiten, wie 
einem Zeiger die Nullpointer-Eigenschaft zukommen kann:
1. Er wurde (direkt oder indirekt) auf NULL gesetzt.
2. Ein Vergleich mit NULL zeigt, dass er NULL ist.

Genau genommen könnte der Compiler auch eine Liste von Variablen zur 
Laufzeit führen, in der er sich merkt, welche NULL sind, weil er sie 
NULL gesetzt hat und der Vergleich mit NULL nur nachsehen, ob der Wert 
in der Liste steht.

Auf deinen Beweis, dass ein Zeiger NULL sein könnte, ohne, dass eine der 
beiden Bedingungen da oben zutrifft, warte ich...

Aber, bitte! Zur Ablenkung:
1
In  the  abstract  machine,  all  expressions  are  evaluated  as  specified  by  the  semantics.  An actual  implementation  need  not  evaluate  part  of  an  expression  if  it  can  deduce  that  its
2
value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object).

von S. R. (svenska)


Lesenswert?

Heiko L. schrieb:
> 2. Ein Vergleich mit NULL zeigt, dass er NULL ist.

Ein Nullzeiger wird nicht zu einem Nullzeiger, indem man ihn auf seine 
Nullzeigereigenschaft testet.

von Heiko L. (zer0)


Lesenswert?

S. R. schrieb:
> Ein Nullzeiger wird nicht zu einem Nullzeiger, indem man ihn auf seine
> Nullzeigereigenschaft testet.

Alternativ kann man einen Zeiger auf NULL setzen. Sonst gibt es da keine 
Möglichkeit, wie ich das sehe.

von S. R. (svenska)


Lesenswert?

1
char* a = NULL;
2
char* b = a;

b ist ein Nullzeiger, obwohl ihm nicht NULL zugewiesen wurde.

von Heiko L. (zer0)


Lesenswert?

S. R. schrieb:
> char* a = NULL;
> char* b = a;
>
> b ist ein Nullzeiger, obwohl ihm nicht NULL zugewiesen wurde.

Nicht? Transitivität?

von S. R. (svenska)


Lesenswert?

Heiko L. schrieb:
> Nicht? Transitivität?

Ihm wurde der Wert eines Nullzeigers zugewiesen, nicht NULL.

Statt "char* a = NULL" hätte da auch "char* a = func()" stehen können, 
was du weiter oben ja kategorisch als Grund für Nullzeigerei 
ausgeschlossen hast, falls der Compiler nicht "func() == NULL" schon zur 
Compilezeit diagnostizieren kann.

von Heiko L. (zer0)


Lesenswert?

S. R. schrieb:
> Ihm wurde der Wert eines Nullzeigers zugewiesen, nicht NULL.

Wenn der listenführende Compiler weiß, dass a NULL ist, weil er a NULL 
gesetzt hat, kann er nach b=a wissen, dass b NULL ist. Der Schluß 
scheint mir ziemlich sicher.

Ich will hier noch einmal darauf hinweisen:
1
In  the  abstract  machine,  all  expressions  are  evaluated  as  specified  by  the  semantics.
In meinem Beispiel ist
1
*p = 1;
 eine Zuweisung. Das wäre nicht der Fall, wenn da stünde
1
char *p = NULL;
2
*p = 1;

von Rolf M. (rmagnus)


Lesenswert?

Heiko L. schrieb:
> Rolf M. schrieb:
>> Ich hab nicht gefragt, welchen Code der Compiler generiert, sondern was
>> das System macht (bzw. die abstrakte Maschine). Denn das ist es, was ISO
>> C definiert (oder in dem Fall eben auch nicht).
>
> In welchem "Fall"?

Na in dem, über den wir schon die ganze Zeit sprechen: Die 
Dereferenzierung eines Nullzeigers, unabhängig davon, ob der Compiler an 
der Stelle weiß, dass es ein Nullzeiger ist. Nirgends steht nämlich, 
dass er das wissen soll, darf oder muss.

> Der C Standard definiert genau 2 Möglichkeiten, wie
> einem Zeiger die Nullpointer-Eigenschaft zukommen kann:
> 1. Er wurde (direkt oder indirekt) auf NULL gesetzt.
> 2. Ein Vergleich mit NULL zeigt, dass er NULL ist.

Das zweite ist die Art, wie man erkennt, dass er NULL ist, nicht die 
Art, wie er NULL wird. Wie ich schon die ganze Zeit sage: Er kann auch 
NULL sein, ohne dass man das weiß.

> Genau genommen könnte der Compiler auch eine Liste von Variablen zur
> Laufzeit führen, in der er sich merkt, welche NULL sind, weil er sie
> NULL gesetzt hat und der Vergleich mit NULL nur nachsehen, ob der Wert
> in der Liste steht.

Du versteifst dich immer auf den Compiler. Nochmal: ISO C definiert das 
Verhalten einer abstrakte Maschine, nicht eines Compilers.
Aber ja, im Prinzip sollte das gehen.

> Auf deinen Beweis, dass ein Zeiger NULL sein könnte, ohne, dass eine der
> beiden Bedingungen da oben zutrifft, warte ich...

Warum sollte ich das beweisen müssen? Deine Hypothese war, wenn der 
Zeiger von einer Funktion zurückgegeben wird, könne er nicht NULL sein. 
Auch dann nicht, wenn da innerhalb der Funktion explizit NULL 
reingeschrieben wird. Das hat aber gar nichts damit zu tun, ob das nun 
in einer eigenen Funktion passiert oder nicht. Und es gilt auch, wenn du 
den Inhalt der Funktion nicht kennst und wenn auch der Compiler den 
Inhalt der Funktion nicht kennt. Ein return NULL innerhalb der Funktion 
wurde nämlich von der abstrakten Maschine ganz genauso ausgeführt, wie 
ein p = NULL außerhalb der Funktion.
Ich warte übrigens noch darauf, wie das exakt von ISO C definierte 
Verhalten der abstrakten Maschine deiner Meinung nach aussieht, wenn so 
ein Zeiger dereferenziert wird.

> Aber, bitte! Zur Ablenkung:In  the  abstract  machine,  all  expressions
> are  evaluated  as  specified  by  the  semantics.  An actual
> implementation  need  not  evaluate  part  of  an  expression  if  it
> can  deduce  that  its value is not used and that no needed side effects
> are produced (including any caused by calling a function or accessing a
> volatile  object).

Das ist die as-if-Regel, die wichtig für Optimierung ist. Was hat die 
jetzt damit zu tun?

S. R. schrieb:
> Heiko L. schrieb:
>> 2. Ein Vergleich mit NULL zeigt, dass er NULL ist.
>
> Ein Nullzeiger wird nicht zu einem Nullzeiger, indem man ihn auf seine
> Nullzeigereigenschaft testet.

Da waren wir schon mal: 
Beitrag "Re: char Array für ultoa()"

Heiko L. schrieb:
> S. R. schrieb:
>> Ihm wurde der Wert eines Nullzeigers zugewiesen, nicht NULL.
>
> Wenn der listenführende Compiler weiß, dass a NULL ist, weil er a NULL
> gesetzt hat, kann er nach b=a wissen, dass b NULL ist. Der Schluß
> scheint mir ziemlich sicher.

Ja. Er muss nicht zwingend (C schreibt es nicht vor), aber er kann.

> Ich will hier noch einmal darauf hinweisen:In  the  abstract  machine,
> all  expressions  are  evaluated  as  specified  by  the  semantics.In
> meinem Beispiel ist
> *p = 1; eine Zuweisung. Das wäre nicht der Fall, wenn da stünde
> char *p = NULL;
> *p = 1;

Warum nicht? a = b ist eine Zuweisung, egal was a und b sind. Das ist 
unabhängig davon, ob dein p direkt davor oder in einer anderen Funktion 
auf NULL gesetzt wird. Und in beiden Fällen wird ein Nullzeiger 
dereferenziert, was zu undefiniertem Verhalten der abstrakten Maschine 
führt.

von Heiko L. (zer0)


Lesenswert?

Rolf M. schrieb:
> Die
> Dereferenzierung eines Nullzeigers, unabhängig davon, ob der Compiler an
> der Stelle weiß, dass es ein Nullzeiger ist. Nirgends steht nämlich,
> dass er das wissen soll, darf oder muss.

Tja, wenn es eine "Dereferenzierung" ist und nicht irgendetwas 
undefiniertes - du kriegst die Kurve noch...

Rolf M. schrieb:
> Warum nicht? a = b ist eine Zuweisung, egal was a und b sind.

Das eine ist eine Zuweisung, das andere irgendetwas Undefiniertes. Das 
eine muss kompiliert werden, das andere nicht. Ganz einfach.

Rolf M. schrieb:
> Das zweite ist die Art, wie man erkennt, dass er NULL ist, nicht die
> Art, wie er NULL wird. Wie ich schon die ganze Zeit sage: Er kann auch
> NULL sein, ohne dass man das weiß.
...
> Du versteifst dich immer auf den Compiler.

Nein, du versteifst dich auf Annahmen, die nicht abgedeckt sind. Wo im 
Standard steht z.B. bitte, dass etwas ein Nullzeiger sein könnte, ohne 
dass eine der beiden Bedingungen de-facto gegeben wäre? Du 
argumentierst: "Ja, eigentlich müsste er da und da doch auch NULL sein 
können." Das muß aber keineswegs so sein.

: Bearbeitet durch User
von S. R. (svenska)


Lesenswert?

Heiko L. schrieb:
> Das muß aber keineswegs so sein.

Ein Zeiger ohne weitere Information ist ein Zeiger - er ist kein 
Nullzeiger und kein Nicht-Nullzeiger. Dem Compiler ist es freigestellt, 
in die eine oder die andere Richtung zu beweisen. Er muss es nicht tun.

Damit ist jeder Zeiger möglicherweise ein Nullzeiger. Sollte er zum 
Zeitpunkt der Dereferenzierung zufällig einer sein, gibt's UB. Dafür 
muss nicht bewiesen worden sein, dass der Zeiger NULL sein musste - es 
reicht völlig aus, dass er es war.

Und eine Zuweisung ist auch dann eine Zuweisung, wenn sie zu UB führt. 
UB macht ja die Zuweisungseigenschaft nicht kaputt, sondern nur das 
bestimmte Verhalten der abstrakten Maschine.

von Heiko L. (zer0)


Lesenswert?

S. R. schrieb:
> Heiko L. schrieb:
>> Das muß aber keineswegs so sein.
>
> Ein Zeiger ohne weitere Information ist ein Zeiger - er ist kein
> Nullzeiger und kein Nicht-Nullzeiger. Dem Compiler ist es freigestellt,
> in die eine oder die andere Richtung zu beweisen. Er muss es nicht tun.
>
> Damit ist jeder Zeiger möglicherweise ein Nullzeiger. Sollte er zum
> Zeitpunkt der Dereferenzierung zufällig einer sein, gibt's UB. Dafür
> muss nicht bewiesen worden sein, dass der Zeiger NULL sein musste - es
> reicht völlig aus, dass er es war.
>
> Und eine Zuweisung ist auch dann eine Zuweisung, wenn sie zu UB führt.
> UB macht ja die Zuweisungseigenschaft nicht kaputt, sondern nur das
> bestimmte Verhalten der abstrakten Maschine.

In dem einen Fall darf der Compiler die Kompilierung ganz verweigern, 
oder auch nur einige Anweisungen ignorieren, oder, oder, oder.
In dem anderen Fall muss ein Standard-konformer Compiler ein Kompilat 
erzeugen. Und zwar eines, welches der Semantik des Programms entspricht.
Dort steht dann mindestens eine Anweisung a lá "mov [eax], 1". Ich sehe 
da nichts undefiniertes. Ich glaube, was dann passiert, ist im Gegenteil 
auch wieder ziemlich genau spezifiziert.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Heiko L. schrieb:
> Rolf M. schrieb:
>> Die
>> Dereferenzierung eines Nullzeigers, unabhängig davon, ob der Compiler an
>> der Stelle weiß, dass es ein Nullzeiger ist. Nirgends steht nämlich,
>> dass er das wissen soll, darf oder muss.
>
> Tja, wenn es eine "Dereferenzierung" ist und nicht irgendetwas
> undefiniertes - du kriegst die Kurve noch...

Natürlich ist es eine Dereferenzierung. Undefiniert ist das Verhalten, 
wenn man das versucht. Das ist eigentlich ein simpler kausaler 
Zusammenhang: Wäre es keine Dereferenzierung, dann gäbe es kein 
undefiniertes Verhalten, denn das entsteht ja erst durch die 
Dereferenzierung.

> Rolf M. schrieb:
>> Warum nicht? a = b ist eine Zuweisung, egal was a und b sind.
>
> Das eine ist eine Zuweisung, das andere irgendetwas Undefiniertes.

Unsinn. ISO C definiert ganz genau, was das ist.

> Das eine muss kompiliert werden, das andere nicht. Ganz einfach.

Wenn irgendwo im Programm undefiniertes Verhalten vorkommt, muss gar 
nichts mehr.

> Rolf M. schrieb:
>> Das zweite ist die Art, wie man erkennt, dass er NULL ist, nicht die
>> Art, wie er NULL wird. Wie ich schon die ganze Zeit sage: Er kann auch
>> NULL sein, ohne dass man das weiß.
> ...
>> Du versteifst dich immer auf den Compiler.
>
> Nein, du versteifst dich auf Annahmen, die nicht abgedeckt sind. Wo im
> Standard steht z.B. bitte, dass etwas ein Nullzeiger sein könnte, ohne
> dass eine der beiden Bedingungen de-facto gegeben wäre?

Wenn ich NULL reinschreibe, ist das gegeben. Egal, ob in einer Funktion 
oder nicht. Das hab ich doch ziemlich klar geschrieben. Das ist deine 
"Bedingung 1". Ich weiß auch nicht, wie du auf die Idee kommst, die 
Zusagen und Vorgaben von C würden nicht mehr gelten, sobald man eine 
Funktion aufruft.

Heiko L. schrieb:
> 1. Er wurde (direkt oder indirekt) auf NULL gesetzt.

Und nein, das muss nicht in der Codezeile davor geschehen, sondern kann 
an beliebiger anderer Stelle im Programm passiert sein. Sogar in einer 
anderen Funktion.

> Du argumentierst: "Ja, eigentlich müsste er da und da doch auch NULL sein
> können." Das muß aber keineswegs so sein.

Nein. Ich argumentiere: "Wenn die Funktion (oder wer auch immer) da NULL 
reingeschrieben hat, steht da NULL drin. Das muss man weder wissen, noch 
prüfen, damit das so ist. Es steht einfach so drin."
Du argumentierst dagegen: "Wenn es aus einer Funktion zurückgegeben 
wird, kann da nicht NULL drin stehen, auch wenn sie es da 
reingeschrieben hat. Erst wenn ich es auf NULL prüfe, ändert sich der 
Wert auf magische Weise, und danach steht dann NULL drin."

Heiko L. schrieb:
> In dem einen Fall darf der Compiler die Kompilierung ganz verweigern,
> oder auch nur einige Anweisungen ignorieren, oder, oder, oder.

Nicht nur der Compiler! Auch die Laufzeitumgebung, die Hardware, das 
Betriebssystem oder das Kraftwerk, von dem der Strom für deinen Rechner 
kommt. Der Monitor darf explodieren, das Programm darf vom Prozessor ab 
der Stelle rückwärts ausgeführt werden, oder dir können Dämonen aus der 
Nase fliegen ( https://en.wiktionary.org/wiki/nasal_demon ). All das 
wäre nach ISO C korrektes Verhalten dieses Konstrukts.

> In dem anderen Fall muss ein Standard-konformer Compiler ein Kompilat
> erzeugen.

Nö. Auch hier gelten genau die selben Dinge wie oben. Gar keiner muss 
irgendwas.

> Und zwar eines, welches der Semantik des Programms entspricht.
> Dort steht dann mindestens eine Anweisung a lá "mov [eax], 1". Ich sehe
> da nichts undefiniertes. Ich glaube, was dann passiert, ist im Gegenteil
> auch wieder ziemlich genau spezifiziert.

Ich hab ja schon mehrfach gefragt, wo in ISO C das genau spezifiziert 
sein soll.

: Bearbeitet durch User
von Heiko L. (zer0)


Lesenswert?

Rolf M. schrieb:
> Natürlich ist es eine Dereferenzierung.

Nee, das sieht nur so aus, als ob das eine werden sollte.
Wäre es eine Dereferenzierung, wäre das Verhalten definiert: der Pointer 
würde dereferenziert werden. Kein definiertes Verhalten -> keine 
festgelegte "Semantik" -> keine Dereferenzierung.
Man kann etwas nicht ein "Sitzen" nennen, wenn es nicht sitzt.
1
Das Sein soll erfaßt und zum Thema gemacht werden. Sein ist jeweils Sein von Seiendem und wird demnach zunächst nur im Ausgang von einem Seienden zugänglich. Dabei muß sich der erfassende phänomenologische Blick zwar auf Seiendes mitrichten, aber so, daß dabei das Sein dieses Seienden zur Abhebung und zur möglichen Thematisierung kommt. Das Erfassen des Seins geht zwar zunächst und notwendig je auf Seiendes zu, wird aber dann von dem Seienden in bestimmter Weise weg- und zurückgeführt auf dessen Sein.
2
(Martin Heidegger, Grundprobleme)

Auf deutsch: Eine Dereferenzierung dereferenziert. Damit ist sie nicht 
undefiniert.
2 + 1 = 4 ist keine Formel der Arithmetik - sowas existiert noch nicht 
einmal.

Rolf M. schrieb:
>> In dem einen Fall darf der Compiler die Kompilierung ganz verweigern,
>> oder auch nur einige Anweisungen ignorieren, oder, oder, oder.
> Nicht nur der Compiler! Auch die Laufzeitumgebung, die Hardware, das
> Betriebssystem oder das Kraftwerk, von dem der Strom für deinen Rechner
> kommt.

DAS ist sowieso der Fall - siehe ExitProcess(). Das, was dort nach wie 
vor garantiert ist, ist, dass die abstrakte Maschine den Befehl bekommt 
"Rufe die Funktion auf." Und die ist z.B. in Assembler geschrieben - was 
sagt der  C-Standard, was dann passiert? Ist die Maschine kaputt?

Rolf M. schrieb:
> Ich hab ja schon mehrfach gefragt, wo in ISO C das genau spezifiziert
> sein soll.

Und darauf habe ich mehrfach geantwortet. Lese doch einfach noch einmal 
nach!

von S. R. (svenska)


Lesenswert?

Heiko L. schrieb:
> Kein definiertes Verhalten -> keine
> festgelegte "Semantik" -> keine Dereferenzierung.

Du denkst rückwärts. Ich bin dann mal raus, das wird nix.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.