精通c语言中的指针-以汇编的角度理解数组名

数组名最让人困惑的地方,是在使用多维数组名,做加减法取多维数组中数据的时候,当维数变多,或者出现指针数组的时候。

如果理解不深,那么很容易出现取数据错误的问题。

  结论是:在使用多维数组名的时候,最好把数组名当做指向第一维数据的地址(同样适用于一维数组)。  

      多维数组,在内存中是按照一维的数组方式存储中,

如图:

  我们定义局部变量int a,到时候编译器会在堆栈中给a这个变量分配一个空间,所以在程序中打印会发现a是2,&a是一个地址的值。

  但是当定义局部2维数组的时候,是没有给这个数组名b分配空间的,当你打印的时候,就会发现b和&b是一样的。而当探测这数组名b和&b的类型的时候,

  你会发现这两个类型不一样,&b是int (*)[3]的类型,b是int [3]的类型。程序员要明白一件事,CPU只认识0和1,只有程序员自己才关心使用的这个值是什么类型,对CPU来讲都是数字。

定义多维数组的时候,你会发现,多维数组在内存中是一样的,对编译器来讲,没有多维数组,只有一维数组。

    c的类型是int [2][4]

              c[0]等价于*c等价于*(c+0),是去掉一维以后的类型int [4]类型

    c+1(c+i)是指向第i维数组的指针,类型是int (*)[4],要理解图中的c[0]和c+1。

    因此会发现当使用数组名做加减法c+i和c[1]的时候,会发现这个时候数组名C就是当做指向第一维数据的地址。

              类型_q c[m][n] = *(*(c + m) + n) = c+m×(去掉c的一个*类型宽度) × 类型_q 的宽度+ n*(去掉c+m一个*类型的宽度) × 类型_q 的宽度

    那么把c当成是指向第一维数据的地址,那么c就是 类型_q  (*)[n]

    c[m][n] = c + m×n×类型_q 的宽度 + n×类型_q 的宽度 ,编译器取多维数组中的数据也是这样取的。

    关键是一维数组中的第一维如何理解?

    

 以上图为例子:

     内存中是以字节为单位存储数组的,我们把字节当成一个维度,因此把一维数组b当成是3维的多维数组,那么第一维就是4个字节类型(int)。

     那么以后理解数组名a,不管a(相当于a+0)是几维数组,都当成是指向第一维的地址就是通用的。

   

以一个实例来检验:

先定义一个结构体类型

 

struct Student{
  int a;
  char b;
}student;

//然后定义6个变量初始化,准备给2维数组赋值。

Student student1,student2,student3,student4,student5,student6;
student1.a = 1;
student1.b = 11;
student2.a = 2;
student2.b = 22;
student3.a = 3;
student3.b = 33;

student4.a = 4;
student4.b = 44;
student5.a = 5;
student5.b = 55;
student6.a = 6;
student6.b = 66;

//定义一个结构体数组

Student b[2][3]={student1,student2,student3,student4,student5,student6};

//定义一个指针数组

Student (*p)[2][3];    

p = &b;

printf("Student:%d\n",sizeof(Student));  //注意,由于对齐的原因,这里Student宽度是8,而不是5。
printf("p:%d,%d\n",p,p+1);//+ 48     p+1等于p+2×3×(Stuedent类型宽度) 
printf("*p:%d,%d\n",*p,*p+1);//+24    *p是Student [2][3]类型,用到我们前边讲的*p是Student  (*)[3],p+1等于p+3×(Stuedent类型宽度)
printf("p[0]:%d,%d\n",p[0],p[0]+1);//+24  p[0]=*(p+0)=*p,因此同上,还可以这样理解,p[i]或*(p+i)都是Student [2][3]类型,*(p+i)相当于数组名,相当于为多维数组去掉一维后的指针类型,理解为Student (*)[3]
printf("&p[0]:%d,%d\n",&p[0],&p[0]+1);//+48  p[0]是Student [2][3]类型,&p[0]就相当于指针数组Student (*)[2][3],因此&p[0]+1为p+2×3×(Stuedent类型宽度)
printf("&p[0][0]:%d,%d\n",&p[0][0],&(p[0][0])+1);//+24  &p[0][0],p[0]是Student [2][3]类型,p[0][0]是Student [3]类型,&p[0][0]是Student (*)[3]类型,&p[0][0]+1=p + 33×(Stuedent类型宽度)
printf("&p[1][2]:%d,%d\n",&p[1][2],&p[1][2]+1);//+24    p[1][2]是Student [3]类型,&p[1][2]是Student (*)[3]类型,&p[1][2]+1因为这里是大3×(Stuedent类型宽度)

printf("p:%d,%d\n",p,&p[1][2]+1);      //&*(*(p + 1) +2) + 1

                分步计算   p + 1 = p+2*3*8=p+48,这个时候p[1]也就是*(p + 1 )是Student [2][3]类型,相当于Student (*)[3]

                       *(p+1) + 2 = p+3*2*8+2*3*8 = p + 48 +48 = p + 96,p[1][2]也就是,*(*(p+1)+2)相当于Student  [3]

                      &*(*(p+1)+2)也就是&p[1][2]相当于Student  (*)[3]

                       &p[1][2]+1 相当于p + 96 + 1*3*8 = p + 120

  首先不要把&p[0][0]与&b[0][0]搞混了,两个完全不一样,(因为p=&b);可以把[]转换为*()进行理解

  一个知识点:a[x] = *(a+x),即[] =*(),这2个是等价的。

 学习指针要牢记:

        指针是一种带*的新的类型,这种类型和int、char、long、Student等类型一样,是完美的。

        不要把指针和地址必须联系在一起,指针和地址是没有关系的,地址只是一个内存编号,而指针是一种新类型,

        为了取出内存中我们想要的数据,我们可以定义各种带*的类型,char*、int*、char****、int****,short (*)[3][4]、int (*Function)(int x ,int y)

        指针这种类型就是为了随心所欲取出内存中的数据。

 

posted @ 2023-06-30 18:29  一日学一日功  阅读(56)  评论(0)    收藏  举报