Seite 1 von 2

Zeiger und Struktur

Verfasst: Mo Feb 03, 2014 10:39 pm
von ufor
Hallo zusammen
Probiere mich gerade an Strukturen dabei habe ich ein Problem mit dem Aufruf durch einen Zeiger.
Das Problem ist der Compiler bringt mir keine Fehlermeldung und auch keine Warnung, aber das Programm stürzt ab.
Hier der Code

Code: Alles auswählen

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

int main()
{
struct test
  {
  int a;
  int b;
  }eins;

  struct test *p = NULL;

  eins.a = 1;
  eins.b = 2;

  printf(" a = %d \n",eins.a);
  printf(" b = %d \n",eins.b);
  printf(" a = %d \n",p->a);
  printf(" b = %d \n",p->b);

    return 0;
}
Die ersten beiden printf Anweisungen werden ausgegeben danach ist Schluss
wäre um einen Tipp dankbar

Gruß

Re: Zeiger und Struktur

Verfasst: Mo Feb 03, 2014 10:59 pm
von Nemo
Dein Problem ist diese Zeile:

Code: Alles auswählen

  struct test *p = NULL;
Der Compiler gibt deswegen keine Fehlermeldung aus, weil das gültiges C ist, auch wenn es so keinen Zweck erfüllt. Du weist hier einen Nullzeiger zu, du willst allerdings einen Zeiger auf "eins".

So wäre es richtig:

Code: Alles auswählen

  struct test *p = &eins;
Das Programm stürzt ab, weil printf mit dem Nullzeiger nichts anfangen kann.

Ich hoffe, die Erklärung ist verständlich.

Re: Zeiger und Struktur

Verfasst: Mo Feb 03, 2014 11:06 pm
von cloidnerux
Das Problem ist der Compiler bringt mir keine Fehlermeldung und auch keine Warnung, aber das Programm stürzt ab.
Das ist ein häufiges Symptom, wenn man unsauber oder fehlerhaft mit Zeigern arbeitet.

Code: Alles auswählen

  struct test *p = NULL;
Der Sinn eines Zeigers(Pointers) ist als Variable mit fester Größe auf eine Adresse im Speicher zu Zeigen um so auf Variablen/Strukturen/Daten mit beliebiger Größe zu zu greifen.
Das bedeutet, den Wert den ein Pointer an sich hat, ist die Adresse von dem auf das du zugreifen willst, der Pointer-Typ ist nur ein schwacher Typen-Schutz und damit man leichter mit Pointern arbeiten kann.
Was du in der genannten Code-Zeile machst ist ein Pointer anzulegen und ihm den Wert 0 zu geben. Damit zeigt er auf die Speicheradresse 0. Wenn du nun aber versuchst von da zu lesen, dann erzeugt das einen ungültigen Aufruf und dein System bricht das Programm ab. Das ist das was du siehst.
Deswegen, wie schon Nemo geschrieben hat, dem Pointer einen gültigen Wert verpassen.

Re: Zeiger und Struktur

Verfasst: Di Feb 04, 2014 10:47 am
von Xin
Nemo hat geschrieben:Der Compiler gibt deswegen keine Fehlermeldung aus, weil das gültiges C ist, auch wenn es so keinen Zweck erfüllt.
Kleiner Einspruch: Der Nullpointer hat einen Zweck, nämlich den auszusagen, dass die Zeigervariable nicht genutzt wird. Würde sie einfach nur willkürlich in die Gegend zeigen könnte man nicht unterscheiden, ob das Ziel ein gültiger Wert oder einfach nur zufällig da steht. Der Nullzeiger kennzeichnet einen "leeren" Zeiger.

Aber Du hast natürlich recht, wenn es darum geht, dass er vermutlich einen Zeiger auf eins braucht.
Nemo hat geschrieben:Das Programm stürzt ab, weil printf mit dem Nullzeiger nichts anfangen kann.
Hsmpf... ungenau. (jaja, ich weiß ich bin ein Korinthenkacker ;-))

Der printf greift auf das Datenelement bei Adresse 0 zu - das funktioniert bei alten Betriebsystemen und printf spuckt so irgendwelchen Müll raus, der halt bei 0 steht. Moderne Betriebsysteme haben Speicherschutz. Der Zugriff auf die Adresse 0 ist damit verboten und das Betriebsystem bricht das Programm ab, weil man in ein Speichersegment zugreifen möchte, für das einem die Rechte fehlen. Entsprechend gibt das OS (unter Linux) einen "Segmentation fault" aus.

