从汇编的角度看C语言(二)一道面试题

本文详细探讨了一个经典的面试问题,涉及程序在Linux环境下编译与运行HelloWorld程序时遇到的栈空间限制、静态变量的影响、全局变量使用及优化编译选项的作用。通过剖析汇编代码,揭示了内存管理在进程空间中的关键点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 一道面试题Hello World

下面是我经常用的一道面试题

#include <stdio.h>

int main(int argc, char* argv[])
{
        int a[100000000];
        printf("Hello World\n");
        return 0;
}

在linux 64位系统下,问题如下:

  1. 以上程序能否编译通过,能否运行通过,能否看到“Hello World”打印出来?为什么?
  2. 如果把int a和print两行对调,结果如何?
  3. 把int a[100000000] 前面加static, 结果如何?为什么?
  4. 把int a[100000000]移动到main函数之外,成为全局变量,结果如何?为什么?
  5. 加优化编译选项又如何?为什么?

此问题考点是对进程空间的理解。虽然是个简单程序,回答漂亮的人并不多。

我们来逐个分析以上四个问题。

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被完全优化没了。因为后续也没用到。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

二宝真好记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值