忘记密码   免费注册 查看新帖 | 论坛精华区

ChinaUnix.net

  平台 论坛 博客 认证专区 大话IT 视频 徽章 文库 沙龙 自测 下载 频道自动化运维 虚拟化 储存备份 C/C++ PHP MySQL 嵌入式 Linux系统
1234下一页
最近访问板块 发新帖
查看: 40926 | 回复: 36

[C] [原创]数组与指针---都是"退化"惹的祸 [复制链接]

论坛徽章:
0
发表于 2007-12-19 20:23 |显示全部楼层
个人的浅显认识, 欢迎批评指正.

1. 什么是数组类型?

下面是C99中原话:
An array type describes a contiguously allocated nonempty set of objects with a
particular member object type, called the element type.36) Array types are characterized by their element type and by the number of elements in the array. An array type is said to be derived from its element type, and if its element type is T , the array type is sometimes called ‘‘array of T ’’. The construction of an array type from an element type is called ‘‘array type derivation’’.

很显然, 数组类型也是一种数据类型, 其本质功能和其他类型无异:定义该类型的数据所占内存空间的大小以及可以对该类型数据进行的操作(及如何操作).

2. 数组类型定义的数据是什么?它是变量还是常量?
char s[10] = "china";
在这个例子中, 数组类型为 array of 10 chars(姑且这样写), 定义的数据显然是一个数组s.

下面是C99中原话:
  An lvalue is an expression with an object type or an incomplete type other than void; if an lvalue does not designate an object when it is evaluated, the behavior is undefined. When an object is said to have a particular type, the type is specified by the lvalue used to designate the object. A modifiable lvalue is an lvalue that does not have array type, does not have an incomplete type, does not have a const-qualified type, and if it is a structure or union, does not have any member (including, recursively, any member or element of all contained aggregates or unions) with a const-qualified type.

看了上面的定义, 大家应该明白了modifiable lvalue和lvalue的区别, 大家也应该注意到array type定义的是lvalue而不是modifiable lvalue.所以说s是lvalue.

s指代的是整个数组, s的内容显然是指整个数组中的数据, 它是china\0****(这里*表示任意别的字符).s的内容是可以改变的, 从这个意义上来说, s显然是个变量.

3. 数组什么时候会"退化"

下面是C99中原话:
Except when it is the operand of the sizeof operator or the unary & operator, or is a string literal used to initialize an array, an expression that has type ‘‘array of type’’ is converted to an expression with type ‘‘pointer to type’’ that points to the initial element of the array object and is not an lvalue.

上面这句话说的很清楚了, 数组在除了3种情况外, 其他时候都要"退化"成指向首元素的指针.
比如对 char s[10] = "china";
这3中例外情况是:
(1) sizeof(s)
(2) &s;
(3) 用来初始化s的"china";

除了上述3种情况外,s都会退化成&s[0], 这就是数组变量的操作方式

4. 数组与指针有什么不同?
4.1 初始化的不同
char s[] = "china";
char *p = "china";

在第一句中,以&s[0]开始的连续6个字节内存分别被赋值为:
'c', 'h', 'i', 'n', 'a', '\0'

第二句中,p被初始化为程序data段的某个地址,该地址是字符串"china"的首地址

4.2 sizeof的不同

sizeof就是要求一种数据(类型)所占内存的字节数. 对于4.1中的s和p
sizeof(s)应为6, 而sizeof(p)应为一个"指针"的大小.

这个结果可以从1中对于数组类型的定义和3中数组什么时候不会"退化"中得出来.

4.3 &操作符

对于&操作符, 数组同样不会退化.
4.1中的s和p分别取地址后,其意义为:
&s的类型为pointer to array of 6 chars.
&p的类型为pointer to pointer to char.

4.4 s退化后为什么不可修改

除3种情况外,数组s在表达式中都会退化为"指向数组首元素的指针", 既&s[0]

举个例子
int a;
(&a)++; //你想对谁++? 这显然是不对的

对(&s[0])++操作犹如(&a)++, 同样是不对的,这就导致退化后的s变成不可修改的了.

4.5 二维数组与二级指针

char s[10];与char *p;
char s2[10][8];与char **p2;

s与p的关系,s2与p2的关系,两者相同吗?
紧扣定义的时候又到了.
除3种情况外,数组在表达式中都会退化为"指向数组首元素的指针".

s退化后称为&s[0], 类型为pointer to char, 与p相同
s2退化后称为&s2[0], 类型为pointer to array of 8 chars, 与p2不同

4.6 数组作为函数参数

毫无疑问, 数组还是会退化.

void func(char s[10]); <===> void func(char *s);

void func(char s[10][8]); <===> void func(char (*s)[8]);

4.7 在一个文件中定义char s[8], 在另外一个文件中声明extern char *s. 这样可以吗?

---------file1.c---------
char s[8];

---------file2.c---------
extern char *s;


答案是不可以. 一般来说,在file2.c中使用*s会引起core dump, 这是为什么呢?

先考虑int的例子.
---------file1.c---------
int a;

---------file2.c---------
extern int a;

file1.c和file2.c经过编译后, 在file2.o的符号表中, a的地址是尚未解析的
file1.o和file2.o在链接后, file2.o中a的地址被确定.假设此地址为0xbf8eafae

file2.o中对该地址的使用,完全是按照声明extern int a;进行的,即0xbf8eafae会被认为是整形a的地址
比如 a = 2; 其伪代码会对应为 *((int *)0xbf8eafae) = 2;

现在再看原来的例子.

---------file1.c---------
char s[8];

---------file2.c---------
extern char *s;

