1. 一道面试题Hello World
下面是我经常用的一道面试题
#include <stdio.h>
int main(int argc, char* argv[])
{
int a[100000000];
printf("Hello World\n");
return 0;
}
在linux 64位系统下,问题如下:
- 以上程序能否编译通过,能否运行通过,能否看到“Hello World”打印出来?为什么?
- 如果把int a和print两行对调,结果如何?
- 把int a[100000000] 前面加static, 结果如何?为什么?
- 把int a[100000000]移动到main函数之外,成为全局变量,结果如何?为什么?
- 加优化编译选项又如何?为什么?
此问题考点是对进程空间的理解。虽然是个简单程序,回答漂亮的人并不多。
我们来逐个分析以上四个问题。
2. 问题1
2.1 结果
[]$ g++ -o test_hello test_hello.cpp -g
[]$ ./test_hello
段错误(吐核)
[]$
答案:编译通过,运行崩溃。
2.2 原因
我们把源程序翻译成汇编
g++ -S hello_world.cpp
得到汇编代码如下:
.file "test_hello.cpp"
.section .rodata
.LC0:
.string "Hello World"
.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
subq $400000016, %rsp
movl %edi, -400000004(%rbp)
movq %rsi, -400000016(%rbp)
movl $.LC0, %edi
call puts
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)"
.section .note.GNU-stack,"",@progbits
问题出在这一行上:subq $400000016, %rsp。压栈压了400M。但linux系统默认的进程栈空间是8M. 所以进程启动失败。
[]$ ulimit -a |grep stack
stack size (kbytes, -s) 8192
如果把栈开大,则运行成功
[]$ ulimit -s 512000
[]$ ./test_hello
Hello World
问题2
程序变成这个样子:
#include <stdio.h>
int main(int argc, char* argv[])
{
printf("Hello World\n");
int a[100000000];
return 0;
}
一部分同学经过了问题1的实践,认为运行时出错,但Hello World能打出来。这些可能是在用解释语言的逐行执行来来思考。我们看看前后程序汇编的区别:
答案是:除了文件名没有区别。表现当然一样了。
问题3
程序为:
#include <stdio.h>
int main(int argc, char* argv[])
{
static int a[100000000];
printf("Hello World\n");
return 0;
}
我们来编译运行:
[]$ g++ -o test_hello test_hello.cpp -g
[]$ ./test_hello
Hello World
[]$
程序运行通过。一个static作用为什么这么大呢?我们来看看汇编
.file "test_hello.cpp"
.section .rodata
.LC0:
.string "Hello World"
.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
subq $16, %rsp
movl %edi, -4(%rbp)
movq %rsi, -16(%rbp)
movl $.LC0, %edi
call puts
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.local _ZZ4mainE1a
.comm _ZZ4mainE1a,400000000,32
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)"
.section .note.GNU-stack,"",@progbits
发现压栈只压了16字节。而数组a不再存储在栈中,而是到了一个新的数据区域。从而避开了栈空间的限制。
问题4
把数组a变成全局变量。运行通过。汇编代码与static完全一致。
问题5
C代码还是最初的那份代码
[]$ g++ -o test_hello test_hello.cpp -g
[]$ ./test_hello
段错误(吐核)
[]$ g++ -o test_hello test_hello.cpp -O2
[]$ ./test_hello
Hello World
优化编译,程序就运行通过了?我们看看优化编译的汇编代码
.file "test_hello.cpp"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "Hello World"
.section .text.startup,"ax",@progbits
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB12:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $.LC0, %edi
call puts
xorl %eax, %eax
addq $8, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE12:
.size main, .-main
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)"
.section .note.GNU-stack,"",@progbits
压栈还是16, 大数组a被完全优化没了。因为后续也没用到。