Forum: PC-Programmierung Pointer auf ungültigen Wert prüfen


von Dennis S. (eltio)


Lesenswert?

Hallo zusammen,

was ist die beste Praxis wenn ich prüfen möchte, ob ein Speicherbereich 
noch mit gültigen Werten initialisiert oder bereits wieder freigegeben 
ist? Folgendes Beispiel:
1
#include <stdio.h>
2
#include <stdlib.h>
3
4
struct point_t {
5
  int x;
6
  int y;
7
};
8
9
struct point_t* createPoint(int newX, int newY) {
10
  struct point_t *newPoint = NULL;
11
12
  newPoint = malloc(sizeof(struct point_t));
13
  newPoint->x = newX;
14
  newPoint->y = newY;
15
16
  return newPoint;
17
}
18
19
int main(void) {
20
21
  struct point_t *new = NULL;
22
23
  new = createPoint(4, 2);
24
25
  printf("%d / %d\n", new->x, new->y);
26
  free(new);
27
  printf("%d / %d\n", new->x, new->y);
28
29
  return EXIT_SUCCESS;
30
}

Das zweite printf gibt ja auch noch einen Wert aus, auch wenn dieser 
hier Blödsinn ergibt. Theoretisch könnte er aber gültig sein. Mein 
Ansatz wäre jetzt, eine Funktion "deletePoint" zu schreiben, dort ein 
free zu machen und den Pointer auf NULL zu setzen. Das kapselt zwar 
schön, ändert aber nichts am Ergebnis..

Gruß
Dennis

von g457 (Gast)


Lesenswert?

> [..] und den Pointer auf NULL zu setzen. Das kapselt zwar schön,
> ändert aber nichts am Ergebnis..

Doch, das gibt dann nämlich eine Nullpointerdreference die Dir um die 
Ohren fliegt (und zwar zurecht!).

Gültigkeit von Pointer prüft man ∗grundsätzlich∗ per Prüfung auf NULL. 
Wer einen Pointer nutzt, ohne ihn geprüft zu haben, wird gesteinigt - 
und der erkannte Fehler behoben.

von Fake Dipl Ing FH mit Eiern (Gast)


Lesenswert?

Bei dem free, den Pointer auf NULL setzen.

Alle Pointer die ungleich NULL sind, sind als gültig anzunehmen.

von Εrnst B. (ernst)


Lesenswert?

Dennis S. schrieb:
> was ist die beste Praxis

Testen, testen, testen. Programm-Quelltext nicht mit unötigen Checks 
verunstalten.

Zum Testen Tools verwenden, die solche und ähnliche Fehler fangen. 
Valgrind, -fstack-protector, MALLOC_CHECK_ usw.

von Peter II (Gast)


Lesenswert?

g457 schrieb:
> Doch, das gibt dann nämlich eine Nullpointerdreference die Dir um die
> Ohren fliegt (und zwar zurecht!).

auf eine µC kann es auch null pointer geben die gültig sind. Adressen 
fangen nun mal bei 0 an.

von nicht"Gast" (Gast)


Lesenswert?

Hallo,

mit C hast du keine Möglichkeiten verlässlich zu prüfen ob irgend ein 
Zeiger gültig ist oder nicht.

Kapseln ist die einzig gute Art damit umzugehen.
Das sollte man eh immer machen und nicht wild mit Zeigern hantieren.


Grüße,

von g457 (Gast)


Lesenswert?

> auf eine µC kann es auch null pointer geben die gültig sind. Adressen
> fangen nun mal bei 0 an.

Das ist erstens in dieser Allgemeinheit falsch und zweitens hier 
vollkommen fehl am Platz - hier gehts um PC-Programmierung.

von Dennis S. (eltio)


Lesenswert?

Hmm... ich stehe gerade auf dem Schlauch. Habe folgendes zum Testen 
ergänzt:
1
void deletePoint(struct point_t *del){
2
  free(del);
3
  del = NULL;
4
}

In main:
1
...
2
  deletePoint(new);
3
  printf("%p", new);

Ich bekomme aber eine Ausgabe die ich nicht erwartet habe:
1
4 / 2
2
00372E88

Ich hätte jetzt eigentlich mit einem 00000000 gerechnet!

Gruß
Dennis

P.S.: Das Prüfen würde ich im "richtigen" Code natürlich machen, aber 
wenn es grundlegend schon nicht klappt... Valgrind arbeite ich mich auch 
gerade ein.

von Peter II (Gast)


Lesenswert?

g457 schrieb:
> Das ist erstens in dieser Allgemeinheit falsch und zweitens hier
> vollkommen fehl am Platz - hier gehts um PC-Programmierung.

auch dort kann es eine Adresse 0 geben - im Linux Kernel hat das sogar 
schon zu Problemen geführt.

von g457 (Gast)


Lesenswert?

> Ich bekomme aber eine Ausgabe die ich nicht erwartet habe:

..kann ja auch nicht funktionieren.. hier der korrekte(re) Code:
1
void deletePoint(struct point_t **del){
2
  free(*del);
3
  *del = NULL;
4
}
5
[..]
6
deletePoint(&new);
7
printf("%p", new);

von Dennis S. (eltio)


Lesenswert?

Peter II schrieb:
> auch dort kann es eine Adresse 0 geben - im Linux Kernel hat das sogar
> schon zu Problemen geführt.
Mal ernsthaft.. sieht die Frage aus, als ob ich den Kernel umschreibe...

von Peter II (Gast)


Lesenswert?

Dennis S. schrieb:
> Ich hätte jetzt eigentlich mit einem 00000000 gerechnet!

dann musst du einen pointer auf einen pointer übergeben.

1
void deletePoint(struct point_t **del){
2
  free(*del);
3
  *del = NULL;
4
}
5
6
   deletePoint(&new);

von g457 (Gast)


Lesenswert?

> auch dort kann es eine Adresse 0 geben - im Linux Kernel hat das sogar
> schon zu Problemen geführt.

Ist das Speicher der über die C-Runtime mit der malloc-Familie angelegt 
worden ist so wie hier? Dann ist die implementierung fehlerhaft. Und es 
ist hier immernoch off-topic weils hier offensichtlich nicht um 
Kernelcode geht.

von Εrnst B. (ernst)


Lesenswert?

Dennis S. schrieb:
> was ist die beste Praxis wenn ich prüfen möchte, ob ein Speicherbereich
> noch mit gültigen Werten initialisiert oder bereits wieder freigegeben
> ist?

Generell, unabhängig von dem synthetischen "createPoint/freePoint" 
Beispiel:


Wenn du irgendwo auf einen bereits freigegebenen Speicherbereich 
zugreifst, ist das ein Fehler.
Fehler will man idealerweise finden und beheben, nicht verschleiern.

Ein Vorgehen mit "bei Free pointer auf NULL setzen, bei jedem 
Pointer-Zugriff prüfen" macht genau das: den Fehler verschleiern.

Bei komplexeren Programmen: Woher weißt du, welche Kopien von deinem 
Pointer noch existieren?

Wie soll dein "freePoint" wissen, welche anderen Pointer noch auf 0 zu 
setzen sind? (Jaja, in C++ mit boost usw. wär das alles ganz einfach...)

Ein Check mit valgrind & co hingegen kann dir ganz klar sagen:
Speicher wurde in xxx.c, Zeile 12345 alloziert, freigegeben in yyy.c, 
Zeile 5432, und anschliessend wurde böserweise in zzz.c, Zeile 333, auf 
den freigegebenen Speicher zugegriffen.

: Bearbeitet durch User
von g457 (Gast)


Lesenswert?

> Ein Vorgehen mit "bei Free pointer auf NULL setzen, bei jedem
> Pointer-Zugriff prüfen" macht genau das: den Fehler verschleiern.

Nö, im Gegenteil: Es haut Dir eine Nullpointerdereference (vergessene 
Prüfung) oder eine lesbare Warnung auf $stderr nebst ∗erkanntem∗ Fehler 
(Error condition/Failure/.. return code, lesbare Exception in C++) um 
die Ohren [0]. Und es ∗verhindert∗ zuverlässig, dass Du versehentlich 
nicht (mehr) gültigen Speicher benutzt.

> Bei komplexeren Programmen: Woher weißt du, welche Kopien von deinem
> Pointer noch existieren?

Das ist ein anderes Problem.

> Ein Check mit valgrind & co hingegen kann dir ganz klar sagen:
> Speicher wurde in xxx.c, Zeile 12345 alloziert, freigegeben in yyy.c,
> Zeile 5432, und anschliessend wurde böserweise in zzz.c, Zeile 333, auf
> den freigegebenen Speicher zugegriffen.

Nope, das funktioniert nur sehr eingeschränkt. Klar nutzt man diese 
Tools ∗zusätzlich∗, aber keinesfalls ausschließlich. Die üblichen 
Verdächtigen laufen nämlich nur zur Compilezeit (oder davor) und können 
dabei nicht aller erkennen, oder verursachen einen massiven 
Performanceverlust zur Laufzeit und können dann nur tatsächlich 
abgelaufene Codepfade mit tatsächlich vorgekommenen Daten prüfen.
In letzterem Fall lässt man die Dinger dann aufgrund der unerträglich 
schlechten Performance nur in 'Debugversionen' mit einem sehr begrenzten 
Testdatensatz laufen und merkt dann in der 'Releaseversion' nicht, dass 
man gerade ungültigen Speicher bearbeitet - klassischer Schuss in eigene 
Knie.

Fehler wie ungültige Zeiger muss man grundsätzlich zur Laufzeit erkennen 
können, denn nur da hat man die tatsächlichen Daten zur Verfügung und 
nicht irgendeinen schlecht gewählten Unittestbeispieldatensatz der zu 
fälligerweise funktioniert.

Nix für ungut.

[0] den erkannten Fehler im Code stillschweigend zu ignorieren anstatt 
ihn zu propagieren ist selbstredend keine Option und bleibt hier deshalb 
unerwähnt.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

g457 schrieb:
> Es haut Dir eine Nullpointerdereference (vergessene
> Prüfung) oder eine lesbare Warnung auf $stderr nebst ∗erkanntem∗ Fehler
> (Error condition/Failure/.. return code, lesbare Exception in C++) um
> die Ohren

Vorausgesetzt, daß das die Zielhardware und Laufzeitumgebung auch 
unterstützt. Was auf einem µP mit MMU leicht möglich ist, wird auf einem 
µC schon deutlich schwieriger ... bis unmöglich, weil a) 0 eine völlig 
legale Speicheradresse sein kann und b) es keine Hardwareunterstützung 
zur Erkennung von Zugriffen auf "ungültige" Speicheradressen geben muss.

Aber gut, wir sind hier bei "PC-Programmierung", da gibt es eine MMU, 
und entsprechende Mechanismen, solange nicht gerade im x86-Real-Mode 
programmiert wird (was hoffentlich niemand mehr macht).

von Karl H. (kbuchegg)


Lesenswert?