同样, file1.c和file2.c经过编译后, 在file2.o的符号表中, s的地址是尚未解析的
file1.o和file2.o在链接后, file2.o中s的地址被确定.假设此地址为0xbf8eafae

file2.o中对该地址的使用,完全是按照声明extern char *s;进行的,即0xbf8eafae会被认为是指针s的地址
比如 *s = 2; 其伪代码会对应为 *(*((char **)0xbf8eafae)) = 2;

*((char **)0xbf8eafae)会是什么结果呢?
这个操作的意思是:将0xbf8eafae做为一个二级字符指针, 将0xbf8eafae为始址的4个字节(32位机)作为一级字符指针
也就是将file1.o中的s[0], s[1], s[2], s[3]拼接成一个字符指针.


那么*(*((char **)0xbf8eafae)) = 2;的结果就是对file1.o中s[0], s[1], s[2], s[3]拼接成的这个地址对应
的内存赋值为2.
这样怎么会正确呢?


下面看看正确的写法:

---------file1.c---------
char s[8];

---------file2.c---------
extern char s[];


同样, file1.c和file2.c经过编译后, 在file2.o的符号表中, s的地址是尚未解析的
file1.o和file2.o在链接后, file2.o中s的地址被确定.假设此地址为0xbf8eafae

file2.o中对该地址的使用,完全是按照声明extern char s[];进行的,即0xbf8eafae会被认为是数组s的地址
比如 *s = 2; 其伪代码会对应为 *(*((char (*)[])0xbf8eafae)) = 2;

*((char (*)[])0xbf8eafae)会是什么结果呢?
这个操作的意思是:将0xbf8eafae做为一个指向字符数组的指针, 然后对该指针进行*操作.
这就用到了数组的一个重要性质:
对于数组 char aaa[10];来说, &aaa[0], &aaa, *(&aaa)在数值上是相同的(其实, *(&aaa)之所以在程序中
会在值上等于&aaa[0], 这也是退化的结果: *(&aaa)就是数组名aaa, aaa退化为&aaa[0]).
所以, *((char (*)[])0xbf8eafae)的结果在值上还是0xbf8eafae, 在类型上退化成"指向数组首元素的指针"


那么*(*((char (*)[])0xbf8eafae)) = 2;
其伪代码就成为*((char *)0xbf8eafae) = 2; 即将数组s的第一个元素设为2


5. 小结论

(a). 数组类型是一种特殊类型, 它定义的是数组变量, 是lvalue但不是modifiable lvalue
(b). 除了3种情况外(sizeof, &, 用做数组初始化的字符串数组), 数组会退化成"指向数组首元素的指针"
(c). 不要将数组名简单的看作不可修改的相应的指针, 它们还是有很多不同的

[ 本帖最后由 ypxing 于 2007-12-20 09:46 编辑 ]

论坛徽章:
0
发表于 2007-12-19 21:23 |显示全部楼层
char s[] = "china";
这个也是放在.data节的

那个*(*((char **)0xbf8eafae)) 还没看懂
下班回家了,明天继续看

论坛徽章:
0
发表于 2007-12-19 21:26 |显示全部楼层
俺这里想强调的是s的内容, 呵呵

原帖由 dxcnjupt 于 2007-12-19 21:23 发表
char s[] = "china";
这个也是放在.data节的

那个*(*((char **)0xbf8eafae)) 还没看懂
下班回家了,明天继续看

论坛徽章:
0
发表于 2007-12-19 21:41 |显示全部楼层
顶顶

论坛徽章:
0
发表于 2007-12-19 22:01 |显示全部楼层
不错, 老兄知识掌握得相当好,  文笔也相当不错. 顶一个!

论坛徽章:
0
发表于 2007-12-19 22:08 |显示全部楼层
过奖了

原帖由 scutan 于 2007-12-19 22:01 发表
不错, 老兄知识掌握得相当好,  文笔也相当不错. 顶一个!

论坛徽章:
0
发表于 2007-12-20 09:41 |显示全部楼层
thanks!

论坛徽章:
0
发表于 2007-12-20 10:18 |显示全部楼层
不错。。。

论坛徽章:
0
发表于 2007-12-20 10:32 |显示全部楼层
能静下心来看标准的人都是强人!

论坛徽章:
0
发表于 2007-12-20 10:58 |显示全部楼层
ypxing写得非常不错,没看出有什么问题,顶一个。

另外有个疑问,ypxing怎么想到“退化”这个词的,有英文资料这么说,还是自己想出来的?

[ 本帖最后由 Sorehead 于 2007-12-20 11:00 编辑 ]
您需要登录后才可以回帖 登录 | 注册

本版积分规则

SACC2017购票7.8折优惠进行时

2017中国系统架构师大会(SACC2017)将于10月19-21日在北京新云南皇冠假日酒店震撼来袭。今年,大会以“云智未来”为主题,云集国内外顶级专家,围绕云计算、人工智能、大数据、移动互联网、产业应用等热点领域展开技术探讨与交流。本届大会共设置2大主会场,18个技术专场;邀请来自互联网、金融、制造业、电商等多个领域,100余位技术专家及行业领袖来分享他们的经验;并将吸引4000+人次的系统运维、架构师及IT决策人士参会,为他们提供最具价值的交流平台。
----------------------------------------
优惠时间:2017年8月2日前

活动链接>>
  

北京皓辰网域网络信息技术有限公司. 版权所有 京ICP证:060528号 北京市公安局海淀分局网监中心备案编号:1101082001
广播电视节目制作经营许可证(京) 字第1234号 中国互联网协会会员  联系我们:
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP