数组和指针是如何访问的
首先需要注意的是“地址y”和“地址y的内容”之间的区别。
X = Y;
在这个上下文环境里
符号X的含义是X所代表的地址,这里被称为左值,左值在编译时可知,左值表示存储结果的地方
符号Y的含义是Y所代表的地址的内容,这里被称为右值,右值直到运行时才知,如无特别说明,右值表示“Y的内容”
C语言引入了“可修改的左值”这个术语。它表示左值允许出现在赋值语句的左边。这个奇怪的术语是为与数组名区分,数组名也用于确定对象在内存中的位置,也是左值,但它不能作为赋值的对象。因此,数组名是个左值但不是可修改的左值。标准规定赋值符必须用可修改的左值作为左边一侧的操作数。用通俗的话说,只能给可以修改的东西赋值。
编译器为每个变量分配一个地址(左值)。这个地址在编译时可知,而且该变量在运行时一直保存于这个地址。相反,存储于变量中的值(它的右值)只有在运行时才可知。如果需要用到变量中存储的值,编译器就发出指令从指定地址读入变量值并将它存入寄存器中。
这里的关键之处在于每个符号的地址在编译时可知。所以,如果编译器需要一个地址(可能还需要加上偏移量)来执行某种操作,它就可以直接进行操作,并不需要增加指令首先取得具体的地址。相反,对于指针,必须首先在运行时取得它的当前值,然后才能对它进行解除引用操作(作为以后进行查找的步骤之一)。
数组的下标引用:
char a[9] = "abcdefgh";
c = a[i];
编译器符号表具有一个地址9980
运行时步骤1:取i的值,将它与9980相加
运行时步骤2:取地址(9980 + i)的内容
9980 +1 +2 +3 +4..........+i
对指针的引用:
char *p;
c = *p;
编译器符号表有一个符号p,它的地址为4624
运行时步骤1:取地址4624的内容,就是‘5081’
运行时步骤2:取地址5081的内容
当你“定义为指针,但以数组方式引用”时会发生什么
char *p = "abcdefgh";
c = p[i];
编译器符号表具有一个p,地址为4624
运行时步骤1:取地址4624的内容,即 5081
运行时步骤2:取得i的值,并将它与5081相加
运行时步骤3:取地址[ 5081+i ]的内容
最后一种方式其实质是前两种访问方式的组合。先间接引用,在直接引用
- 取得符号表中p的地址,提取存储于此处的指针
- 把下标所表示的偏移量与指针的值相加,产生一个地址
- 访问上面这个地址
只要把p声明为指针,那么不管p原先是定义为指针还是数组,都会按照上面所示的三个步骤进行操作,但只有当p原来定义为指针时这个方法才是正确的。
文件1:
char p[10];
文件2:
extern char*p;
这种情况,当用p[i]这种形式提取这个声明的内容时,实际上得到的是一个字符,但按照上面的方法,编译器却把它当做指针,把ACSII解释为地址显示不对。
char *p = "abcdefgh"
p[3];
char *a = "abcdefgh"
a[3];
显然这两种情况下,都可以取得‘d’,但两者的途径不一样
数组与指针的其他区别
表三:数组与指针的区别
数组和指针都可以在它们的定义中用字符串常量进行初始化,尽管看上去一样,底层的机制却不相同
定义指针时,编译器并不为指针所指向的对象分配空间,它只是分配指针本身的空间,除非在定义时同时赋给指针一个字符串常量进行初始化,在ACSI C中,初始化指针所创建的字符串常量被定义为只读,如果试图通过指针修改这个字符串的值,程序就会出现未定义的行为,是因为:指针p 指向常量字符串(位于常量存储区),常量字符串的内容是不可以被修改的。
与指针相反,由字符串常量初始化的数组是可以修改的