目录
一、阅读资料
首先这些资料也值得去了解下:
强力推荐:数组和指针——都是“退化”惹的祸,这篇博客有涉及到编译链接原理。
二、 心得体会
2.1 不“退化”
仅在以下3种情况中,数组名不会“退化”为指针。
- sizeof(数组名);
- 对数组名进行取地址操作:&arr;
- 使用字符串初始化数组;如char arr[] ="Wang";
2.2 退化
其他情况下都会“退化”(退化后就具有常性不可改变)
一、规律1:(常用)数组名做函数参数时,arr都会退化为&arr[0]指针;
情形1: void foo(int* p[4]):形参p被解释成什么呢?int **p(二级指针)。
解释:p是一个一维指针数组,作形参时发生退化,变成指向数组首元素的指针,由于数组首元素是一级指针,故p就自动转变成二级指针。
情形2: void foo(int p[][5]):形参p被解释成什么呢?int(*p)[5],这个结果仍然符合规律。
解释:p是一个二维数组,作形参时发生退化,变成指向数组首元素的指针,由于数组首元素是一个一维数组,故p就自动转变成指向一维数组的指针,即int(*p)[5]。
二、规律2: 形参的形式为int *p、int p[]或int p[N]这三种写法都是允许的,但是最终都被编译器解释为int *p。
三、 代码实践
#include<stdio.h>
int MySize(char arr[])
{
return sizeof(arr);
}
int main()
{
char arr[] = { 'W','a','n','g','F','u','L','u','i','\0' };
const char *p = "WangFuLui";
char *q = arr;
printf("sizeof(arr)=%d\n", sizeof(arr));
printf("sizeof(p)=%d\n", sizeof(p));
printf("sizeof(q)=%d\n", sizeof(q));
printf("MySize(q)=%d\n", sizeof(q));
return 0;
}
四、 补充
4.1 情形1
//file1.c
int a;
//file.c
extern int a;
分析:
- 编译: file1.c和file2.c经过编译后,生成file1.o和file2.o文件;
- 符号重定位:在file2.o的符号表中,变量a的地址是尚未解析的(也可以说是尚未确定的);file1.o和file2.o经过链接后,file2.o中变量a的地址才被确定下来,假设是0xbf8eafae;
- 对地址的解释:file2.o对该地址的使用,是按照声明extern int a;进行的,比如会进行一个a=2的操作,那么相应的伪代码就是: *((int*)0xbf8eafae)=2。毫无疑问,这是正确使用了file1.o中的变量a。
4.2 情形2
//file1.c
char s[10];
//file2.c
extern char *s;
分析:
- 编译: file1.c和file2.c经过编译后,生成file1.o和file2.o文件;
- 符号重定位: 在file2.o的符号表中,变量s的地址是尚未解析的(也可以说是尚未确定的);file1.o和file2.o经过链接后,file2.o中变量s的地址才被确定下来,假设是0xbf8eafae;
- 对地址的解释: file2.o对该地址的使用,是按照声明extern char *s;进行的,比如会进行一个*s=2的操作,那么相应的伪代码就是: *(*((char **)0xbf8eafae)) 。里层*((char **)0xbf8eafae)何解? 将0xbf8eafae为始址的4个字节(32位机)作为一级字符指针,外层和里层统一起来看就相当于file1.o中s[0], s[1], s[2], s[3]拼接成的这个地址指向的内存赋值为2,很明显是不对的,对地址做出了错误的解释。地址是没有意义的,需要将其和类型结合起来看才可以。
4.3 情形2的改正
注意和情形2作比较,一点小差错,就可能导致程序发生莫名其妙的错误,而且一般还查不出来;特别是程序居然能不报错运行出来但就是不能得到预期的结果。
//file1.c
char s[10];
//file2.c
extern char s[];
分析:
- 编译 : file1.c和file2.c经过编译后,生成file1.o和file2.o文件;
- 符号重定位: 在file2.o的符号表中,变量s的地址是尚未解析的(也可以说是尚未确定的);file1.o和file2.o经过链接后,file2.o中变量s的地址才被确定下来,假设是0xbf8eafae;
- 对地址的解释: file2.o对该地址的使用,是按照声明extern char s[];进行的,比如会进行一个*s=2的操作,那么相应的伪代码就是: *((char (*)[])0xbf8eafae)=2; 0xbf8eafae被解释成一个数组地址,那么如何将 0xbf8eafae解释成一个数组的地址?将其强转成指针数组类型即可,编译器就会知道从0xbf8eafae开始向下连续的空间被分配给一个字符数组了(值得注意的是,在file2.o中使用该数组可能会越界,因为无从知道该数组的大小)。数组地址又退化成&s[0](回顾数组名不退化的三种情况),进行解引用效果上相当于令数组下标为0的元素的值变为2。