g457 schrieb:
>> Ein Vorgehen mit "bei Free pointer auf NULL setzen, bei jedem
>> Pointer-Zugriff prüfen" macht genau das: den Fehler verschleiern.
>
> Nö, im Gegenteil: Es haut Dir eine Nullpointerdereference (vergessene
> Prüfung) oder eine lesbare Warnung auf $stderr nebst ∗erkanntem∗ Fehler
> (Error condition/Failure/.. return code, lesbare Exception in C++) um
> die Ohren [0].


Ja. In diesem Wischi Waschi Beispiel.
Nur sind die Dinge in der Praxis komplexer.

Hast du 2 Pointer auf denselben Speicher, was bei komplizierteren 
Datenstrukturen schon mal vorkommt, dann haut dir eben keine Runtime 
eine Null-Pointer Dereference für den 2.ten Pointer um die Ohren.

> Das ist ein anderes Problem.

Das ist exakt das Problem.
Im Trivialbeispiel da oben ist die Sache einfach und überschaubar. Aber 
die vorgeschlagege Lösungsmethode verallgemeinert nun mal nicht für 
komplexere Fälle. Bei Trivialbeispielen funktioniert sie gut. Allerdings 
braucht man sie dort eher selten (mit ein wenig Erfahrung), eben weil 
man noch alles gut überblicken kann. Und in den Fällen, in denen man 
Hilfe benötigen würde, versagt sie aus den genannten Gründen.

> Fehler wie ungültige Zeiger muss man grundsätzlich zur Laufzeit erkennen können,

Kannst du aber nicht, weil es in C (Standard-C) keine Möglichkeit gibt 
prüfen zu lassen, ob ein Pointer in einen gültig allokierten Speicher 
zeigt. In einer Funktion, die einen Pointer als Argument kriegt, bist du 
auf Deutsch gesagt, mit der Prüfung aufgeschmissen. Auf NULL kann man 
noch prüfen, aber ein Pointer der nicht NULL ist, den musst du als 
gültig annehmen - ob du willst oder nicht.

: Bearbeitet durch User
von Nase (Gast)


Lesenswert?

Peter II schrieb:
> auf eine µC kann es auch null pointer geben die gültig sind. Adressen
> fangen nun mal bei 0 an.
Und du hast scheinbar immer noch nicht begriffen, dass Zeiter keine 
Adressen sind.

von Karl H. (kbuchegg)


Lesenswert?

Nase schrieb:
> Peter II schrieb:
>> auf eine µC kann es auch null pointer geben die gültig sind. Adressen
>> fangen nun mal bei 0 an.
> Und du hast scheinbar immer noch nicht begriffen, dass Zeiter keine
> Adressen sind.

Abgesehen davon ist das C ziemlich egal. Der NULL Pointer hat in C den 
Wert 0. Egal ob da damit dann die tatsächliche Speicheradresse 0x00 oder 
irgendeine andere nicht zugreifbar wird. Für einen C Programmierer ist 
der Wert dieses 'Ich zeige nirgendwohin' Pointers immer 0. Wenn das auf 
einer bestimmten Zielhardware anders sein soll (weil zb an der Adresse 
0xFFFF kein Speicher liegt und sich daher 0xFFFF als Null-Pointer 
anbieten würde), dann ist es die Sache des Compilers, eine derartige 
Umsetzung zu machen.

Und ja. Das bedeutet, dass man eine Adresse aus dem potentiellen 
Adressraum der Maschine opfert, um den Wert 'ungültig' darstellen zu 
können, was normalerweise keine große Sache ist.

: Bearbeitet durch User
von Nase (Gast)


Lesenswert?

Karl Heinz schrieb:
> Der NULL Pointer hat in C den
> Wert 0.
Eben.

von Ach was (Gast)


Lesenswert?

Nase (Gast) schrieb:

Peter II schrieb:
>> auf eine µC kann es auch null pointer geben die gültig sind. Adressen
>> fangen nun mal bei 0 an.
> Und du hast scheinbar immer noch nicht begriffen, dass Zeiter keine
> Adressen sind.

Natürlich sind Zeiger "Adressen". Aus Assemblersicht ist gibt es nur 
Adressen und Inhalte und sonst nichts. Und je nach Gebrauch steht der 
Pointer Synonym für eine Adresse oder deren Inhalt. Deswegen gibt es 
auch einen Adress & und einen Inhaltsoperator *.

Das Problem von euch Käseköpfen ist, ihr macht aus allem was C und 
besonders C++ angeht eine Mystifizierung und an Pointern ist nichts 
Mystisches.

von Stefan (Gast)


Lesenswert?

Nase schrieb:
> Karl Heinz schrieb:
>> Der NULL Pointer hat in C den
>> Wert 0.
> Eben.

Eben nicht.

Der NULL Pointer hat den Wert NULL.
Auf welche 'Adresse' oder was auch immer das zeigt ist egal.

Stefan

von Nase (Gast)


Lesenswert?

Ach was schrieb:
> Natürlich sind Zeiger "Adressen"
Ja und nein. Ok, ich formuliere es besser: Zeiger sind keine 
Maschinenadressen.

Stefan schrieb:
> Nase schrieb:
>> Karl Heinz schrieb:
>>> Der NULL Pointer hat in C den
>>> Wert 0.
>> Eben.
>
> Eben nicht.
Eben doch.

Es ist richtig: Auf welche (Maschinen-)Adresse der Null-Zeiger zeigt, 
ist egal. Das interessiert in C aber nicht, da man nicht mit 
(Maschinen-)Adressen arbeitet, sondern mit Zeigern.
Und für Standard-C ist eindeutig definiert, dass die Ganzzahl-Konstante 
'0' zum Null-Zeiger wird[1]. Die einzige Freiheit der C-Umgebung ist es, 
das Makro 'NULL' als '0' oder '0L' oder auch mit Cast als '(void *)0' zu 
definieren[2].

