Переполнение буфера кучи из 2d массива - PullRequest
0 голосов
/ 22 февраля 2019

так что я новичок в программировании на C и распределении памяти.Итак, я написал программу, которая выполняет умножение матриц.Я выделил память для массива 1d в массиве 2d для матрицы 1 и то же самое с матрицей 2. Ниже приведен мой код, и я не понимаю, почему я получаю переполнение буфера динамической памяти.Вход содержит файл, который содержит размеры и компоненты обеих матриц.Пример формата файла может содержать следующий формат

    3       3
    1       2       3
    4       5       6
    7       8       9
    3       3
    1       2       3
    4       5       6
    7       8       9 

Первая строка 3 и 3 будет означать 3 строки и 3 столбца матрицы 1. Следовательно, при чтении этого файла он будет храниться в строках 1 и столбцах 1,Далее 1-9 будет содержаться в первой матрице.3 и 3 будут 3 строки матрицы 2 и 3 столбца матрицы 2. Следовательно, он будет храниться в строках 2 и столбцах 2.Все эти цифры разделены вкладками.Приведенный выше файл был одним из многих, которые я тестировал, и он переполнен буфером кучи

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
void print(int** square, int rows,int columns);

int main(int argc, char** argv) {
    FILE *fp = fopen(argv[1], "r");
    if (fp == NULL) {
        printf("error\n");
        return 0;
    }
    int rows1 = 0; int columns1 = 0; int num = 0;
    fscanf(fp, "%d", &rows1);
    fscanf(fp, "%d", &columns1);

    int** square = (int**) malloc(sizeof(int*) * rows1);
    for (int i = 0; i < rows1; i++) {
        square[i] = (int*) malloc(sizeof(int) * columns1);
    }
    for (int i = 0; i < rows1; i++) {
        for (int j = 0; j < columns1; j++) {
            fscanf(fp, "%d", &num);
            square[i][j] = num;
        }
    }
    int rows2 = 0; int columns2; int num2 = 0;
    fscanf(fp, "%d", &rows2);
    fscanf(fp, "%d", &columns2);

    int** square2 = (int**) malloc(sizeof(int*) * rows2);
    for (int i = 0; i < rows2; i++) {
        square2[i] = (int*) malloc(sizeof(int) * columns2);
    }
    for (int i = 0; i < rows2; i++) {
        for (int j = 0; j < columns2; j++) {
            fscanf(fp, "%d", &num2);
            square2[i][j] = num2;
        }
    }
    if (columns1 != rows2) {
        printf("bad-matrices\n");
        return 0;
    }
    int ans = 0;
    int** answer = (int**) malloc(sizeof(int*) * rows1);
    for (int i = 0; i < rows1; i++) {
        answer[i] = (int*) malloc(sizeof(int) * columns2);
    }
    for (int i = 0; i < rows1; i++) {
        for (int j = 0; j < columns2; j++) {
            for (int k = 0; k < rows2; k++) {
                ans += square[i][k] * square2[k][j];
            }
            answer[i][j] = ans;
            ans = 0; 
        }
    }
    print(answer, rows1, columns2);
    fclose(fp);
    return 0;
}

void print(int** square, int rows, int columns) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < columns; j++) {
            printf("%d\t", square[i][j]);
        }
        printf("\n");
    }
    return;
}

Результат:

==31599== ERROR: AddressSanitizer: heap-buffer-overflow on address..... 

Ответы [ 2 ]

0 голосов
/ 22 февраля 2019

Использование malloc

Во-первых, ваш код в порядке, но это не значит, что он не содержит проблем.Во-первых, давайте посмотрим, как вы используете malloc, например,

int** answer = (int**) malloc(sizeof(int*) * rows1);

Нет необходимости разыгрывать возврат malloc, это не нужно.Смотрите: Я разыгрываю результат malloc? .Далее, и это больше стиля, чем что-либо еще, '*'s, показывающий уровни косвенности, идет с переменной, а не с типом.Почему?

int* a, b, c;

Это, конечно, не объявляет 3-указатели на int.Он объявляет один указатель и два целых числа, например

int *a, b, c;

При установке размера шрифта для выделения, если вы всегда используете сам указатель с разыменованным указателем, вы никогда не получите неправильный размер шрифта, например

int **answer = malloc (rows1 * sizeof *answer);

Если вы выделите его, вы должны его подтвердить, и вам решать free это

Для каждого выделения вы должны проверить, что указатель возвращенна malloc, calloc, realloc не NULL.Функции распределения не работают, когда у вас заканчивается память.Всегда проверяйте.

В любом написанном вами коде, который динамически распределяет память, у вас есть 2 обязанности относительно любого выделенного блока памяти: (1) всегда сохраняйте указатель на начальный адрес для блока памяти, таким образом, (2) он может быть освобожден , когда он больше не нужен.

Крайне важно, чтобы вы использовали программу проверки ошибок памяти, чтобы убедиться, что вы не пытаетесьполучить доступ к памяти или выполнить запись за пределами / за пределами выделенного блока, попытаться прочитать или основать условный переход на неинициализированном значении и, наконец, подтвердить, что вы освобождаете всю выделенную память.

