[C语言]leetcode坑爹的二维数组问题导致heap-buffer-overflow记录

导致heap-buffer-overflow的原因

1. 数组越界

网上搜到的很多结果反馈是数组越界导致,而如果数组真的越界,也确实会报这个错,这很正常。因为leetcode使用了AddressSanitizer。

关于Sanitizer,我也只是网上找了一些文章稍微了解了一下其功能。记录一些自己找的关于Sanitizer的资料连接。
github上的sanitizers项目,读项目的原始文档应该最容易理解项目
Sanitizers简介

2. 二维数组的使用导致的问题

这个问题是在做leetcode上的这个题出现的:
1139. 最大的以 1 为边界的正方形

给你一个由若干 0 和 1 组成的二维网格 grid,请你找出边界全部由 1 组成的最大 正方形 子网格,
并返回该子网格中的元素数量。如果不存在,则返回 0。

示例 1:
输入:grid = [[1,1,1],[1,0,1],[1,1,1]]
输出:9

示例 2:
输入:grid = [[1,1,0,0]]
输出:1

提示:
1 <= grid.length <= 100
1 <= grid[0].length <= 100
grid[i][j] 为 0 或 1

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/largest-1-bordered-square
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

提供的函数原型长这样

/* 参数说明:假设传过来的是一个arr[5][4]的数组
 * @grid: 二维数组的地址。我觉得用C语言里面准确的描述应该是:指向二维数组的指针
 * @gridSize: 二维数组的行大小,按照上面的例子,传过来的就是5
 * @gridColSize:指向二维数组的列大小的指针,至于为什么要传指针我也不清楚,反正 *gridColSize = 4.
 */
int largest1BorderedSquare(int** grid, int gridSize, int* gridColSize);

正常来说,C语言函数调用的时候传递的无论是几维度数组的地址,都会退化成指针,也就是说,下面代码中调用largest1BorderedSquare函数传递过去的arr,对于largest1BorderedSquare函数来说,arr就是个指针,至于到底是个指向几维数组的指针是不知道的。

int main(void)
{
	//int arr[5][4] = {{0,1,0,0},{1,1,1,1}, {1,0,1,1}, {1,1,1,1}, {1,1,1,1}};
	int arr[][3] = {{1,1,1}, {1,0,1}, {1,1,1}};
	int ret;
	int col = sizeof(arr[0]) / sizeof(arr[0][0]);
	ret = largest1BorderedSquare(arr, sizeof(arr)/sizeof(arr[0]), &col);
	printf("ret = %d\n",ret);
	return 0;
}

所以如果想在largest1BorderedSquare中用访问数组的方式(arr[x][y])来访问数组数据,需要将传过来的指针做转换:int (*g)[*gridColSize] = (void *)grid;// 后面代码可以用g[x][y]的方式访问数据

int largest1BorderedSquare(int** grid, int gridSize, int* gridColSize){
	int (*g)[*gridColSize] = (void *)grid;// 后面代码可以用g[x][y]的方式访问数据
	...
}

因为在leetcode上不好调试代码,所以我是在虚拟机的linux系统上调试代码,调好了在上传leetcode,所以写的代码全部都是用的上面那种指针做了转换的方式用,在linux系统上执行完全没问题。

但是!但是!但是!
代码上传leetcode去执行代码,就报错。报错如下。

无语的debug过程

1. 调试代码,无果

最开始我还以为是代码写的不严谨导致有些地方数组越界了。但是代码调试了很久,将执行过程中所有数组下标都打印出来看,没有发现有数组越界。所以哪怕是代码有问题,也不是数组越界导致。那到底是什么问题呢?

2. 分析报错信息

这里我就开始分析leetcode的报错信息了。
ps:我的测试用例是int arr[][3] = {{1,1,1}, {1,0,1}, {1,1,1}};这个对于分析问题有用

=================================================================
==42==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x603000000028 at pc 0x557d8183caee bp 0x7ffd27656310 sp 0x7ffd27656300
READ of size 4 at 0x603000000028 thread T0
    #4 0x7fd34c4a4082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082)
