Chinaunix首页 | 论坛 | 博客
  • 博客访问: 669671
  • 博文数量: 137
  • 博客积分: 7000
  • 博客等级: 少将
  • 技术积分: 1335
  • 用 户 组: 普通用户
  • 注册时间: 2005-11-23 15:18
文章分类

全部博文(137)

文章存档

2010年(2)

2009年(2)

2008年(2)

2007年(30)

2006年(99)

2005年(2)

我的朋友

分类: C/C++

2007-07-20 18:32:41

把一个指针以数组的方式来引用是个很不好的习惯,在某些情况下,编译器对指针和数组的引用方式是存在差异的。不信?请看下面一段会引起崩溃的代码:


/* pna.c */

int main ()
{
  extern char *ea;
  char a[] = "abcdef";
  char *p = "abcdef";
  extern char ep[];

  printf("a[3]=%c %p %p\n", a[3], a, &a[3]);
  printf("p[3]=%c %p %p\n", p[3], p, &p[3]);
  printf("ep[3]=%c %p %p\n", ep[3], ep, &ep[3]);
  printf("ea[3]=%c\n", ea[3]); /* 引起崩溃的罪魁祸首 */
}


/* pna_nothing.c */

char ea[] = "abcdefg";

char *ep = "abcdefg";


编译:

gcc -g -o pna pna.c pna_nothing.c


执行:

$ ./pna.exe

a[3]=d 0x22ccd0 0x22ccd3

p[3]=d 0x403000 0x403003

ep[3]=  0x402008 0x40200b

    8 [main] pna 3596 _cygtls::handle_exceptions: Error while dumping state (probably corrupted st ack)

Segmentation fault (core dumped)


Oh,出错喽
我们来看看调试的中间过程:

Breakpoint 1, main () at pna.c:2
2 {
(gdb) l
1 int main ()
2 {
3 extern char *ea;
4 char a[] = "abcdef";
5 char *p = "abcdef";
6
7 printf("a[3]=%c %p %p\n", a[3], a, &a[3]);
8 printf("p[3]=%c %p %p\n", p[3], p, &p[3]);
9 printf("ea[3]=%c\n", ea[3]);
10 }
(gdb) n
4 char a[] = "abcdef";
(gdb)
5 char *p = "abcdef";
(gdb)
7 printf("a[3]=%c %p %p\n", a[3], a, &a[3]);
(gdb)
a[3]=d 0x22cc90 0x22cc93
8 printf("p[3]=%c %p %p\n", p[3], p, &p[3]);
(gdb)
p[3]=d 0x403000 0x403003
9 printf("ea[3]=%c\n", ea[3]);
(gdb) p a
$1 = "abcdef"
(gdb) p &a
$2 = (char (*)[7]) 0x22cc90
(gdb) p p
$3 = 0x403000 "abcdef"
(gdb) p &p
$4 = (char **) 0x22cc8c
(gdb) p ea
$5 = "abcdefg"
(gdb) p &ea
$6 = (char (*)[8]) 0x402000
(gdb) p ea+1
$7 = 0x402001 "bcdefg"
(gdb) p ea+2
$8 = 0x402002 "cdefg"
(gdb) p a+3
$9 = 0x22cc93 "def"
(gdb)


根据以上结果,我们分析一下数组元素a[3]的引用过程:
1.编译器符号表具有地址 0x22cc90
2.运行时步骤1,
0x22cc90+3=0x22cc93
3.运行时步骤2,取地址
0x22cc93的值d

其内存布局大致如下:
address |value   |name
------------------------
0x22cc90|'a'     |a[0]
...     |...     |...
0x22cc93|'d'     |a[3]

接着我们分析一下p[3]的引用过程:
1.编译器符号表有一个符号p:它的地址为
0x22cc8c
2.运行时步骤1,取
地址0x22cc8c的内容0x403000
3.运行时步骤2,
0x403000+3=0x403003
4.运行时步骤3,取地址
0x403003的值d

其内存布局大致如下:
address |value   |name
------------------------
0x22cc8c|0x403000| p
...     |...     |...
0x403000|'a'     |p[0]
...     |...     |...
0x403003|'d'     |p[3]

看出区别了么?数组引用是直接寻址,指针方式引用是间接寻址
这就是一个细微的区别,如果不注意区别可能会导致致命的错误,就像接着对ea[3]的外部引用,把ea声明为一个指针,但实际上它是一个数组,对一个数组间接引用会产生什么后果呢?过程如下:
1.编译器符号表有一个符号ea:它的地址为0x402000
2.运行时步骤1,取
地址0x402000的内容,编译器被告知ea是指针(其实是数组),于是采用间接寻址方式,作为32位平台,指针大小为4byte,因此取0x402000--0x402003范围内的值,即ASCII码abcd被解释成16进制值0x64636261(little-endian),作为寻址地址
3.运行时步骤2,偏移运算,因为ea被告知是指向char型的,故
0x64636261+3=0x64636264
4.运行时步骤3,
试图取0x64636264地址的值!@#$$%^$%&^&^,这你都敢取??越界了啦
于是……
Segmentation fault……

其内存布局大致如下:
address   |value   |name
------------------------
0x64636264|xxxxxxxx|寻址ea+3到这,该值未知……
...       |...     |...
0x402000  |01100001|不同的解释:61(hex) a(char),但ea却被不幸地解释成了&ea
0x402001  |
01100010|...       :62(hex) b(char)
0x402002  |
01100011|...       :63(hex) c(char)
0x402003  |01100100|...       :64(hex) d(char)

呵呵,不知这么解释清楚了么?要修正这个错误很简单,老老实实把ea声明为数组就行了。

定义指针时,编译器并不为指针所指向的对象分配空间,它只是分配指针本身的空间。除非在定义时同时赋给指针一个字符串常量进行初始化。注意,只有对字符串常量才是如此,不能指望为浮点数或整型的常量分配空间。

ANSI C中,初始化指针时所创建的字符串常量被定义为只读。
而由字符串常量初始化的数组是可以修改的。

数组和指针在编译器处理时是不同的,在运行时的表示形式也是不一样的,并可能产生不同的代码。对编译器而言,一个数组就是一个地址,一个指针就是一个地址的地址。


阅读(3101) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~