Fork me on GitHub

字符串常量引起的思考

  记得以前看过一道这样的题目:

  以下程序的执行结果是?

#include <stdio.h>                                                          
  
int main()
{
       char* p="Hello World"; 
       *(p+1)='a'; 
       printf("%s\n",p);
       return 0; 
} 

  应该不难吧,不知道大家的答案是什么。

  以下是我的一些解答:

    对于指针p,他的大小是sizeof(p),一般为4,至于他指向的对象的大小是sizeof(char),那么在哪里存放字符串“Hello World”呢?

  我们知道,程序编译时,编译器将代码翻译成汇编代码,然后汇编器将汇编代码翻译成机器代码(得到目标文件),最后链接器将目标文件链接成可执行文件。而目标文件和可执行文件的格式一般是类似的,由一个个section(段)组成,一般来说有代码段、数据段、bss段等,有些平台还会有.rodata段(只读数据段),用来放置只读变量(const变量)和字符串常量,这样不仅可以在语义上支持C++的const,而且操作系统还可以在加载的时候将.rodata映射为只读,这样对于这个段的修改会作为非法处理,保证了程序的安全。

  (1)在Linux下可以用objdump查看目标文件或可执行文件中的内容:  

objdump -x -s -d testchar

  得到的结果如下:

  

  (左边是十六进制内容,右边是ASCII形式的内容)

  可以看到字符串Hello World就在.rodata段中。

  (2)接下来我们使用readelf来查看.rodata段的属性:

    

  得到:

  

  其中.rodata段的标志(Flg)为A(alloc),表示该段在进程中要分配空间,如果一个段可写的话,应该要有W(write)标志,如数据段(.data):

    

  也就是说,现在字符串“Hello World”在不可写的.rodata中,如果我们用指针直接修改的话,就会引发段错误。

  所以程序的运行结果是:Segmentation fault 应该说在Linux 下用gcc编译后,程序运行结果是:Segmentation fault

  感谢@garbageMan的回复,确实,我说只是在gcc下的情况

@garbageMan:

很难说String literal 放在哪里
C语言并没有具体规定
放在哪里是编译器自己确定的
所以从一个编译结果推广到所有就是错误的

此外,C语言规定,修改String literal是一种UB
任何可能结果都有
不一定引发“段错误”

  (3)数组中的情况

  如果程序是这样呢:

#include <stdio.h>                                                          

int main()
{
     char p[]="Hello World";
     *(p+1)='a'; 
     printf("%s\n",p);
     return 0; 
} 

  程序是可以正常运行的。

  同样用objdump查看.rodata的内容:

  

  发现字符串已经不在.rodata中了。

  对于数组,我们可以直接分配空间,像上面的数组p就可以直接在栈中分配空间来存放字符串“Hello World”,这样就可以修改字符串了。

 

  以下内容我有点不肯定:

  查看text段(代码段)中的内容,发现字符串在text段中。

  

    所以我的猜想是程序执行时,先将text段中的字符串复制到数组p在栈中的分配的内存里,这样就可以对字符串修改同时又不影响代码段中的内容(因为代码段一般是只读   的)不知我这个猜想对不对,希望各位解答一下……同时这个问题也感谢 @buzzerrookie的提醒。

posted @ 2013-01-12 22:28  _Lei  阅读(6757)  评论(9编辑  收藏  举报