Search code examples
carraysstructfreecalloc

Error in `./a.out': free(): invalid next size (normal) while freeing dynamically allocated 2D array of struct


Basically, I am creating 2D array of struct using calloc(). Then I utilize that array and I free that allocated space, while freeing it I am getting "double free or corruption (!prev)". Code is written in C language.


Code:

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <fcntl.h>
#include <malloc.h>
#include <unistd.h>
#include <string.h>
#include <float.h>

typedef struct  complex_num{
    double real;
    double imag;
}comp;

void transpose(comp *arr, int height, int width);
void change(comp *arr, int width);

void main(){    
        int width = 64, height = 64;

        int len = width;
        comp *fft[len];
        for(int i=0; i<len; i++){
                fft[i] = (comp *)calloc(len, sizeof(comp));
        }


        for (int scan=0;scan<height;scan++){
                for (int pix=0;pix<width;pix++){
                        fft[scan][pix].real = 1.0;
                        fft[scan][pix].imag = 0.0;
                }
                change(&fft[scan][0], len);
        }
        transpose(&fft[0][0], len, len);
        for(int i=0;i<len;i++){
                change(&fft[i][0], len);
        }
        transpose(&fft[0][0], len, len); 
        for(int i=0;i<len;i++){
                free(fft[i]);
        }
}
void transpose(comp *arr, int height, int width){
        comp var;
        for(int i=0;i<height;i++){
                for(int j=0;j<width;j++){
                        if(j>i){
                                var = *((arr + (i*width)) + j);
                                *((arr + i*width) + j) = *((arr + j*width) + i);
                                *((arr + j*width) + i) = var;
                        }
                }
        }
}

void change(comp *arr, int width){
        for(int i=0; i<width; i++){
                (arr + i)->real = 5.0;
                (arr + i)->imag = 6.9;
        }
}

Error message:

    *** Error in `./a.out': double free or corruption (!prev): 0x0000000002095010 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7f05108a77e5]
/lib/x86_64-linux-gnu/libc.so.6(+0x8037a)[0x7f05108b037a]
/lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7f05108b453c]
./a.out[0x400880]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7f0510850830]
./a.out[0x400509]
======= Memory map: ========
00400000-00401000 r-xp 00000000 00:00 467935                     /path/to/a.out
00600000-00601000 r--p 00000000 00:00 467935                     /path/to/a.out
00601000-00602000 rw-p 00001000 00:00 467935                     /path/to/a.out
02095000-020b6000 rw-p 00000000 00:00 0                          [heap]
7f050c000000-7f050c021000 rw-p 00000000 00:00 0
7f050c021000-7f0510000000 ---p 00000000 00:00 0
7f0510610000-7f0510626000 r-xp 00000000 00:00 360788             /lib/x86_64-linux-gnu/libgcc_s.so.1
7f0510626000-7f0510825000 ---p 00000016 00:00 360788             /lib/x86_64-linux-gnu/libgcc_s.so.1
7f0510825000-7f0510826000 rw-p 00015000 00:00 360788             /lib/x86_64-linux-gnu/libgcc_s.so.1
7f0510830000-7f05109f0000 r-xp 00000000 00:00 360750             /lib/x86_64-linux-gnu/libc-2.23.so
7f05109f0000-7f05109f9000 ---p 001c0000 00:00 360750             /lib/x86_64-linux-gnu/libc-2.23.so
7f05109f9000-7f0510bf0000 ---p 000001c9 00:00 360750             /lib/x86_64-linux-gnu/libc-2.23.so
7f0510bf0000-7f0510bf4000 r--p 001c0000 00:00 360750             /lib/x86_64-linux-gnu/libc-2.23.so
7f0510bf4000-7f0510bf6000 rw-p 001c4000 00:00 360750             /lib/x86_64-linux-gnu/libc-2.23.so
7f0510bf6000-7f0510bfa000 rw-p 00000000 00:00 0
7f0510c00000-7f0510c25000 r-xp 00000000 00:00 360690             /lib/x86_64-linux-gnu/ld-2.23.so
7f0510c25000-7f0510c26000 r-xp 00025000 00:00 360690             /lib/x86_64-linux-gnu/ld-2.23.so
7f0510e25000-7f0510e26000 r--p 00025000 00:00 360690             /lib/x86_64-linux-gnu/ld-2.23.so
7f0510e26000-7f0510e27000 rw-p 00026000 00:00 360690             /lib/x86_64-linux-gnu/ld-2.23.so
7f0510e27000-7f0510e28000 rw-p 00000000 00:00 0
7f0510e30000-7f0510e31000 rw-p 00000000 00:00 0
7f0510e40000-7f0510e41000 rw-p 00000000 00:00 0
7f0510e50000-7f0510e51000 rw-p 00000000 00:00 0
7f0510e60000-7f0510e61000 rw-p 00000000 00:00 0
7fffc47c7000-7fffc4fc7000 rw-p 00000000 00:00 0                  [stack]
7fffc5242000-7fffc5243000 r-xp 00000000 00:00 0                  [vdso]