[1] ISO/IEC 9899:1999 7.17§3, 6.3.2.3§3
[2] C99-Rationale 7.17§30

von Εrnst B. (ernst)


Lesenswert?

Nase schrieb:
> Karl Heinz schrieb:
>> Der NULL Pointer hat in C den
>> Wert 0.
> Eben.

http://c-faq.com/null/machexamp.html

von Nase (Gast)


Lesenswert?

Εrnst B✶ schrieb:
> Nase schrieb:
>> Karl Heinz schrieb:
>>> Der NULL Pointer hat in C den
>>> Wert 0.
>> Eben.
>
> http://c-faq.com/null/machexamp.html
Auch das ist in Ordnung.

Es ist dann aber Aufgabe des Compilers, dir all diese verschiedenen 
Darstellungen des Null-Zeigers abzunehmen. Das heißt auch, dass er die 
richtige Darstellung (z.B. 07777:0) einsetzt, wenn du in C den 
Null-Zeiger mit '0' erzeugst. Oder dass er die richtige Instruktion 
benutzt, wenn du gegen Null-Zeiger testest.

Damit hat der Null-Zeiger in C halt effektiv wieder den Wert '0'. 
Möglicherweise eine andere Darstellung im Maschinenadressraum. Aber 
Zeiger sind ja keine (Maschinen-)Adressen...

von Ach was (Gast)


Lesenswert?

Εrnst B✶ (ernst) schrieb:

Nase schrieb:
>> Karl Heinz schrieb:
>>> Der NULL Pointer hat in C den
>>> Wert 0.
>> Eben.

> http://c-faq.com/null/machexamp.html

Wo immer es eine Regel gibt, gibt es auch Ausnahmen. Die Ausnahmen 
BESTÄTIGEN aber jeweils immer die Regel und sind kein Gegenbeweis.

von Nase (Gast)


Lesenswert?

Es ist ja keine Ausnahme. Es ist eine der Möglichkeiten, die der 
C-Standard anbietet. Vorallem aber ist es eine Sache, die mich als 
C-Programmierer normalerweise nicht interessiert.

von Ach was (Gast)


Lesenswert?

Nase (Gast) schrieb:

> Es ist ja keine Ausnahme. Es ist eine der Möglichkeiten, die der
> C-Standard anbietet.

Doch, es ist eine Ausnahme. Auch Ausnahmen sind irgendwo in einem 
Regelwerk beschrieben. Unsere Gesetzestexte sind voll von Ausnahmen. 
"Gesetz A gilt immer, jedoch dann nicht, wenn B, C, D eintreten und E 
sowie F gemeinsam vorhanden sind" usw.

In C ist das nicht anders.

Wen interessiert heute noch was ein "The old HP 3000 series" oder eine 
The "CDC Cyber 180" mal irgendwann an "null pointers of 0xB00000000000" 
implementiert hat, wenn beispielsweise 99% aller derzeitigen C/C++ 
Compilate dem Symbol NULL eine '0' zuschreiben?

> Vorallem aber ist es eine Sache, die mich als
> C-Programmierer normalerweise nicht interessiert.

Diese ganzen kleinkarierten Diskussionen um die Auslegung von 
Begrifflichkeiten in C/C++, die Poiner mystifizieren und mit Buzzwörtern 
angeben, finde ich genauso anstrengend wie lästig. Die machen auch die 
in C/C++ geschirbene Software nicht besser. Gerade an so Programmen wie 
FreeCAD, das mir bei der kleinsten Fehlbedienung eine Exeption schmeißt 
merke ich, wie weit dieser ganze geredete Klein-Klein-Formalismus dann 
wirklich beim praktischen Programmieren auf der Strecke bleibt bzw. 
nichts bewirkt. Da wird mir eine Datei beim Konvertieren erzeugt, die 
gar keinen Inhalt hat. Da wird mir eine Fehlermeldung um die Ohren 
gehauen, aber kein Hinweis, was zu tun ist, um diese krude Software 
vernünftig zu bedienen. Grrr.

von Nase (Gast)


Lesenswert?

Natürlich, wenn man die Grundlage nie begriffen hat, dann wird ganz 
schnell alles zur Ausnahme...

von Ach was (Gast)


Lesenswert?

Nase (Gast) schrieb:

> Natürlich, wenn man die Grundlage nie begriffen hat, dann wird ganz
> schnell alles zur Ausnahme...

Wer soll was nicht begriffen haben? Das worum es hier geht ist banal.

von Nase (Gast)


Lesenswert?

Scheinbar nicht banal genug, sonst würden nicht ständig wieder Leute 
Zeiger und Adressen pauschal zusammenschmeißen und sich anschließend 
wundern.

Wenn man sich den Unterschied - und ja, das mag in den meisten Fällen 
nur eine Nuance sein - mal klar gemacht hat, erübrigt sich die gesamte 
Diskussion.

Aber es scheint ja einfacher zu sein, die Ausnahme zur Regel zu erklären 
und dann für jede Abweichung wieder Ausnahmen zu basteln. Ist mir nun zu 
blöde.

von Karl H. (kbuchegg)


Lesenswert?

Ach was schrieb:

> Wen interessiert heute noch was ein "The old HP 3000 series" oder eine
> The "CDC Cyber 180" mal irgendwann an "null pointers of 0xB00000000000"
> implementiert hat, wenn beispielsweise 99% aller derzeitigen C/C++
> Compilate dem Symbol NULL eine '0' zuschreiben?
>