Das ist ein gültiges C-Programm, dass halt nicht tut, was der Entwickler wollte und dabei etwas tut, was das OS auch nicht will. Darum schießt das OS das Programm ab.

Re: Zeiger und Struktur

Verfasst: Di Feb 04, 2014 3:14 pm
von mfro
Der aktuelle C-Standard (der sich - genauso wie ich - anscheinend sehr wohl vorstellen kann, daß man in C auf Daten an Adresse 0 zugreifen können muß) definiert (übrigens anders als C++), daß ein ungültiger Zeiger (=NULL) als "(void *) 0L" definiert ist.

Ein "(void *) 0L" ist also (in C, in C++ nicht) etwas völlig anderes als z.B. ein "(char *) 0L".


Edit by Xin: Hier standen noch ein paar weitere Sätze, die ich blöderweise entfernte und nicht rekonstruiert bekomme. Habe offenbar statt auf 'Zitieren' auf 'Ändern' geklickt und es nicht gemerkt. Sorry!!!

Re: Zeiger und Struktur

Verfasst: Di Feb 04, 2014 3:53 pm
von Xin
Xin hat geschrieben:Der aktuelle C-Standard (der sich - genauso wie ich - anscheinend sehr wohl vorstellen kann, daß man in C auf Daten an Adresse 0 zugreifen können muß) definiert (übrigens anders als C++), daß ein ungültiger Zeiger (=NULL) als "(void *) 0L" definiert ist.

Ein "(void *) 0L" ist also (in C, in C++ nicht) etwas völlig anderes als z.B. ein "(char *) 0L".
Ich bin im aktuellen C-Standard nicht mehr so bewandert, aber steht da noch drin, dass sich (void *) völlig kommentarlos auf etwas beliebiges anderes konvertieren lässt, wie beispielsweise (char *)? So bekommt man in C malloc mit der Rückgabe (void *) dazu, das Ergebnis auf eine (char *)-Variable zu speichern.

So wie ich C gelernt habe, ist (void *) 0L exakt das gleiche wie (char *) 0L, entsprechend muss sich der Compiler auch keine Gedanken darüber machen, ob er das unterscheiden muss oder nicht.

NULL == (void *)0 ist als ungültiger Zeiger definiert, schon alleine weil man das billig abfragen kann und weil die Chance sehr gering ist, dass alle ihre Daten bei Adresse 0 ablegen. Es ist also einfach zu definieren, dass gefälligst niemand irgendwas bei 0 ablegt.
Im Ausnahmefall (Systemprogrammierung) kann der Nullpointer aber natürlich auch ein gültiger Pointer sein.

Re: Zeiger und Struktur

Verfasst: Di Feb 04, 2014 6:12 pm
von ufor
Hallo an alle

Erst mal dankeschön für die schnelle Antwort. Das hilft mir weiter und beantwortet gleichzeitig meine zweite Frage wie weiß der Zeiger, dass a aus der Struktur kommt.
Den Zeiger hab ich initial auf NULL gesetzt da ich mir dachte besser ein Zeiger auf NULL als er zeigt irgendwo hin.
Mein Gedanke war, daß ich mit -> a eben den Zeiger auf diese Element zeigen lasse.

Noch mal Danke für eure Hilfe und eure Geduld, ich glaube so kann ich mich verbessern.

ufor

Re: Zeiger und Struktur

Verfasst: Di Feb 04, 2014 8:50 pm
von Nemo
Xin hat geschrieben: Hsmpf... ungenau. (jaja, ich weiß ich bin ein Korinthenkacker ;-))
Ein Korintenhacker sozusagen :D
Xin hat geschrieben: Der Zugriff auf die Adresse 0 ist damit verboten und das Betriebsystem bricht das Programm ab, weil man in ein Speichersegment zugreifen möchte, für das einem die Rechte fehlen.
Warum gibt es überhaupt noch ein Speichersegment 0, könnte man nicht einfach mit 1 zu zählen beginnen, oder war das schon so gemeint?
ufor hat geschrieben:Den Zeiger hab ich initial auf NULL gesetzt da ich mir dachte besser ein Zeiger auf NULL als er zeigt irgendwo hin.
Hierzu würde mich die Meinung der erfahreneren Programmierer interessieren. Schaden kann es ja kaum, die Frage ist, ob es wirklich etwas bringt.

