C语言中的字符串常量

我们先看第一段代码

1 void foo() {
2         char *a="abcdefg";
3         a[2]='x';
4 }
5  
6 int main() {
7         foo();
8         return 0;
9 }

用gcc编译执行后,报“段错误”(Segment fault)。如果你在windows下执行的话,那就会弹出一个非法操作的对话框了。

第二段代码

1 void foo() {
2         char a[]="abcdefg";
3         a[2]='x';
4 }
5  
6 int main() {
7         foo();
8         return 0;
9 }

用gcc编译执行后一切正常。

那么char *a="abcdefg"和char a[]="abcdefg"究竟有啥区别呢?为啥第一段代码会出段错误呢。

我们把第一段代码反汇编来看一下(foo函数),反汇编的命令是objdump -d exec_file_name

 

08048394 <foo>:
 8048394: 55                   push   %ebp
 8048395: 89 e5                 mov    %esp,%ebp
 8048397: 83 ec 10             sub    $0x10,%esp
 804839a: c7 45 fc 80 84 04 08 movl   $0x8048480,-0x4(%ebp)
 80483a1: 8b 45 fc             mov    -0x4(%ebp),%eax
 80483a4: 83 c0 02             add    $0x2,%eax
 80483a7: c6 00 78             movb   $0x78,(%eax)
 80483aa: c9                   leave  
 80483ab: c3                   ret    
08048394 <foo>:
 8048394: 55                    push   %ebp
 8048395: 89 e5                 mov    %esp,%ebp
 8048397: 83 ec 10              sub    $0x10,%esp
 804839a: c7 45 fc 80 84 04 08  movl   $0x8048480,-0x4(%ebp)
 80483a1: 8b 45 fc              mov    -0x4(%ebp),%eax
 80483a4: 83 c0 02              add    $0x2,%eax
 80483a7: c6 00 78              movb   $0x78,(%eax)
 80483aa: c9                    leave  
 80483ab: c3                    ret    

 

我们可以看到,对于char *a="abcdefg",代码中会把地址0x8048480赋值给a,然后给a[2]赋值。那么这个0x8048480地址,我们猜一下也知道,肯定是"abcdefg"所在的位置了。那段错误是如何产生的呢?

原因在于,"abcdefg"被编译器放到了.rodata段,这个段的属性是readonly的,因此当我们要给a[2]赋值时,就会看到段错误了。

我们使用objdump -s exec_file_name命令可以看到可执行文件中的所有段,下面是.rodata的内容,可以看到"abcdefg"就在里面(0x8048478表示这一行的地址,61正好在0x8048478+8=0x8048480位置)。

Contents of section .rodata:

 8048478 03000000 01000200  61626364 65666700  ........abcdefg.

那么对于char a[]="abcdefg"又是什么情况呢?

我们也来反汇编一下。

 

080483e4 <foo>:
 80483e4: 55                    push   %ebp
 80483e5: 89 e5                 mov    %esp,%ebp
 80483e7: 83 ec 18              sub    $0x18,%esp
 80483ea: 65 a1 14 00 00 00     mov    %gs:0x14,%eax
 80483f0: 89 45 f4              mov    %eax,-0xc(%ebp)
 80483f3: 31 c0                 xor    %eax,%eax
 80483f5: a1 00 85 04 08        mov    0x8048500,%eax
 80483fa: 8b 15 04 85 04 08     mov    0x8048504,%edx
 8048400: 89 45 ec              mov    %eax,-0x14(%ebp)
 8048403: 89 55 f0              mov    %edx,-0x10(%ebp)
 8048406: c6 45 ee 78           movb   $0x78,-0x12(%ebp)
 804840a: 8b 45 f4              mov    -0xc(%ebp),%eax
 804840d: 65 33 05 14 00 00 00  xor    %gs:0x14,%eax
 8048414: 74 05                 je     804841b <foo+0x37>
 8048416: e8 fd fe ff ff        call   8048318 <__stack_chk_fail@plt>
 804841b: c9                    leave  
 804841c: c3                    ret    

 

这里有两个地址0x8048500和0x8048504,如果看不懂汇编也关系,事实上这几行的意思是从这两个地址把数据拷贝给数组a。显然a的内存空间在栈上,那么这当然是可以写的啦,所以就不会出错了。也就是说,对于char a[]="abcdefg",编译器会生成一段代码,把数据从.rodata这个只读的段,拷贝到a的数组中。这里附上.rodata的内容,大家可以算算地址。

 

Contents of section .rodata:
 80484f8 03000000 01000200 61626364 65666700  ........abcdefg.

 

还有点东西,如果你把char a[]="abcdefg"改成char a[]="testtest",反汇编出来的又会是不一样的结果。

 

080483e4 <foo>:
 80483e4: 55                    push   %ebp
 80483e5: 89 e5                 mov    %esp,%ebp
 80483e7: 83 ec 18              sub    $0x18,%esp
 80483ea: 65 a1 14 00 00 00     mov    %gs:0x14,%eax
 80483f0: 89 45 f4              mov    %eax,-0xc(%ebp)
 80483f3: 31 c0                 xor    %eax,%eax
 80483f5: c7 45 eb 74 65 73 74  movl   $0x74736574,-0x15(%ebp)
 80483fc: c7 45 ef 74 65 73 74  movl   $0x74736574,-0x11(%ebp)
 8048403: c6 45 f3 00           movb   $0x0,-0xd(%ebp)
 8048407: c6 45 ed 78           movb   $0x78,-0x13(%ebp)
 804840b: 8b 45 f4              mov    -0xc(%ebp),%eax
 804840e: 65 33 05 14 00 00 00  xor    %gs:0x14,%eax
 8048415: 74 05                 je     804841c <foo+0x38>
 8048417: e8 fc fe ff ff        call   8048318 <__stack_chk_fail@plt>
 804841c: c9                    leave  
 804841d: c3                    ret    

这里直接将两个常量0x74736574赋值给数组a了,0x74736574翻译成ascii正好是test(注意字节序,这里是little endian)。

其实可能还有其他的初始化数组a的情况,也不必去细究细节。

关键是要明白一点,写char *a=“abcdefg”的话,a指针指向的事实上是只读的区域,因此,更准确的写法是const char *a="abcdefg"。

而char a[]=“abcdefg”,编译器会生成一段初始化数组的代码,但是数组a的内容是可以修改的。如果不需要修改数组a的内容,写成const char *a="abcdefg"会有效率上的提高。

最后感慨一下,C/C++是一个大坑,没有多年的编程积累和对操作系统及编译器等底层的了解,是很难真正掌握的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值