Darum gehts doch gar nicht.
Auch auf derartigen Systemen hätte man in C
1
  void * pPtr = (void*)0;
geschrieben und es wäre der Job des Compilers, diese Zuweisung durch die 
Zuweisung vo n0xB0000000000 zu ersetzen.

Als C Programmierer benutzt du IMMER den Zahlenwert 0 um den Null 
Pointer auszudrücken. Da ist also nichts mit Ausnahme. Ganz im 
Gegenteil. Das ist eines der wenigen Dinge, die tatsächlich auf allen C 
Systemen gleich sind. Der Null-Pointer hat im C Programm den Wert 0. 
Unabhängig davon, wie er auf dem fraglichen System tatsächlich 
implementiert wird. Und ja, wenn man mit dem Debugger sich den Hex-Wert 
einer derartigen Variable mit einem Null-Pointer ansehen würde, dann 
würde man da ein anderes Bitmuster drinn finden. Trotzdem vergleicht ein 
derartiger Null-Pointer bei
1
    if( pPtr == 0 )
mit TRUE.


Zu deinen Ausführungen über FreeCad: Zweifellos gibt es auch schlechte 
Software. Ich würde sogar soweit gehen und sagen, dass ein großer 
Prozentsatz in diese Kategorie fällt. Aber das hat mit der 
Programmiersprache recht wenig zu tun. Schon eher damit, dass die 
Programmierer die Regeln ihrer Sprache nicht beherrschen.

von Ach was (Gast)


Lesenswert?

Karl Heinz (kbuchegg) (Moderator) schrieb:

Ach was schrieb:

>> Wen interessiert heute noch was ein "The old HP 3000 series" oder eine
>> The "CDC Cyber 180" mal irgendwann an "null pointers of 0xB00000000000"
>> implementiert hat, wenn beispielsweise 99% aller derzeitigen C/C++
>> Compilate dem Symbol NULL eine '0' zuschreiben?
>>

> Darum gehts doch gar nicht.
> Auch auf derartigen Systemen hätte man in C

>  void * pPtr = (void*)0;

> geschrieben und es wäre der Job des Compilers, diese Zuweisung durch die
> Zuweisung vo n0xB0000000000 zu ersetzen.

Das ist genau die Bestätigung meiner Aussage. Nur noch mal zur 
Erinnerung, er ging darum das in C-Programmen der NULL Pointer den Wert 
0 hat. Darauf hatte einer als Einwand ein Posting gebracht und ich 
bekräftigte nur, DEINE Aussgage Karl Heinz, nämlich das hier (jedenfalls 
war es so gemeint)

>  void * pPtr = (void*)0;

Das der Compiler oder das BS hier den Wert dann möglicherweise (intern) 
ersetzt durch ein n0xB0000000000 oder sonstwas wollte und würde ich 
nicht in Abrede stellen.

Aber du hast recht, ich bin wohl davon ausgegangen, dass sich das 
"Umbiegen" des Wertes auch schon in einem anderen Makro in einem der 
C-Code Header, z.B. durch ein

/* Define NULL pointer value */
#ifndef NULL
#ifdef __cplusplus
#define NULL    0
#else
#define NULL    ((void *)0)
#endif
#endif

nur mit anderem Wert als 0 zeigt. (vielleict ist es auch so)

Mein Fehler!

> ... Und ja, wenn man mit dem Debugger sich den Hex-Wert
> einer derartigen Variable mit einem Null-Pointer ansehen würde, dann
> würde man da ein anderes Bitmuster drinn finden. Trotzdem vergleicht ein
> derartiger Null-Pointer bei

Ich hab noch mal nachgeschaut. In der Win32-Speicherarchitektur vom 
alten Win95 sind die untersten 4 kByte 0x00000FFF besonders geschützt, 
gerade wegen u.a. "kein Zugriff - Null-Zeiger" (sowie 16-bit Windows und 
MS-DOS). Bei der Implementation des Win32 in NT 3.5 war es schon der 
Adressraum 0x0 bis 0x0000FFFF, also 64 kByte für das Erkennen 
nichtinitialisierter Zeiger, also Null-Zeiger.

(Buch von Jeffrey Richter, Kapitel Virtuelle Adressräume)

> Zu deinen Ausführungen über FreeCad: Zweifellos gibt es auch schlechte
> Software. Ich würde sogar soweit gehen und sagen, dass ein großer
> Prozentsatz in diese Kategorie fällt. Aber das hat mit der
> Programmiersprache recht wenig zu tun. Schon eher damit, dass die
> Programmierer die Regeln ihrer Sprache nicht beherrschen.

FreeCAD ist nicht grundsätzlich schlecht. Es ist in gewisser Weise sogar 
konkurrenzlos in der Vielfalt seiner Funktionen und 
Konvertierungsmöglichkeiten. Man kommt kaum beim Thema 3D CAD ohne Geld 
auszugeben an dem Teil vorbei (ebenso wie Blender). Leider hat das 
Programm sowohl in der Bedienweise als auch im Abfangen von Fehlern 
schlimme Macken. Ich hab das an dieser Stelle hier nur mal angeführt, 
weil es mir auf den "Zeiger" geht, wie hier oft bis ins Klein-Klein 
hinein auf Nebenkriegsschauplätzen sich ausargumentiert wird (über 
Formalien, Definitionen usw.), während so bekannte Softwaren wie FreeCAD 
(was übrigens recht beliebt ist) mit erheblichen Softwarefehlern oder 
sagen wir Überraschungen dem Anwender gegenüber aufwartet. Wie oft mir 
das Teil schon abgeschmiert ist kann ich gar nicht sagen.

Naja, ist halt so.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Ach was schrieb:
> Aber du hast recht, ich bin wohl davon ausgegangen, dass sich das
> "Umbiegen" des Wertes auch schon in einem anderen Makro in einem der
> C-Code Header, z.B. durch ein
>
> /* Define NULL pointer value */
> #ifndef NULL
> #ifdef __cplusplus
> #define NULL    0
> #else
> #define NULL    ((void *)0)
> #endif
> #endif
>
> nur mit anderem Wert als 0 zeigt. (vielleict ist es auch so)

Dann würde nur noch ein direkter Vergleich mit "NULL" funktionieren, 
nicht aber so etwas:
1
char* p;
2
3
p = malloc(123);
4
5
if (!p)
6
{
7
  ...
8
}

von g457 (Gast)


Lesenswert?

>> Das ist ein anderes Problem.
>
> Das ist exakt das Problem.

Nein eben nicht, siehe unten :-)

> Auf NULL kann man noch prüfen, aber ein Pointer der nicht NULL ist, den
> musst du als gültig annehmen - ob du willst oder nicht.

Ebenst, genau das ist das Konzept. Jeder Zeiger, der nicht gültig ist, 
hat NULL zu sein. Wenn man eine Ressource (hier: Speicher) freigibt, hat 
man alle Nutzer (hier Halter von Zeigern) mittelbar oder unmittelbar 
darüber zu informieren. Sowas nennet man dann Architektur.

Wer seine Architektur dermaßen versemmelt, dass er keinen Überblick mehr 
hat, wer wann wo welche Ressourcen wie nutzt, der hat dann genau obiges 
(nämlich das andere) Problem: ein selbstverschuldetes Chaos. Da hilft 
dann natürlich(!) kein Prüfen mehr, da kann man nur noch beten. Aber das 
ist ein anderes Problem :-)

von MaWin (Gast)


Lesenswert?

Dennis S. schrieb:
> was ist die beste Praxis wenn ich prüfen möchte, ob ein Speicherbereich
> noch mit gültigen Werten initialisiert oder bereits wieder freigegeben
> ist?

Das freigegebener Speicher anderweitig verwendet wird (und eventuell für 
etwas exakt gleiches), kann man das nicht zuverlässig prüfen.

Es gibt spezielle Speicherverwaltungen zur Fehlersuche, die mit 
zusätzlichen Bytes und Speicher nie freigeben und Markierungen (meist 
vor dem eigentlichen Spaeicherblock) überprüfen, wie gross er ist und ob 
er freigegeben ist.

Da aber in C Pointer auch mitten in Speicher zeigen können, und das 
völlig legal ist, kann so eine Überprüfung bei jedem Zugriff nur mit 
immensem Aufwand erfolgen, und damit langsamem Programmlauf.

Daher ist man am Besten bedient wenn man sich selbst einschränkt in der 
Verwendung. Denn selbst NULL-setzen von freigegebenem Speicher ist ja 
nicht zuverlässig
1
  int *ptr,*ptr2;
2
  ptr=malloc(10);
3
  ptr2=ptr;
4
  free(ptr);
5
  ptr=NULL;
6
  // und nun
7
  *ptr2=3333; // da hilft auch kein if(ptr2!=NULL)

von Kaj (Gast)


Lesenswert?

Was ist denn das für ein blödsinn?

MaWin schrieb:
> nur mit
> immensem Aufwand erfolgen, und damit langsamem Programmlauf.
Was ist an einem
1
if(pointer == NULL)
2
{
3
  return pointer_not_valid_error_code_or_what_ever;
4
}
immenser aufwand? Wo verlangsamt das das programm bitte so erheblich?

MaWin schrieb:
> int *ptr,*ptr2;
>   ptr=malloc(10);
>   ptr2=ptr;
>   free(ptr);
>   ptr=NULL;
>   // und nun
>   *ptr2=3333; // da hilft auch kein if(ptr2!=NULL)
Wenn du so eine scheiße machst, bist du selbst schuld!
Du erschafst da ein Problem, das gar keins ist, sondern nur die 
schludrigkeit des Programmieres zeigt! Es gibt halt regeln, in jeder 
Sprache, und an die hat man sich zu halten.
Wenn man sich nicht dran hält, musst man sich nicht wundern, wenns in 
die hose geht! Dein beispielcode ist von der Qualität ala: Ich will 
zeigen wie schlecht und unsicher etwas ist, also mache ich absichtlich 
fehler um das zu beweisen! Sorry, aber solch absichtlichen fehler sind 
einfach nur dumm! Da ist so als wenn du den schnittschutz an einer 
handkreissäge/winkelschleifer demontierst und dich dann beschwerst, das 
du gerade deine hand verloren hast...

Und dein Code zeigt genau folgendes Problem:
g457 schrieb:
> Wer seine Architektur dermaßen versemmelt, dass er keinen Überblick mehr
> hat, wer wann wo welche Ressourcen wie nutzt,...

MaWin schrieb:
> Denn selbst NULL-setzen von freigegebenem Speicher ist ja
> nicht zuverlässig
Natürlich kann man JEDEM System schwächen nachweisen, in dem man es 
absichtlich falsch benutzt! auch Python oder Java oder Ada kranken an 
sowas, wenn man es mit absicht falsch benutzt!

Sorry MaWin, aber dein Beispiel Code zeigt gar nichts, außer ein 
hausgemachtes Problem, was aber kein Problem der Sprache ist!

von Karl H. (kbuchegg)


Lesenswert?

g457 schrieb:

> Wer seine Architektur dermaßen versemmelt, dass er keinen Überblick mehr
> hat, wer wann wo welche Ressourcen wie nutzt, der hat dann genau obiges
> (nämlich das andere) Problem: ein selbstverschuldetes Chaos. Da hilft
> dann natürlich(!) kein Prüfen mehr, da kann man nur noch beten. Aber das
> ist ein anderes Problem :-)

Dann sind wir ja einer Meinung.

Allerdings: In einem fehlerfreien Programm passiert sowas natürlich 
nicht, dass man auf die Pointerkopie vergisst, mit der man die Knoten 
einer Liste anhand eines weiteren Sortierkriteriums in einer anderen 
Reihenfolge in eine Index-Datenstruktur per Pointer zum Original 
einträgt. Aber wann hat man schon ein fehlerfreies Programm.


> Da hilft dann natürlich(!) kein Prüfen mehr

Doch das hilft.
Hast du eine Datenstruktur in der du 6 verschiedene Strukturen jeweils 
in eigenen Listen halten musst UND es von den jeweiligen Strukturen 
Querverdindungen in andere dieser Listen in Form von Verpointerungen 
gibt (und das auch so sein muss), dann bist du heilfroh, wenn du bei der 
Entwicklung der Bearbeitungsfunktionen eine Check-Funktion hast, die dir 
alle Knoten und alle Pointer in allen Knoten abklappert und überprüft, 
ob dieser Pointer noch gültig sind, oder ob du in den 
Manipulationsfunktionen einen übersehen hast.
Die Manipulationsfunktionen einer Winged Edge Datastructure schüttelt 
man nicht (und vor allen Dingen nicht fehlerfrei) aus dem Ärmel.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Kaj schrieb:
> Was ist denn das für ein blödsinn?
>
> MaWin schrieb:
>> nur mit
>> immensem Aufwand erfolgen, und damit langsamem Programmlauf.
> Was ist an einem
>
1
> if(pointer == NULL)
2
> {
3
>   return pointer_not_valid_error_code_or_what_ever;
4
> }
5
>
> immenser aufwand? Wo verlangsamt das das programm bitte so erheblich?

Das ist nicht das, wovon MaWin gesprochen hat.

von g457 (Gast)


Lesenswert?

>> Da hilft dann natürlich(!) kein Prüfen mehr
>
> Doch das hilft.
> Hast du eine Datenstruktur in der du 6 verschiedene Strukturen jeweils
> in eigenen Listen halten musst UND [..]

Das ist nicht die Art prüfen, die ich hier meinte - denn die ist - und 
da sind wir uns offenkundig einig - schlicht nicht möglich (man kann an 
einem Zeiger sehen, dass er nicht gültig ist (er ist genullt), aber ohne 
die Runtime zu manipulieren oder höheres Wissen zu haben (umgebende 
Datenstrukturen) kann man nicht ∗prüfen∗, ob ein Zeiger, der nicht 0 
ist, gültig ist (-> Archtiktur und Wissen um die Abhängigkeit der Daten 
regelt).

Die von Dir angesprochene Art zu prüfen (Datenstrukturen auf Konsistenz 
prüfen) hat damit erst mal nichts zu tun (die fällt unter Architektur 
und höheres Wissen), ist aber selbstredend verpflichtend [0].

Also alles im grünen Bereich ;-)

Nix für ungut.

[0] wenn man wartbaren, fehlerarmen, stabilen und schnellen Code 
schreiben will

von MaWin (Gast)


Lesenswert?

Kaj schrieb:
> Wenn du so eine scheiße machst, bist du selbst schuld!

Wenn du glaubst, so etwas käme in der Realität nicht vor, in einem 10 
Jahre von 3 verschiedenen Teams gewarteten Programm, das Erweiterungen 
erhielt die bei Grundsteinlegung noch nicht vorherzuahnen waren,

dann bist du noch ein kleines Kind daß dem Sandkasten nicht entwachsen 
ist, aber schon genau weiss, wer schuld hat (du nicht).

von Amateur (Gast)


Lesenswert?

Dein Problem lässt sich NUR durch konsequentes Programmieren lösen.

Im Nachhinein läuft da nichts.

Du kannst zwar jede Freigabe suchen und mit einem Zeiger=NULL ergänzen, 
aber wenn Dein Programm, außer "if (Pointer)" noch was zustande bringen 
soll, ist diese Abfrage, im Nachhinein ein echter Bremsklotz.

Da man Fehltritte dieser Art nicht ignorieren kann, kommst Du um einen 
Leiharbeiter (Sisyphos) nicht herum.

Mit vielen Einschränkungen: lint oder seine Kumpels könnten helfen.

von Georg (Gast)


Lesenswert?

g457 schrieb:
> kann man nicht ∗prüfen∗, ob ein Zeiger, der nicht 0
> ist, gültig ist

Es nützt ja nicht mal was, wenn man sicher ist, dass man auf eigene 
Daten zeigt - eher im Gegenteil. Wenn nicht gibt es eine Exception oder 
das Programm wird von Windows beendet, soweit Ok. Ist das nicht der 
Fall, so heisst das ja noch lange nicht, dass der Pointer auf das 
"richtige" (bzw gewollte) zeigt. Als Nebeneffekt eine eigene Variable zu 
ändern ist aber noch viiiel schlimmer als ein Absturz, da kommt man erst 
nach längerer Forschung auf den Grund für seltsame Effekte.