Для Linux valgrind - нормальный выбор.Для каждой платформы есть похожие проверки памяти.Все они просты в использовании, просто запустите вашу программу через нее.

Всегда подтверждайте, что вы освободили всю выделенную память и что ошибок памяти нет.

Просто объявите функцию дляосвободите ваши массивы указателей и передайте каждому из них функцию free вместе с количеством строк до выхода из вашей программы, например,

void freearr (int **a, int rows)
{
    for (int i = 0; i < rows; i++)
        free (a[i]);
    free (a);
}

и

...
fclose(fp);

freearr (square, rows1);
freearr (square2, rows2);
freearr (answer, rows1);

return 0;

Почему я получаю: ОШИБКА: AddressSanitizer: переполнение динамического буфера на адресе .....?

Это больше результат того, что ваш компилятор сказал вам перепроверить использование границ массива.В частности, здесь, скорее всего, это результат:

int answer = malloc (rows1 * sizeof *asnwer);
for (int i = 0; i < rows1; i++)
    answer[i] = malloc (columns2 * sizeof *answer[i]);

for (int i = 0; i < rows1; i++) {
    for (int j = 0; j < columns2; j++) {
        for (int k = 0; k < rows2; k++) {
            ans += square[i][k] * square2[k][j];
        }
        answer[i][j] = ans;

Примечание: как answer измеряется с использованием границ rows1 и columns2, тогда как square выделяется с использованиемrows1, columns1 и square2 с rows2, columns2.Ваш компилятор может помочь вам определить потенциальное переполнение кучи, отслеживая переменные, используемые для определения размера выделения.В этом некоторые компиляторы лучше других.

Если компилятор не может определить, какие ограничения вы используете для итерации по вашему массиву, он может выдать предупреждение о потенциальном переполнении буфера.(все, что должно волновать, это значение используемых пределов, но, как я сказал, некоторые компиляторы лучше, чем другие ...)

После выделения с ограничениями, изложенными выше, вы затем переходите к итерациимассивы указателей с различными ограничениями, которые были прочитаны в отдельные и не связанные переменные.Использование rows1, columns2 для итерации по square, square2 & answer.Подумайте об этом, хотя вы знаете columns1 == columns2, тогда компилятор не имеет на это гарантии.То же самое для rows2 == rows1.

Ваш компилятор не гарантирует, что использование rows1 с square2 не запишет за пределы его выделенного размера.Также нет гарантии, что использование columns2 не нарушит границы square.Ваш тест columns1 != rows2 не дает никаких гарантий для rows1 == columns2 или rows1 == rows2 и т. Д. *

Итак, все используемые ограничения белого цвета в порядке - ваш компилятор не может гарантировать это и предупреждает.Однако, поскольку вы утомительно выбрали свой код, чтобы знать, что ваши ограничения хороши, все, что требуется, - это доля секунды, чтобы подтвердить его, например,

 $ valgrind ./bin/read2darrq dat/arr_2-3x3.txt
==29210== Memcheck, a memory error detector
==29210== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==29210== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==29210== Command: ./bin/read2darrq dat/arr_2-3x3.txt
==29210==
90      96      102
216     231     246
342     366     390
==29210==
==29210== HEAP SUMMARY:
==29210==     in use at exit: 0 bytes in 0 blocks
==29210==   total heap usage: 13 allocs, 13 frees, 732 bytes allocated
==29210==
==29210== All heap blocks were freed -- no leaks are possible
==29210==
==29210== For counts of detected and suppressed errors, rerun with: -v
==29210== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
0 голосов
/ 22 февраля 2019

«heap-buffer-overflow» означает, что вы создали буфер определенного размера, но пытались получить доступ за пределами буфера.Обычно это означает, что либо у вас есть цикл, который использует неправильное значение для верхней границы, либо один из ваших буферов на самом деле не соответствует размеру, который вы считаете.

Трудно точно сказать, что происходитЗдесь.Копирование / вставка кода в мой gcc, кажется, работает должным образом (сейчас у меня нет доступа к AddressSanitizer).Первое, что я заметил в вашем коде, это то, что он использует значения, считанные из входного файла как для размеров буфера, так и для границ цикла без какой-либо проверки работоспособности.Я рекомендую просмотреть этот код в вашем отладчике и убедиться, что значения, которые считываются с диска, и вычисленные размеры буфера соответствуют ожидаемым.Все, что требуется для того, чтобы один из этих scanf() вызовов встретил что-то неожиданное, вернул ноль и сбросил все ваши вычисления.

Кроме того, это может быть полезно, если вы включите весь вывод ошибки компиляторасообщение (не забудьте скомпилировать в режиме отладки).Вывод AddressSanitizer обычно включает в себя трассировку стека, которая может указывать на номер строки, в которой возникла проблема.Также полезно будет указать имя и номер версии вашего компилятора, а также любые параметры командной строки, которые вы используете.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...