有一天一个朋友问了我一个很有意思的问题。他问我如果用C代码在一个函数里面写一行字符串初始化代码,如“char str[]="hello world",那么该字符串是如何被初始化的呢?
开始我不以为然,立刻回答:该字符串应该是程序在运行时,通过立即数寻址直接写入堆栈中的嘛。结果该朋友反问了一句:真的吗?我隐约觉得不对劲,等回来我写了段代码看看它到底是怎么初始化的。
代码(test.c)如下:
int main(int argc, char *argv[]){
char str[]="hello world";
str[1]='a';
return 0;
}
然后gcc -S test.c,
生成test.s文件,内容如下,去掉了一些无关紧要的部分:
.file "test.c"
.section .rodata
.LC0:
.string "hello world"
.text
.globl main
.type main, @function
main:
.LFB2:
pushq %rbp
.LCFI0:
movq %rsp, %rbp
.LCFI1:
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
movq .LC0(%rip), %rax
movq %rax, -16(%rbp)
movl .LC0+8(%rip), %eax
movl %eax, -8(%rbp)
movb $97, -15(%rbp)
movl $0, %eax
leave
ret
.LEFDE1:
.ident "GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-44)"
.section .note.GNU-stack,"",@progbits
结果出乎我的意料,第16-19行汇编代码清楚的告诉我们,该编译器是在
常量存储区(.rodata)里面先生成一个"hello world"的字符串,然后再初始化str数组时分两次将该字符串拷贝到堆栈中。
那么所有编译器都是这样做的吗?因为通过立即数寻址的方式也是可以对字符串进行初始化的,问题有没有编译器是这样做的呢?下面是我在另外一台机器上运行的结果,源代码用的还是test.c。
.file "test.c"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
movl $1819043176, -16(%rbp)
movl $1870078063, -12(%rbp)
movl $6581362, -8(%rbp)
movb $97, -16(%rbp)
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 4.6.2 20111027 (Red Hat 4.6.2-1)"
.section .note.GNU-stack,"",@progbits
第15-17行显示,在4.6版本的GCC里面是通过立即数寻址来完成字符串的初始化的。1819043176和1870078063表示的含义下面的表格解释的很清楚,因为这台测试的机器是小端机器,所以在读字符串的时候是从右往左读的。
十进制 | 十六进制 | 对应ASCII字符串 |
1819043176 | 6C6C6568 | lleh |
1870078063 | 6F77206F | ow o |
6581362 | 646C72 | dlr |