Christian Seiler: (C / Linux) Richtig debuggen?

Beitrag lesen

Hallo Markus,

files = (File *)calloc(filecount, sizeof(File *));  //Genauso viel Speicher reservieren wie man File-Zeiger hat

Zum einen: Hier wäre es wieder interessant zu erfahren, wie File definiert ist. Ist das ein typedef auf struct file oder auf struct file *?

Zum anderen: Das ist falsch! Du willst ein Array allozieren (d.h. files ist om Typ "File *"), jedes einzelne Element (2. Argument von calloc) ist aber vom Typ "File", d.h. es hat die Größe sizeof(File), nicht sizeof(File *)! In Deinem anderen Thread hattest Du das zumindest doch richtig?

if ( (files[i] = (File)malloc(sizeof(File)) ) == NULL)   {

Ok, das lässt mich darauf schließen, dass Du

typedef struct file *File;

hast. Ich habe aus gutem Grund davon abgeraten! Denn hier hast Du das Problem: sizeof(File) ist die Größe eines Zeigers (!) auf die Struktur, nicht die Größe der Struktur! Du allozierst hier also mit Abstand viel zu wenig (!) für jede einzelne Datei!

Ich füge hier nochmal ein paar generelle Erklärungen zu Zeigern in C und zu malloc() / calloc() ein:

1. Ein Zeiger ist eine Variable, die die Adresse eines Speicherbereichs enthält. Beispiel:

int a = 0;  
int *p = &a;

Dann sieht der Speicher so aus:

+------------------+
    a: | 0                |<-----+
       +------------------+      |
    p: | 0x.........  ----|------+
       +------------------+

D.h. p ist eine Variable, die die Speicheradresse der Variable a enthält. a enthält dagegen den Wert der Variable.

sizeof(p) gibt Dir die Größe der Variable p zurück, d.h. die benötigten Bytes, um eine Speicheradresse (!) zu speichern. sizeof(a) gibt Dir die Größe der Variable a zurück, d.h. die benötigten Bytes, um den Wert (!) zu speichern.

2. malloc/calloc

malloc/calloc liefern Dir einen Zeiger auf einen dynamisch allozierten Speicherbereich zurück. Dieser Speicherbereich hat die Größe, die Du malloc/calloc übergibst, malloc/calloc geben Dir jedoch nur die Adresse zurück, aus der Du nicht die Größe des Speicherbereichs sclhießen kannst. Beispiel:

int *p = (int *)malloc(1); /* BÖSE */

+----------------+       +----------------------------------+
  p: | 0x........    -|------>| Speicherbereich der Größe 1 Byte |
     +----------------+       +----------------------------------+

Das würde einen Speicherbereich der Größe 1 Byte allozieren und die Adresse zurückgeben. Da passt natürlich kein int hinein, d.h. wenn Du auf *p zugreifen willst, hast Du ein Problem.

Weiteres Beispiel:

int *p = (int *)malloc(sizeof(int)); /* OK */

+----------------+       +---------------------------------------+
  p: | 0x........    -|------>| Speicherbereich der Größe sizeof(int) |
     +----------------+       +---------------------------------------+

Hier allozierst Du einen Speicherbereich, wo ein int hineinpasst und weist die Speicheradresse der Variablen p zu. Nachdem in den Speicherbereich, auf den p zeigt, genügend hineinpasst, hast Du hier keine Probleme.

3. Dynamische Arrays

In C kannst Du auf Zeiger wie auf Arrays zugreifen. Im Endeffekt ist die Syntax array[i] nichts anderes als eine Kurzform für *(array + i), d.h. nimm die Speicheradresse von "array" und addiere i mal die Elementgröße des Typs auf den Array zeigt drauf und dereferenziere diese neue Speicheradresse. Beispiel:

int *p = (int *)calloc(4, sizeof(int));  
p[0] = 1;  
p[1] = 2;  
p[2] = 3;  
p[3] = 4;  
printf ("%d\n", *(p + 2));

Ausgabe: 3.

Im Speicher sieht das ganze so aus:

jedes dieser Elemente ist
                                 von der Größe sizeof(int)
                               _____   _____   _____   _____
                              /     \ /     \ /     \ /     \      +----------+            +-------+-------+-------+-------+
  p: | 0x..... -|----------->|     1 |     2 |     3 |     4 |
     +----------+            +-------+-------+-------+-------+
                              ^       ^       ^       ^
                              |       |       |       |
        &p[0] == p + 0 -------+       |       |       |
        &p[1] == p + 1 ---------------+       |       |
        &p[2] == p + 2 -----------------------+       |
        &p[3] == p + 3 -------------------------------+

4. Was passiert nun wenn Du in Deinem Fall folgende Situation hast:

struct file {  
 /* irgendwas drin */  
};  
  
typedef struct file *File;  
  
int count = 42;  
File *files = (File *)calloc(count, sizeof(File *));

Um das wirklich verstehen zu können ist es hier sinnvoll, die typedefs wegzulassen:

struct file **files = (struct file **)calloc(count, sizeof(struct file **));

Zuerst der calloc-Aufruf: Es wird hier ein Speicherbereich allozierst, der 42 Elemente enthält, die so groß sind wie der Typ "struct file **", also ein Zeiger auf einen Zeiger auf struct file. Was Du aber eigentlich allozieren willst ist ein Array von Zeigern auf struct file (struct file ** - hier meint das zweite * das Array während das erste * dann die Zeiger auf die einzelnen Blöcke selbst meint - Pointerdeklarationen in C liest man von Rechts nach Links). Das heißt: Du willst eigentlich folgendes Speicherbild haben:

+------ Typ (schematisch, keine korrekte Syntax):
 |                                   ((struct file *)*) files;
 |                                     \___________/ |
 |                                         |         |
 |                    Zeier auf struct file       Array von
 |
 |                               jedes dieser Elemente ist
 |                               von der Größe sizeof(struct file *)
 |                               _____   _____   _____   _____
 |                              /     \ /     \ /     \ /     \  v     +----------+            +-------+-------+-------+-------+- ...
files: | 0x..... -|----------->| 0x... | 0x... | 0x... | 0x... |  ...
       +----------+            +-------+-------+-------+-------+- ...
                                ^       ^       ^       ^
                                |       |       |       |
  &files[0] == files + 0 -------+       |       |       |
  &files[1] == files + 1 ---------------+       |       |
  &files[2] == files + 2 -----------------------+       |
  &files[3] == files + 3 -------------------------------+

Da jedes Element eigentlich vom Typ "struct file *" sein soll müsstest Du eigentlich sizeof(struct file *) angeben und *nicht* wie Du es gemacht hast sizeof(struct file **). Da alle Zeiger auf Daten in C die gleiche Größe haben müssen, ist sizeof(struct file **) genau so groß wie sizeof(struct file *) - hier an der Stelle bist Du durch Zufall aus der Klemme.

Wenn Du dann jedoch folgende Zeile hast:

files[i] = (struct file *)malloc(sizeof(struct file *));

Dann hast Du ein Problem. Denn hier willst Du eigentlich folgende Speichersituation haben:

+------------- Hat den Typ (schematisch, keine valide Syntax):
   |                                     (struct file *)
   |                                      \___________/
   |                                Zeiger auf struct file
   |
   |                         Hat die Größe sizeof(struct file)!
   |                        ________________________________
   |                       /                                \    |       +--------+     +----------------------------------+
 files[i]: | 0x... -|---->| Hier die eigentliche struct file |
           +--------+     +----------------------------------+

Dann schießt Du Dir hiermit selbst ins Knie. Denn Deine struct file ist um ein vielfaches Größer als der Speicherbereich, der für einen Zeiger zur Verfügung steht - und damit überschreibst Du Speicher, der gar nicht mehr zum eigentlich allozierten Bereich gehört, was Dir in andere Teile des Programms reinspielt, was dann an einer VÖLLIG ANDEREN Stelle zu einem Absturz führt, weil die Daten dort nicht mehr konsistent sind.

Erst einmal: So geht es richtig:

files[i] = (struct file *)malloc(sizeof(struct file));

Oder, wenn Du unbedingt bei Deinem typedef auf einen Zeiger bleiben willst (ich halte das wie gesagt für sehr gefährlich, weil es Informationen vor einem versteckt!), dann:

files[i] = (File)malloc(sizeof(*File));

sizeof(*File) wäre hier die Größe des Typs auf den File ein Zeiger ist.

########################### MERKREGEL #############################
 #                                                                 #
 #  Es gibt IMMER, IMMER, IMMER, IMMER,  IMMER eine Differenz von  #
 #  GENAU EINEM Stern (*) zwischen dem Typen, den man von malloc,  #
 #  calloc, realloc oder alloca zurückbekommen will und dem Typen, #
 #  den man in da sizeof()-Argument der Funktion packt!            #
 #                                                                 #
 ###################################################################

Als weiterer, genereller Tipp: Der Fehler, das man in C über einen Speicherbereich hinausschreibt, ist relativ häufig. Daher gibt es Tools, mit denen man so etwas erkennen kann. Eines davon nennt sich valgrind. Das erkennt, solche Speicherzugriffe und merkt sich, an welcher Stelle im Programm sie passieren (man muss das Programm natürlich mit Debug-Symbolen kompilieren, damit das funktioniert).

Valgrind kann außerdem noch viel mehr:

* Zugriff auf uninitialisierten Speicher erkennen, bspw.:
        int a; printf ("%d\n", a);
   Allerdings nicht nur auf dem Stack, was moderne Compiler inzwischen
   selbst erkennen, sondern auch auf dem Heap, was ein Compiler nicht
   erkennen kann.

* Memory-Leaks erkennen, d.h. wenn Du Speicher, den Du per malloc, calloc
   oder realloc alloziert hast mit free() nicht mehr freigibst.

* Einige andere Dinge, die ebenfalls mit Speicherverwaltung zu tun haben.

Beachte aber, dass die Systembibliotheken einige uninitialisierte Speicherzugriffe beim Start der Anwendung vornehmen, d.h. wenn Du in libc.so oder ld.so ganz am Anfang des Programms uninitialisierte Speicherzugriffe gemeldet bekommst, dann ist das nicht zwangsweise ein Fehler in Deinem Programm. Am besten Du probierst valgrind mal mit einem billigen "Hello World"-Programm aus, das garantiert keine derartigen Fehler enthält und genau die Meldungen, die da auch kommen, kannst Du grundsätzlich ignorieren. Alle anderen aber nicht!

Viele Grüße,
Christian

--
Mein "Weblog" [RSS]
Using XSLT to create JSON output (Saxon-B 9.0 for Java)
»I don't believe you can call yourself a web developer until you've built an app that uses hyperlinks for deletion and have all your data deleted by a search bot.«
            -- Kommentar bei TDWTF