繁体   English   中英

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

[英]Heap Buffer Overflow from a 2d array

因此,我是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..... 

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

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

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

使用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;

注意:如何使用rows1columns2的边界来rows1 answer的大小,而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)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM