网上搜到的很多结果反馈是数组越界导致,而如果数组真的越界,也确实会报这个错,这很正常。因为leetcode使用了AddressSanitizer。
关于Sanitizer,我也只是网上找了一些文章稍微了解了一下其功能。记录一些自己找的关于Sanitizer的资料连接。
github上的sanitizers项目,读项目的原始文档应该最容易理解项目
Sanitizers简介
这个问题是在做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去执行代码,就报错。报错如下。
最开始我还以为是代码写的不严谨导致有些地方数组越界了。但是代码调试了很久,将执行过程中所有数组下标都打印出来看,没有发现有数组越界。所以哪怕是代码有问题,也不是数组越界导致。那到底是什么问题呢?
这里我就开始分析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