void f(int **a) { cout << a[0][0] << endl; cout << a[1][0] << endl; } int main() { { int t1[2]; int tt[2][2]; tt[0][0] = 1; tt[0][1] = 2; tt[1][0] = 3; tt[1][1] = 4; int **ttp = new int*[2]; for(int i = 0; i < 2; ++i) { ttp[i] = new int[2]; for(int j = 0; j < 2; ++j) { ttp[i][j] = tt[i][j]; } } // 此处强转,在f中访问[0][0]元素时会出错。 // 传入ttp则是好的。 // 错误提示:test.exe 中的 0x00ee17bd 处有未经处理的异常: 0xC0000005: 读取位置 0x00000001 时发生访问冲突 f((int**)tt); } return 0;
楼主是初学C语言吗?如果是初学者能够触探到这样的问题我认为是大有前途的。
都说C语言最难理解的是指针,而指向指针的指针和二维数组又是最容易搞混的地方,因为二者都通过在前面加两个*来取值。但二者是完全不同的类型,运算法则完全不一样。
楼主做强制转换虽然骗过了编译器,但接下来执行f里面代码的时候,a[0][0]是按照*(*(a+0))的方式展开执行的,注意a的类型是指向指针的指针而不是二维数组名,所以括号里面的*(a+0)的执行结果就是1,接下来再执行*1,这个时候把1当作一个地址,试图把这个地址里面的数据取出来,现在可以明白了,如果不报错,取到的并非你预期的数据,是一个不可预料的值。之所以报错是因为运行环境检测到1这个地址不是本程序有权访问的(是操作系统的保留内存),所以就报错了,这里其实就形成了一个野指针。
而假如a的类型是二维数组名,a[0][0]也是按照*(*(a+0))的方式执行的,但此时应注意a是一个行指针,指向二维数据的第一行,a+0=a还是指向二维数据的第一行(a+1就表示指向第二行的行指针),*(a+0)只不过是把行指针转成了列指针(注意a+0和*(a+0)的值是相同的,只不过类型不同,你可以打印出来看下),接下来再执行最外层的*就是取第一行第一列的值了。
由此可见指向指针的指针和二维数组名取值的运算法则是完全不一样的,请楼主仔细体会。
另外要补充说明的是,二维数组从物理上来说也是一段连续的内存空间,而不应把它想象成
装着多个一维数组的数组。
THX,
理解了好久,终于有点思路了.
调用函数void f(int **a)时, 形参实际上只接收了tt值,即tt数组的首地址...
此时, a[0][0]相当于取*(*(a+0)),*(a+0)已经取到了指定地址的内容,再来一发的话,是取以1为地址的内容了.所以运行时出错了...
执行打印
------------------------------------------
void main(): tt+0: 007AFD8C *(tt+0): 007AFD8C tt+1: 007AFD94 *(tt+1): 007AFD94 ttp+0: 009D4A20 *(ttp+0): 009D4A20 ttp+1: 009D4A24 *(ttp+1): 009D4A24
用一维数组解 -------------------------------------------------
void f(int *a): (a+0): 007AFD8C (a+1): 007AFD90 (a+2): 007AFD94 (a+3): 007AFD98 (*(a+0)): 10 (*(a+1)): 20 (*(a+2)): 30 (*(a+3)): 40
本质上还是一个一维数组
@leungcnblogs:
修改了tt的赋值部分 tt[0][0] = 10; tt[0][1] = 20; tt[1][0] = 30; tt[1][1] = 40;
嗯,不错,加油!
并不是f[0][0],而是f[0]