0x603000000028 is located 0 bytes to the right of 24-byte region [0x603000000010,0x603000000028)
allocated by thread T0 here:
    #0 0x7fd34d0ec808 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cc:144
    #3 0x7fd34c4a4082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082)
Shadow bytes around the buggy address:
  0x0c067fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c067fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c067fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  ...

上面的报错信息意思是:从地址0x603000000028处读取4字节导致堆溢出,应该就是数组越界。
下面有这样一句

0x603000000028 is located 0 bytes to the right of 24-byte region [0x603000000010,0x603000000028)

我的理解是,有一片24字节的内存[0x603000000010,0x603000000028),所以从地址0x603000000028处读取4字节超出的这片内存,导致溢出。
报错说读取4字节,加上代码中基本都是用的是int类型,所以猜测那一片24字节的内存里面应该是存的int类型,那么就是存了6个int。
6个int??这是什么东西?传来的是个int的3X3的二维数组,如果一片内存存3个int就是数组的一行,9个int就是整个二维数组,6个是个什么东西?
访问的是第7个int数据时候报错。
不管了,先假设就是传过来的完整二维数组,访问一下第7个数据试试。
屏蔽所有逻辑代码,增加如下代码测试:

int tmp = g[2][0];  // 读第7个数据试试

结果: 没报错!它没报错!
难道猜错了?没道理啊,再试试。
测试代码修改成:

int tmp = g[2][0];  // 读第7个数据试试
if(tmp){
    printf("tmp = %d\n", tmp);
}
else{
    printf("tmp = %d\n", tmp);
}

然后就: 报错了!它报错了!
喵了个咪的:编译器优化是吧,只是将数据读取到tmp,但是后面tmp完全不用,编译器就给我优化掉了是吧。加了个打印,要用到tmp里面的数据,才真的会执行int tmp = g[2][0];是吧。我TM…


至此可以确定问题在g[2][0]这里了。


但是,3X3的二维数组,我tm访问g[2][0],越的哪门子界?3X3的int给6个int的内存大小又是什么个意思?

没有思路,我就随便试试用函数里面传来的原本的参数int** grid来访问试试。
测试代码改成:

printf("grid[2][0] = %d\n", grid[2][0]);

结果: 没报错!它没报错!我TM…


然后我去掉所有指针转换的地方,直接用grid,然后就: 通过了…

结论

leetcode里面的多维数组,通过函数传递过来的,不用转换,直接用。

为什么能直接用

至于为什么。看看下面测试代码。
传过来的grid根本就不是二维数组的首地址,而是一个int * arr[line]的一维数组的地址。
更准确点,假如是个arr[5][3],那么传过来的是int (* arr[5])[3] 的首地址。。。

我TM!!!!!!!!!!!!!

言尽于此。
相信懂的看下面测试结果就能懂了。
不懂的,emmm,应该是数组、指针和多级指针还不熟吧。

// 测试代码:
{
    printf("sizeof(int) = %#x\n", sizeof(int));
    printf("sizeof(int *) = %#x\n", sizeof(int *));
    printf("sizeof(int **) = %#x\n", sizeof(int **));    
    printf("&grid[0][0] = %#x, grid[0] = %#x, &grid[0] = %#x\n", &grid[0][0], grid[0], &grid[0]);
    printf("&grid[0][1] = %#x\n", &grid[0][1]);
    printf("&grid[0][2] = %#x\n", &grid[0][2]);
    printf("&grid[1][0] = %#x, grid[1] = %#x, &grid[1] = %#x\n", &grid[1][0], grid[1], &grid[1]);
    printf("&grid[2][0] = %#x, grid[2] = %#x, &grid[2] = %#x\n", &grid[2][0], grid[2], &grid[2]);
}
// 测试结果:
sizeof(int) = 0x4
sizeof(int *) = 0x8
sizeof(int **) = 0x8
&grid[0][0] = 0x30, grid[0] = 0x30, &grid[0] = 0x10
&grid[0][1] = 0x34
&grid[0][2] = 0x38
&grid[1][0] = 0x50, grid[1] = 0x50, &grid[1] = 0x18
&grid[2][0] = 0x70, grid[2] = 0x70, &grid[2] = 0x20

你可能感兴趣的:(leetcode,c语言,算法)