有一天一個朋友問了我一個很有意思的問題。他問我如果用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 |