Untypischer Laufzeitfehler durch malloc()



  • Wenn Du sowas machst wie:

    int *foo = malloc(ROWS * sizeof(int*));
    

    hast Du einen zusammenhängenden Speicherbereich für ROWS Zeiger auf int.
    foo[0] ist der erste dieser Zeiger, foo[ROWS - 1] ist der letzte.

    Dann alloziierst Du mit malloc() die "zweite Dimension" und lässt diese Zeiger foo[0 ... ROWS-1] darauf zeigen:

    for (size_t row = 0; row < ROWS; ++row)
        foo[row] = malloc(COLUMNS * sizeof(int));
    

    Darauf zugreifen kannst Du nun mit foo[row][column].
    Was Du aber nicht machen kannst ist den ersten Zeiger aus foo (foo[0]) nehmen und darauf lustig hinzuaddieren. Die einzelnen Speicherbereiche auf die die Zeiger foo[0 ... ROWS-1] zeigen HÄNGEN NICHT ZUSAMMEN!



  • @Swordfish
    Mit

    int **feld = malloc(width * sizeof(int));
    for(cnt=0; cnt<width; cnt++) feld[cnt]=malloc(height * sizeof(int));
    

    alloziiere ich doch ein Feld von Zeigern das auf int-typen zeigt?
    wenn ich

    *feld = malloc(width * sizeof(int *));
    

    asführe warnt mich der Compiler mit
    warning: assignment to ‘int’ from ‘void *’ makes integer from pointer without a cast [-Wint-conversion]
    174 | *feld = malloc(width * sizeof(int *));
    wenn ich

    *feld = (int)malloc(width * sizeof(int *));
    

    ausführe mit warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
    174 | *feld = (int)malloc(width * sizeof(int *));

    Ich denke die Breite der Typen kann auf unterschiedlichen Platformen zu Problemen führen?
    Auch kann ich dann mit

    &feld[0][0]
    

    nicht mehr die Adresse an eine Funktion übergeben



  • T'schuldige, das da:

    @Swordfish sagte in Untypischer Laufzeitfehler durch malloc():

    Wenn Du sowas machst wie:

    int *foo = malloc(ROWS * sizeof(int*));
    

    sollte natürlich

    int **foo = malloc(ROWS * sizeof(int*));
    //   ^
    

    heißen.



  • @EL-europ sagte in Untypischer Laufzeitfehler durch malloc():

    alloziiere ich doch ein Feld von Zeigern das auf int-typen zeigt?

    Ja, damit bekommst Du ein "Array" von Zeigern auf int.
    feld zeigt dann auf einen zusammenhängenden Speicherbereich für diese Zeiger.

    Dann alloziierst Du für jeden dieser Zeiger ein "Array" von ints. Diese einzelnen Bereiche sind auch jeweils zusammenhängend, alle Werte direkt hintereinander im Speicher.

    ABER:
    Das "Array" auf das feld[1] zeigt kann ganz wo anders sein als das "Array" auf das feld[0] zeigt. Diese einzelnen "Arrays" können im Speicher überall verstreut sein.

    Deshalb kannst Du nicht einfach den ersten Zeiger feld[0] nehmen und von dem ausgehend bis feld[width * height - 1] durchgehen wie Du es zB in initalFeld() machst.



  • @Swordfish
    Ah, ok. Eben 🙂
    Um nochmal klar zu stellen:
    mit

    int **foo =malloc(cnt * sizeof(int))
    for(cnt=0; cnt<width; cnt++)feld[cnt]=malloc(height * sizeof(int));
    

    erstelle ich ein Feld in dem width*height int-typen gespeichert werden
    und mit

    feld = malloc(width * sizeof(int *));
    for(cnt=0; cnt<width; cnt++) feld[cnt]=malloc(height * sizeof(int));
    

    erstelle ich ein Feld in dem width * Zeiger auf height * int-typen zeigen? Welchen Vorteil hat das? Vielleicht Portabilität? Wenn das so ist muss ich den Zugriff auf die Zeiger und deren Werte im Programm bei Änderung auch noch über- prüfen/denken.

    Das tu jetzt mal



  • @EL-europ
    das tu ICH jetzt mal will ich sagen 🙂 sry



  • @Swordfish sagte in Untypischer Laufzeitfehler durch malloc():

    @EL-europ sagte in Untypischer Laufzeitfehler durch malloc():

    alloziiere ich doch ein Feld von Zeigern das auf int-typen zeigt?

    Ja, damit bekommst Du ein "Array" von Zeigern auf int.
    feld zeigt dann auf einen zusammenhängenden Speicherbereich für diese Zeiger.

    Dann alloziierst Du für jeden dieser Zeiger ein "Array" von ints. Diese einzelnen Bereiche sind auch jeweils zusammenhängend, alle Werte direkt hintereinander im Speicher.

    ABER:
    Das "Array" auf das feld[1] zeigt kann ganz wo anders sein als das "Array" auf das feld[0] zeigt. Diese einzelnen "Arrays" können im Speicher überall verstreut sein.

    Deshalb kannst Du nicht einfach den ersten Zeiger feld[0] nehmen und von dem ausgehend bis feld[width * height - 1] durchgehen wie Du es zB in initalFeld() machst.

    Eben hab ich dich vielleicht verstanden und was dazu gelernt?
    In beiden Fällen sind es Zeiger auf int-typen, in erster Version sind die int-arrays nicht hintereinanader im Speicher und mit

    int **feld=malloc(size * sizeof(int *));
    

    werden auch die int-arrays hintereinander im Speicher abgelegt?



  • @EL-europ sagte in Untypischer Laufzeitfehler durch malloc():

    werden auch die int-arrays hintereinander im Speicher abgelegt?

    Nein.

    Ein Doppelzeiger **feld ist kein 2D-Array - es sieht nur so aus.



  • @DirkB
    ja stimmt. Durch den wiederholten Aufruf von malloc() in der Schleife für jedes einzelne array kann nicht gewährleisten sein das sie hintereinander liegen. oder?



  • @SeppJ
    es ist tatsächlich so das ich ein c-Grundlagen und ein c++ umfassendes Handbuch von J.W. habe die beide, zumindest Didaktisch, nicht an andere Autoren heranreichen. Seine Beispielcodes werfen oft Fragen auf die nicht beantwortet werden. Frag jetzt bitte nicht nach einem Beispiel, hab die Bücher grade hinter mir und mag nicht grundlos darin lesen und schlechte Beispiele suchen



  • Ich weiß nicht, wie man es Dir erzählen muss damit der Groschen fällt.

    int *foo;
    foo = malloc(42 * sizeof(int));
    

    Da ist foo ein Zeiger auf einen Integer dem das Ergebnis von malloc() zugewiesen wird. Diese von malloc() zurückgegebene Adresse steht nun in foo.

    Oben sagte ich "Zeiger auf einen" Integer. Du weißt aber, daß malloc() einen zusammenhängenden Speicherbereich besorgt hat, in dem 42 Integer alle direkt hintereinander liegen. Also kannst Du *(foo + 0), *(foo + 1), *(foo + 2), *(foo + 3), ... bis *(foo + 42 - 1) machen um auf die einzelnen Elemente dieses Speicherbereichs zuzugreifen. (Oder, dasselbe, nur hübscher: foo[0], foo[1], foo[2], ... bis foo[42 - 1].)

    Zu Deinem 2d-Dings:

    int **bar;  // ein Zeiger auf einen Zeiger auf Integer
    bar = malloc(42 * sizeof(int*));
    

    Da ist foo ein Zeiger auf einen [Zeiger auf Integer] dem das Ergebnis von malloc() zugewiesen wird. Diese von malloc() zurückgegebene Adresse steht nun in bar.

    Wieder dasselbe wie oben: in bar steht nun die von malloc() zurückgegebene Adresse und wir wissen, daß malloc() einen zusammenhängenden Speicherbereich besorgt hat in dem 42 [Zeiger auf Integer] alle direkt hintereinander liegen. Also können wir *(bar + 0), *(bar + 1), *(bar + 2), *(bar + 3), ... bis *(bar + 42 - 1) machen um auf die einzelnen Elemente dieses Speicherbereichs zuzugreifen. (Oder, dasselbe, nur hübscher: bar[0], bar[1], bar[2], ... bis bar[42 - 1].)

    Schön. Nun besorgen wir uns die 2. Dimension:

    int **bar;                           // dasselbe wie
    bar = malloc(42 * sizeof(int*));    // schon im letzten codeschnippsel
    
    for (size_t i = 0; i < 42; ++i)
        bar[i] = malloc(13 * sizeof(int));  // DIESE ZEILE
    

    besorgt nun jeweils mittels malloc() einen zusammenhängenden Speicherbereich für 13 Integer. Das Ergebnis von malloc() (=die Adresse wo dieser Speicherbereich beginnt) speichern wir jeweils in bar[0], bar[1], bar[2], ... bar[42 - 1].

    Das schaut nun im Speicher so aus:

    
    
     Irgendwo am Stack           Irgendwo am Heap                           Irgendwo am Heap
      |                           |                                           |
    +-------+    zeigt auf    +-------------------------+   zeigt auf      +-----------+
    | bar   |---------------> |  einen Zeiger auf int   |----------------->| einen int |
    +-------+                 |-------------------------|                  | einen int |
                              | malloc garantiert uns   |                  | einen int |
                  bar +  1 -->| daß einen Zeiger weiter |-----------+      | ...       |
                              | noch ein Zeiger ist der |           |      | einen int |
                              | uns gehört              |           |      +-----------+
                              |-------------------------|           |                                    Irgendwo am Heap
                  bar +  2 -->| und noch einer          |----+      |                                       |
                              |-------------------------|    |      |                                    +-----------+
                  bar +  3 -->| und noch einer          |    |      +----- *(*(bar + 1) +  0) ---------->| einen int |
                              |-------------------------|    |             *(*(bar + 1) +  1) ---------->| einen int |
                  ...               wird langweilig          |             *(*(bar + 1) +  2) ---------->| einen int |
                              |-------------------------|    |                   ...                     | ...       |
                  bar + 40 -->| und noch einer          |    |             *(*(bar + 1) + 12) ---------->| einen int |
                              |-------------------------|    |                                           +-----------+
                  bar + 41 -->| und der letzte          |    |
                              +-------------------------+    |                                           IRGENDWO am Heap
                                                             |                                            +-----------+
                                                             +-------------*(*(bar + 2) +  0) ----------->| einen int |
                                                                           *(*(bar + 2) +  1) ----------->| einen int |
                                                                           *(*(bar + 2) +  2) ----------->| einen int |
                                                                                 ...                      | ...       |
                                                                           *(*(bar + 2) + 12) ----------->| einen int |
                                                                                                          +-----------+
    

    Du kannst die Pointer bar[0], bar[1], bar[2], ... bar[42 - 1] nehmen und die dereferenzieren um zu den jeweiligen Anfängen der 2. Dimension zu kommen. bar[y] ist bloß eine hübsche Schreibweise für *(bar + y). Also ist bar[y][x] nur eine hübsche Schreibweise für *(*(bar + y) + x).

    Du addierst zu bar aber einfach immer weiter hinzu bis du irgendwann bei bar + y * x bist. Das funktioniert so nicht weil die Dingstis der "2. Dimension" überall im Speicher verstreut sein können und nicht hintereinander liegen.



  • @Th69 sagte in Untypischer Laufzeitfehler durch malloc():

    @hustbaer sagte in Untypischer Laufzeitfehler durch malloc():

    So wie die Schleife da steht läuft die bis inklusive width * height, sollte aber bis exklusive width * height laufen weil ja bei 0 angefangen wird.
    Also bei beispielsweise width = height = 4 würde die Schleife von 0 bis inklusive 4 laufen, also 5 mal. Sollte aber nur 4 mal laufen.

    Du meinst "würde die Schleife von 0 bis inklusive 16 laufen, also 17 mal. Sollte aber nur 16 mal laufen."!?!

    Fast. Ich wollte width = height = 2 und 4/5 schreiben, und hab dann fälschlicherweise 4/4/5 statt 2/4/5 geschrieben. Läuft aber auf's gleiche raus 🙂



  • Danke der Mühe @Swordfish 🙏🏻
    vor allem die Zeichnung hat mir geholfen zu verstehen. Eins hab ich nun auch noch gelernt: Der Zufall lässt auch Programme laufen die falsch geschrieben sind



  • Danke nochmal allen für ihre Beiträge, das Thema "Zeiger" verstehe ich dank @Swordfish nun besser.
    Jetzt beschäftige ich mich mit Threads in c um die Schlange alleine laufen zu lassen wenn keine Taste gedrückt wird.



  • Dazu brauchst Du keine Threads sondern nur etwas das Tastatureingaben ohne zu blocken erkennt. Schau nach ncurses, der funktion getch() und nodelay.



  • @Swordfish
    Danke für den Hinweis, werd jetzt ncurses Wiki studieren damit ich da rein komme. Hast du vielleicht noch ein paar Tips, oder zusammenfassende Worte für mich bezüglich der ncurses? So wie ich das jetzt verstehe muss neben dem einbinden der ncurses.h beim compilen dem Compiler über "-Incurses" mitgeteilt werden das er die ncurses "benutzt"
    Ist der Umgang anderes als mit den üblichen stdlib.h und stdio.h



  • @EL-europ Die Headerdateien .h sind nicht die Library
    Dort stehen (nur) die Informationen über die Funktionen, aber kein Code.
    Der Code dazu steht (meist) in .lib-Dateien und werden vom Linker dazu gebunden.
    Bei der Standardbibliothek ist das klar, bei Erweiterungen müssen die mit angegeben werden.

    In der Standardbibliothek sind eigentlich alle Funktionen der Header enthalten, die du mit den <> beim #include angibst.
    (wenn man da nicht selber rumgepfuscht hat)

    Eine Ausnahme ist <math.h>. Wenn man die Fließkommafunktionen nutzen möchte, muss man auf manchen Systemen die math-Libraray mit -lm dazu binden.
    (Das gilt nicht für + - * /, sondern für Funktionen wie sin(), sqrt(), ... )



  • @EL-europ
    ncurses ist kein C-Sprachstandard, ein standardkonformer Compiler muss diese Bibliothek nicht anbieten.



  • @Wutz Danke für die Info



  • Danke @DirkB für die ausführliche antwort. Aber mir kommen weitere Fragen dazu auf. Wenn du schon Antworten hast würde mir das einen Haufen "Forschungsarbeit" abnehmen.
    Library bedeuted: Das der Quellcode nicht verfügbar sondern nur ein Binary auf dem Entwicklungsrechner ist?
    Musss die Library auch auf dem Zielsystem vorhanden sein?
    Wenn ja, und ich den Quellcode habe und mit Einbinde muss dann trotzdem die Library auf Zielsystem vorhanden sein?
    Wenn ich cross-compile, brauche ich dann die Library im "cross-Format"?