Re: Zeiger und Struktur

Verfasst: Di Feb 04, 2014 11:21 pm
von Xin
Nemo hat geschrieben:
Xin hat geschrieben: Hsmpf... ungenau. (jaja, ich weiß ich bin ein Korinthenkacker ;-))
Ein Korinthenhacker sozusagen :D
:-D
Genau ^^
Nemo hat geschrieben:
Xin hat geschrieben: Der Zugriff auf die Adresse 0 ist damit verboten und das Betriebsystem bricht das Programm ab, weil man in ein Speichersegment zugreifen möchte, für das einem die Rechte fehlen.
Warum gibt es überhaupt noch ein Speichersegment 0, könnte man nicht einfach mit 1 zu zählen beginnen, oder war das schon so gemeint?
Wenn Du eine Liste von Elementen hast, die Du durchzählen kannst, kommst Du häufig vor das Problem, dass ein Element als "ungültig" deklariert wird. Wenn Deine Liste noch leer ist, kommst als erstes das ungültige Element und erhält den Index 0.

Nun ist ein Speicherriegel kein wachsendes Array, sondern hat immer eine feste Größe. Die CPU kann aber schnell mit 0 vergleichen und daher ist die Speicherstelle 0 zwar da, wird aber in der Applikationsprogrammierung nicht benutzt. Man fängt bei Index 1 an, seine Daten zu schreiben (wobei Index 1 bei einer 32 Bit CPU eben gerne die Adresse 4 ist).
Nemo hat geschrieben:
ufor hat geschrieben:Den Zeiger hab ich initial auf NULL gesetzt da ich mir dachte besser ein Zeiger auf NULL als er zeigt irgendwo hin.
Hierzu würde mich die Meinung der erfahreneren Programmierer interessieren. Schaden kann es ja kaum, die Frage ist, ob es wirklich etwas bringt.
Das ist eine Frage der Definition von "Schaden": Initialisieren von Variablen macht einen Algorithmus niemals falsch. Eher richtiger (falls man später irgendwo vergisst die Variable mit dem richtigen Wert zu setzen - nicht selten läuft's dann trotzdem). Ein richtiger Algorithmus setzt aber genau da und ausschließlich da, wo die Variable gesetzt werden muss, den Wert der Variable. Und zwar genau so oft, wie es sein muss und nicht häufiger.

Code: Alles auswählen

int a = 0;

if( condition() )
   a = 1;

// a ist in keinem Fall willkürlich gesetzt
Dieser Code wird immer funktionieren, da a immer einen definierten Zustand hat. Der Algorithmus kann also nicht aufgrund einer fehlenden Initialisierung von a fehlschlagen. Trotzdem ist der Code wenn es um Effizienz geht nur zweitklassig, da a erst immer auf 0 gesetzt wird und dann eventuell nochmal überschrieben wird. Wenn es überschrieben wird, braucht man es auch nicht zu initialisieren.

Code: Alles auswählen

int a;

if( condition() )
   a = 1;
else
   a = 0;

// a ist in keinem Fall willkürlich gesetzt
Oder kurz:

Code: Alles auswählen

int a;

a = condition() ? 1 : 0;
// a ist in keinem Fall willkürlich gesetzt
Oder speziell für diesen Fall in "tricky":

Code: Alles auswählen

int a;

a = condition() == true;
// a ist in keinem Fall willkürlich gesetzt
Oder more tricky:

Code: Alles auswählen

int a;

a = !!condition();
// a ist in keinem Fall willkürlich gesetzt

Re: Zeiger und Struktur

Verfasst: Di Feb 04, 2014 11:30 pm
von cloidnerux
Hierzu würde mich die Meinung der erfahreneren Programmierer interessieren. Schaden kann es ja kaum, die Frage ist, ob es wirklich etwas bringt.
Es gibt Leute die ihre variablen mit werten wie 0xDEAD oder 0xDEADBEEF initialisieren, damit es beim debuggen auffällt, wenn variablen nicht richtig mit werten gefüttert werden.
Viele C-Funktionen geben im Fehlerfall einen Nullpointer zurück, damit eben auf einem Fehler prüfen kann.
Bei Pointern macht die Initialisierung mit null schon Sinn, man muss andere beachten, dass das Programm auch mal einfach abstürzen kann, eben wegen dem Segmentation Fault.
Bei Variablen muss man im Einzelfall entscheiden.