Почему после добавления вывода строки ошибка исчезла?

Моя программа создает матрицу (размер из аргументов командной строки), заполняет ее и печатает:

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

int **matrix_maker(int lines, int colons)
/*Функция создает матрицу. Внутри функции проблема*/
{
        int **tmp = NULL;
/*Без следующей ЧУДОСТРОКИ программа отказывается работать*/
        printf("%d\n", 1);

        tmp = (int**)malloc(lines*sizeof(int*) + lines*colons*sizeof(int));
        for(int i = 0; i < lines; ++i){
                *(tmp+i) = (int*)(tmp+colons*(i+1));
        }
        return tmp;
}

int str_to_unsigned_int(const char *str)
/*Функция превращает строку в число (для аргументов командной строки)*/
{
        int num = 0;
        for(; *str; ++str){
                if( (*str)<'0' || (*str)>'9' ){
                        fprintf(stderr, "Not a number\n");
                        return -1;
                }
                num = num*10 + (*str)-'0';
        }
        return num;
}

void matrix_filler(int **matrix, int lines, int colons)
/*Функция заполняет матрицу (Чем именно - не важно, программа учебная)*/
{
        for(int i = 0; i < lines; i++)
                for(int j = 0; j < colons; j++){
                        matrix[i][j] = (lines*i+colons*j+i*j)/lines+colons;
                }
}

void matrix_printer(int **matrix, int lines, int colons)
/*Функция печатает матрицу*/
{
        for(int i = 0; i < lines; i++){
                for(int j = 0; j < colons; j++){
                        printf("%d ", matrix[i][j]);
                }
                printf("\n");
        }
}


int main(int argc, const char **argv)
{
        if(argc < 3){
                fprintf(stderr, "To few arguments\n");
                return 1;
        }

        int lines;
        int colons;
        lines = str_to_unsigned_int(*(argv+1));
        colons = str_to_unsigned_int(*(argv+2));
        if(lines == -1 || colons == -1){
                return 2;
        }

        int **matrix = NULL;

        matrix = matrix_maker(lines, colons);
        matrix_filler(matrix, lines, colons);
        matrix_printer(matrix, lines, colons);

        return 0;

}

В этой программе есть функция matrix_maker, которая собственно создает матрицу int-ов размером lines на colons. Чтобы не вызывать функцию malloc много раз (lines+1, если быть точным), я резервирую память сразу под матрицу нужного размера один раз, а потом просто заполняю первые lines ячеек указателями на строки матрицы.

А тепер вопрос: Почему без ЧУДОСТРОКИ вывод моей функции (в терминале при запуске программы):

malloc(): corrupted top size
Aborted (core dumped)

Но если я просто вставлю мою отладочною ЧУДОСТРОКУ, то функция выводит в терминале:

1 /*Это та единица, которую печатает ЧУДОСТРОКА*/
12 13 14 15 16 17 18 19 20 21 22 23 
13 14 15 16 17 18 19 20 21 22 23 24 
14 15 16 17 18 19 21 22 23 24 25 26 
15 16 17 18 20 21 22 23 25 26 27 28 
16 17 18 20 21 22 24 25 26 28 29 30 
17 18 19 21 22 24 25 26 28 29 31 32 
18 19 21 22 24 25 27 28 30 31 33 34 
19 20 22 23 25 26 28 30 31 33 34 36 
20 21 23 25 26 28 30 31 33 35 36 38 
21 22 24 26 28 29 31 33 35 36 38 40 
22 23 25 27 29 31 33 34 36 38 40 42 
23 24 26 28 30 32 34 36 38 40 42 44 

Как так, и чего я не понимаю?


Ответы (2 шт):

Автор решения: Harry

Смотрим сюда:

tmp = (int**)malloc(lines*sizeof(int*) + lines*colons*sizeof(int));

Т.е. выделено вначале место под lines указателей, а потом

for(int i = 0; i < lines; ++i){
        *(tmp+i) = (int*)(tmp+colons*(i+1));
}

вы их расставляете... Но давайте посмотрим, куда смотрит первый же указатель:

*tmp = (int*)(tmp + colons);

А должен? Как минимум на место после lines указателей, т.е.

*tmp = (int*)(tmp + lines);

Вы просто неверно написали работу с памятью...

Посмотрите вот этот ответ, часть, начинающуюся со слов

Еще один вариант - выделение одним большим куском, чтобы и освобождать один раз. При этом вначале хранятся указатели на строки, а затем - данные.

Видите отличие моего кода от вашего?...

Вот так будет правильнее:

int ** tmp = malloc(lines*(sizeof(int*)+colons*sizeof(int)));
for(int i = 0, ofs = lines*sizeof(int*); i < lines; i++)
    tmp[i] = (int*)((char*)tmp + ofs + i*colons*sizeof(int));
→ Ссылка
Автор решения: user7860670

Задумка понятна - выделить один буфер для lines указателей, за которым идут lines x colons значений, затем дать указателям указывать на соотв. строку. Смещение нулевого указателя в буфере должно быть sizeof(int *) * lines байт, первого sizeof(int *) * lines + sizeof(int) * colons, второго - sizeof(int *) * lines + sizeof(int) * colons * 2.

Рассмотрим, что происходит на строке *(tmp+i) = (int*)(tmp+colons*(i+1));. Смещение нулевого указателя будет sizeof(int *) * colons * 1 байт, первого - sizeof(int *) * colons * 2 байт, второго - sizeof(int *) * colons * 3 байт. Как видно, значения указателей начинают разезжаться с самого начала, а потом и вовсе выходят за пределы буфера (подразумевая 64-битный указатель), что при обращении к ним выливается в чтение за пределами буфера, что является Неопределенным Поведением.

→ Ссылка