Aborted (core dumped)

I am compiling my code with GCC version 5.4.0. I don't understand the error message and don't know how to debug it. What should I do to free the array of pointers?


Solution

  • transpose blatantly accesses elements outside of the array bounds.

    fft in main is an array of pointers. Each pointer is initialized to point to a block of dynamically allocated memory (via calloc).

    It looks like this in memory:

                                       0            1                  63
    fft:  0 [ 0x0000a000 ] ----> [ real; imag | real; imag | ... | real; imag ]
          1 [ 0x0000ff08 ] ----> [ real; imag | real; imag | ... | real; imag ]
          .
          .
          .
         63 [ 0x0001041c ] ----> [ real; imag | real; imag | ... | real; imag ]
    

    fft has 64 elements, each of which is a pointer. In this example, fft[0] has the value 0x0000a000, at which address there is another 64-element array (created by calloc), which stores values of type comp (which is a 2-element struct).

    Thus *fft[0] is the first comp struct (at address 0x0000a000), *fft[1] is the second comp struct (at address 0x0000a010), *fft[2] is the third comp struct (at address 0x0000a020), etc. Each comp struct takes 16 bytes, so the addresses increase by 16 (0x10). The last element of fft[0], fft[0][63] lives at address 0x0000a3f0.

    fft[1] is the second pointer, pointing to a second (unrelated) block of memory (also created by calloc). In this example the instances of comp live at addresses 0x0000ff08, 0x0000ff18, 0x0000ff28, etc.

    The same thing happens for all elements of fft, up to fft[63]. In this example the comp instances fft[63][0], fft[63][1], fft[63][2], etc. live at addresses 0x0001041c, 0x0001042c, 0x0001043c, etc.

    Now consider what transpose does. It is called like this:

    transpose(&fft[0][0], len, len);
    

    It accesses memory like this:

    *((arr + (i*width)) + j)
    

    Here arr is the first parameter; its value is &fft[0][0], which is the same as fft[0], which in our example is 0x0000a000.

    width is 64. i and j are somewhere between 0 and 63, depending on which loop iteration we're in. Let's assume they're at 63.

    Then i*width is 63*64 is 4032, and arr + 4032 is a pointer to the 4033rd element of the array. But wait! There is no such element; arr only has 64 elements.

    We're now at memory address 0x00019c00, which is far outside the bounds of fft[0] (recall that its elements only go up to address 0x000a3f0).

    But we're not done yet: To this pointer we add j (63), giving 0x00019ff0. And this pointer is what we dereference with *.

    Had we written this operation using array notation, it would have looked like

    arr[i*width + j]
    

    which more obviously shows that we're accessing element 4095 of a 64-element array.

    transpose even writes to this address:

    *((arr + i*width) + j) = ...
    

    This modifies memory that your program doesn't own, thus corrupting the internal data structures used by malloc / calloc / free. That's what the error message double free or corruption means: Your code has corrupted data that was needed for free, which can happen by freeing the same pointer twice ("double free") or just writing to memory past the end of your arrays (as in your code).


    To fix your code, change transpose to

    void transpose(comp **arr, int height, int width) {
        for (int i = 0 ; i < height; i++) {
            for (int j=0; j < width; j++) {
                if (j > i) {
                    comp var = arr[i][j];
                    arr[i][j] = arr[j][i];
                    arr[j][i] = var;
                }
            }
        }
    }
    

    and call it as

    transpose(fft, len, len);
    

    This way you don't just pass the address of the first sub-array, but the address of the intermediate pointer array (which in turn lets you access any of the 64 sub-arrays).