二维数组中的堆缓冲区溢出

时间:2019-02-21 22:14:57

标签: c arrays memory

所以我是C编程和分配内存的新手。所以我写了一个做矩阵乘法的程序。我已经为矩阵1的2d数组中的1d数组分配了内存,并且与矩阵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表示矩阵1的3行和3列。因此,从文件中读取它时,它将存储在row1和column1中。接下来,将在第一个矩阵中包含1-9。 3和3将是矩阵2的3行和矩阵2的3列。因此,它将存储在row2和column2中。所有这些数字用制表符分隔。上面的文件是我测试过的许多文件之一,它使我的堆缓冲区溢出。

#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 :(得分:0)

“ heap-buffer-overflow”意味着您创建了一定大小的缓冲区,但是试图访问该缓冲区之外的内容。通常,这意味着要么您的循环使用了错误的上限值,要么您的缓冲区之一实际上并不是您认为的大小。

很难确定这里发生了什么。复制/粘贴到我的gcc中的代码似乎按预期工作(尽管我目前无法访问AddressSanitizer)。我注意到的关于代码的第一件事是,它使用从输入文件读取的值来缓存大小和循环边界,而没有进行任何类型的检查。我的建议是在调试器中逐步执行此代码,并确保从磁盘读取的值和计算出的缓冲区大小均符合您的期望。所有这些操作都是为了让这些scanf()调用之一遇到意外情况,返回零,并丢弃所有计算。

此外,如果包含编译器错误消息的整个输出(不要忘记在调试模式下进行编译),这可能会很有用。 AddressSanitizer输出通常包含一个堆栈跟踪,可以将您指向发生问题的行号。同样有用的是编译器的名称和版本号,以及您正在使用的任何命令行选项。

答案 1 :(得分:0)

使用malloc

首先,您的代码很好,但这并不意味着它不包含问题。首先,让我们看看您对malloc的使用,例如

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

无需强制返回malloc,这是不必要的。请参阅:Do I cast the result of 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;

注意:如何使用answerrows1的边界来确定columns2的大小,而使用square来分配rows1, columns1square2rows2, columns2。您的编译器可以通过跟踪用于确定分配大小的变量来帮助您发现潜在的堆溢出。一些编译器在这方面比其他编译器要好。

如果编译器无法确定要用于迭代数组的限制,则它会引发有关潜在缓冲区溢出的警告。 (它所关心的只是所使用的限制的值,但是就像我说的,某些编译器要好于其他编译器...)

使用上面列出的限制进行分配后,然后您可以遍历具有不同限制的指针数组,这些指针被读入单独且不相关的变量。使用rows1, columns2遍历square, square2 & answer。考虑一下,虽然您知道columns1 == columns2,但编译器无法保证。与rows2 == rows1相同。

您的编译器无法保证将rows1square2一起使用不会超出分配的大小。同样,不能保证使用columns2不会违反square的范围。您对columns1 != rows2的测试不能为rows1 == columns2rows1 == 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)