Georg

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Karl Heinz schrieb:
> g457 schrieb:
>
>> Wer seine Architektur dermaßen versemmelt, dass er keinen Überblick mehr
>> hat, wer wann wo welche Ressourcen wie nutzt, der hat dann genau obiges
>> (nämlich das andere) Problem: ein selbstverschuldetes Chaos. Da hilft
>> dann natürlich(!) kein Prüfen mehr, da kann man nur noch beten. Aber das
>> ist ein anderes Problem :-)
>
> Dann sind wir ja einer Meinung.

Naja, es gibt aber auch Anwendungen, deren Datenfluß so komplex ist, 
daß man es nicht mehr überblicken kann.

Ein Beispiel, das hier wohl jeder kennt, ist GCC.

von kopfkratzer (Gast)


Lesenswert?

kopfkratz
Also die Standardmethode ist ja schon genannt worden.
Wenn man nun wirklich effektiv testen will ob da was sinnvolles 
drinsteht muß man halt schauen welchen OPCode man da hat und ob das 1. 
mit der Architektur übereinstimmt und 2. keine NOP Schiene ist o.ä. :-P
Gut solange es kein void pointer ist könnte man mal einen Typecast 
versuchen und abfangen, nur ganz ehrlich wozu das ganze ?
Wenn man Pointerfehler vermeiden will macht man entweder was bereits 
gesagt wurde oder steigt auf eine Programmiersprache um wo ein 
Nullpointer bzw. ungültiger Pointer eine Exception auslöst ohne das 
gleich das ganze Programm einem um die Ohren fliegt.
Welche Sprachen können das ?

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

kopfkratzer schrieb:
> Wenn man nun wirklich effektiv testen will ob da was sinnvolles
> drinsteht muß man halt schauen welchen OPCode man da hat

Das bringt Dir nur bei Funktionspointern etwas; Pointer auf Daten 
können auf irgendwas zeigen.

Wie willst Du bei einem Pointer auf uint8_t anhand des Wertes 
herausfinden, ob der "gültig" ist? Jeder mit uint8_t darstellbare Wert 
ist gültig.

von Rolf Magnus (Gast)


Lesenswert?

kopfkratzer schrieb:
> oder steigt auf eine Programmiersprache um wo ein
> Nullpointer bzw. ungültiger Pointer eine Exception auslöst ohne das
> gleich das ganze Programm einem um die Ohren fliegt.
> Welche Sprachen können das ?

Java hat zwar keine Pointer, aber offenbar Nullpointer, denn 
entsprechende Exceptions werden schon mal gerne geworfen. Ob mein 
Programm nun aber bei einem Nullpointer-Zugriff mit einem Segmentation 
Fault oder mit einer Nullpointer-Exception absemmelt, macht eigentlich 
auch keinen Unterschied.

von Georg (Gast)


Lesenswert?

Rufus Τ. Firefly schrieb:
> Wie willst Du bei einem Pointer auf uint8_t anhand des Wertes
> herausfinden, ob der "gültig" ist? Jeder mit uint8_t darstellbare Wert
> ist gültig.

Und wie ich weiter oben schon erwähnt habe, gültig reicht als Kriterium 
ja auch noch nicht - es kann nur was Sinnvolles rauskommen, wenn der 
Pointer auf das zeigt, was der Programmierer GEMEINT hat. Auf einen 
Algorithmus der das berücksichtigt werden wir aber noch einige Zeit 
warten müssen.

Georg

von Εrnst B. (ernst)


Lesenswert?

Rolf Magnus schrieb:
> Java hat zwar keine Pointer,

Doch, natürlich. Eigentlich hat java nur Pointer auf Objekte am Heap, 
und dafür keine Referenzen auf Objekte, oder z.B. Objekte am Stack / als 
"automatic" Varablen oder globals...

Aber: Java hat auch einen Garbage Collector. d.H. das Problem, dass ein 
Pointer auf einen bereits freigebenen Speicherbereich zeigt, existiert 
nicht: Einfach weil der Speicherbereich nicht freigegeben wird, solange 
ein Pointer darauf zeigt...

Wenn man C verlässt und C++ verwenden mag: Hier gibt es Tonnen von 
passenden Smart-Pointern (ggfs. boost::), die das Problem lösen. Hilft 
nur in C nix.

: Bearbeitet durch User
von Frank (Gast)


Lesenswert?

Das ist ganz trivial. Die beste Praxis dazu ist folgende:

Immer wenn man einen Speicher löscht setzt man auch den Zeiger auf Null.

Also:
free(new);
new=NULL;

Ganz einfach.

von DirkB (Gast)


Lesenswert?

Frank schrieb:
> ....

Facepalm

von Georg (Gast)


Lesenswert?

Frank schrieb:
> Ganz einfach.

Einfach schon, aber völlig wirkungslos wenn man Pointer weitergegeben 
hat, z.B. zillionenfach beim Aufruf von Windows-Funktionen. Besonders 
hübsch: char array als lokale Variable in einer Prozedur verwenden und 
dann per PostMessage weitergeben - wird die Message verarbeitet, ist 
nicht nur der Speicherbreich nicht mehr alloziert, es gibt auch keinen 
auf Null gesetzten Pointer mehr.

Georg

von Dennis S. (eltio)


Lesenswert?

Frank schrieb:
> Das ist ganz trivial. Die beste Praxis dazu ist folgende:
>
> Immer wenn man einen Speicher löscht setzt man auch den Zeiger auf Null.
>
> Also:
> free(new);
> new=NULL;
>
> Ganz einfach.

Meinen Code gelesen?

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.