龙空技术网

C|从栈帧空间分布、地址偏移来理解二维数组操作

小智雅汇 317

前言:

此刻姐妹们对“c二维数组传参”大约比较重视,大家都想要剖析一些“c二维数组传参”的相关内容。那么小编在网络上收集了一些对于“c二维数组传参””的相关内容,希望你们能喜欢,兄弟们快快来学习一下吧!

C编译器会为每个函数分配一个一定空间的栈帧用于存放相关数据。例如主函数main(),即使没有局部变量和参数,也会分配44h的保存局部变量的空间,如果有局部变量定义,则会根据局部变量的空间需求情况,增加局部变量空间。

由此,在函数内部定义一个局部变量后,可以以此局部变量为基准做地址偏移,来测试栈帧的空间使用。当然,这样操作肯定有溢出和非法操作的风险。

1 向高地址偏移(栈底)

#include <stdio.h>int main(){    int a;    int *p = &a;    for(int i=0;i<10;i++) // 向高地址偏移        *(p+i) = i+1;    for(int j=0;j<10;j++)        printf("%d ",*(p+j)); // 正确执行到这里    getchar();    return 0;  // 无法返回,因为ebp和ebp+4所在的返回地址都被覆盖}// 正确打印后,内存访问错误提示

栈帧空间:

2 向低地址偏移情形1

#include <stdio.h>int main(){    int a;    int *p = &a;    for(int i=0;i<10;i++) // 向低地址偏移        *(p-i) = i+1;     // 参考下面栈帧图,当*(p-1)时,修改的是p本身的值    for(int j=0;j<10;j++)        printf("%d ",*(p+j));     getchar();    return 0;  }// 当修改*(p-1)后,提示访问内存错误

栈帧空间:

3 向低地址偏移情形2

#include <stdio.h>#if 1int main()  // sub esp,54h,给main局部变量的空间{    int a;    int *p = &a;    int i;    for(i=3;i<26;i++) // 向低地址偏移,并越过三个局部变量:a,p,i        *(p-i) = i-2; // 正常压入全部值    for(i=3;i<26;i++)        printf("%d ",*(p-i)); // 当调用printf()時,会压入一个栈帧,替换掉ebp-54h更低的地址的值    getchar();    return 0;  } // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 20 4337692 4198539// 前面正确访问,后面访问了一些垃圾值

栈帧空间:

4 不使用二维数组声明和定义来模拟一下二维数组(不超过函数栈帧内存空间)

int main(){    int arr;    int *p = &arr;    int r = 3, c = 4;    int i,j;    const int n = 7; // 用于跨越此前的局部变量数(包括自身)    // 模拟a[3][4];    for(i=n;i<r+n;i++)         for(j=n;j<c+n;j++)            *(p-c*(i-n)-j-n) = (i-n+1)*(j-n+1);    for(i=n;i<r+n;i++)     {        for(j=n;j<c+n;j++)            printf("%d ",*(p-c*(i-n)-j-n));        printf("\n");    }    getchar();    return 0;}/*1 2 3 42 4 6 83 6 9 12*/

由上面可见,int*p指针的移动,是按sizeof(int)个字节的长度来移动的,这个计算由编译器完成。如果声明并定义二维数组,其空间的分配和数组元素的移动长度的计算,也是由编译器完成。

#include <stdio.h>int main(){    int a[3][4];    int (*p)[4] = a; // 处理数组,是处理数组的元素,此时数组名会转换为指向其首元素的指针    int i,j;    for(i=0;i<3;i++)         for(j=0;j<4;j++)            *(*(p+i)+j) = (i+1)*(j+1);    for(i=0;i<3;i++)     {        for(j=0;j<4;j++)            printf("%d ",*(*(p+i)+j));        printf("\n");    }    getchar();    return 0;}/*1 2 3 42 4 6 83 6 9 12*/

p+i 如何偏移或移动指针?p的类型是int[4],由编译器计算按sizeof(int[4])的字节长度来移动。操作a[3][4],操作的是a的元素(数组名在类似的上下文中会转换为指向数组首元素的指针),a[4],操作a[i],操作的是a[i][j]。

*(p+i)+j 如何偏移或移动指针?*(p+i)的类型是int,由编译器计算按sizeof(int)的字节长度来移动。

同样的,用函数来处理二维数组,传参不能传数组,但可以传一个指向数组元素的指针,函数不能返回数组,但可以返回一个指向数组元素的指针。对数组元素的偏移操作,就是其对第一维的处理,由第一维再延伸到其它维。

#include <stdio.h>#include <malloc.h>void arr2d(int(*p)[4],int n){    int i,j;    for(i=0;i<n;i++)         for(j=0;j<4;j++)            *(*(p+i)+j) = (i+1)*(j+1);    for(i=0;i<n;i++)     {        for(j=0;j<4;j++)            printf("%d ",*(*(p+i)+j));        printf("\n");    }}int(*arr2d2(int n))[4]{    int(*p)[4] = (int(*)[4])malloc(sizeof(int)*4*n);    return p;}int main(){    int a[3][4];    arr2d(a,3);    int (*p)[4] = arr2d2(3);    arr2d(p,3);    free(p);    getchar();    return 0;}/*1 2 3 42 4 6 83 6 9 121 2 3 42 4 6 83 6 9 12*/

-End-

标签: #c二维数组传参