字符串常量引起的思考
记得以前看过一道这样的题目:
以下程序的执行结果是?
#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的提醒。