之前帮同学调一个程序的时候遇到的,把二维数据改为全局变量,不通过参数传递就没问题了,否则程序崩溃。
细究一下,二维数据名用于形参时需要注意哪些方面。
测试程序如下:
#include<stdio.h> #include<stdlib.h> void print1(int **a, int m, int n); void print2(int (*a)[2], int m, int n); int main() { int a[2][2]={1,2,3,4}; print1((int**)a,2,2); print2(a,2,2); system("pause"); return 0; } void print1(int **a, int m, int n) { for(int i=0;i<m;++i) for(int j=0;j<n;++j) { printf("%d\n",*(a+i*n+j)); //printf("%d\n",a[i][j]); } } void print2(int (*a)[2], int m, int n) { for(int i=0;i<m;++i) for(int j=0;j<n;++j) { printf("%d\n",a[i][j]); } }
程序可以正常运行,而且没问题。有问题的是print1中注释掉的语句。可能很多人会觉得二维数组的数组名不就是一个二维指针吗,为什么用a[i][j]的方式访问不行?注释掉的语句在执行时,程序崩溃。
原因是这样的,二维数组的数组名其实是一个int (*)[N]的指针,和 int** 指针还是不一样的。直观来看,对前者一次解引用的话,得到的是数组;对后者一次解引用,得到的是指针。其实在调用的时候就可以发现,如果你不进行强制类型转换,而直接进行这样的调用 print1(a,2,2); 会提示如下错误:cannot convert `int (*)[2]‘ to `int**‘ for argument `1‘ to `void print1(int**, int, int)‘ 。编译器已经告诉你,类型不匹配了;这个时候你应该想到的是用第二种方式来定义函数。而有的人可能觉得既然类型不匹配,就给直接强制类型转换了,结果编译通过了,但程序崩溃了,更找不出问题。
产生这个问题的根本原因是,对数组名和指针的细微差别没有认识到。
数组名,在访问元素时,是直接取址的过程。比如a[i],它是直接在a的地址上进行一个 i (乘以a[i]的类型大小)的偏移。(每个符号的地址在编译时可知)
指针,访问元素时,是一个间接寻址的过程。比如对int *a; 在a[i]取元素时,首先它会在变量a的地址上取数,取得的值作为地址再去寻址,然后再进行一个 i (乘以类型大小)的偏移。
这样的话,对上面测试代码而言,在main函数中,你声明的a是数组名,即int (*)[2]类型,传递到print2中,在print2函数中用a[i][j]访问时,是先对形参a进行一个间接寻址,然后直接寻址。而传递到print1中,首先你需要强制转换成int ** 才可以,在print1中用a[i][j]访问时,是对形参a进行间接寻址后,再间接寻址,这样就发生了错误。而在print1中用*(a+i*n+j)的方式则是可以的。
这个和下面这类错误很类似。就是在一个文件中定义了int a[100]; 然后你想在另一文件中使用它,你用了外部声明,但是你声明成了这样: extern int * a; 这样,在访问a数组的元素时,本来应该是按照数组名的直接寻址方式,在这个文件中却会用指针的间接寻址,就会出现不可预知的错误。
也有参考这里。
关于指针和数组名在什么情况下不同,以后有时间再总结下。