浅谈C/C++的编译过程——源码如何变成可执行文件

相信很多人同我一样,在刚刚接触C语言的时候,只是找了一本教材,或者是找了一套教学视频,跟着慢慢学习C语言的语法,并没有去多想一个.c文件在后台究竟是经过了怎样的步骤才最终变成.exe文件;就在前几天,本人闲着无聊翻开了在书架上吃灰将近一年的“全新”CSAPP,在看到其第一章的内容之后,恍然大悟,姑且水一篇博客纪念一下。

 


首先我们来简要看一下CSAPP原书上的内容:(这是我按照自己的理解结合原书内容敲下的,也许会存在事实上的错误,尊重原书的说法。)

一个.c源码最终变成可执行文件要经过以下步骤:

  • 预处理阶段:预处理器根据以#开头的指令,修改源码内容,比如如果源码中有 #include<stdio.h> 则预处理器会读取文件 stdio.h 文件中的内容,并将其直接插入到原来的源码文件中,通常另存为以 .i 为扩展名的文件。
  • 编译阶段:编译器读取 .i 文件中的内容,并将其翻译为以 .s 为扩展名的汇编语言文件。
  • 汇编阶段:汇编器将 .s 文件翻译成机器码,并保存为 .o为扩展名的文件。
  • 链接阶段:链接器将不同的 .o 文件合并到一起,组成最终的可执行文件;比如我们的程序里调用了 printf 函数,作为一个C标准函数,printf 单独存在于一个 printf.o 的文件中,那么链接器将会找到这个 printf.o 文件,将其中的内容合并到我们自己的 .o 文件中,生成可以被加载到内存中执行的文件。

 光看理论没啥意思,让我们来手动操作一下:

首先新建一个 hello.c 文件,在里面敲上经典的C语言hello world代码,保存,进入cmd或powershell,移动到 hello.c 所在的文件夹(linux怎么做肯定不用我讲了,逃ε=ε=ε=┏(゜ロ゜;)┛)。

之后输入如下指令:

gcc -E hello.c -o hello.i
gcc -S hello.c -o hello.s

然后,打开 hello.i 我们就可以看到,预处理器已经对源码进行了修改(文件内容很多,我姑且放一张截图上来)

当然了,在文件的末尾是我们可怜的main函数:

如果想知道完整的文件里有什么,当然是自己动手试一下啦(✿◡‿◡)

然后,我们再打开 hello.s 文件,可以看到原始的 .c 被翻译为 .s 之后的结果:

(CSDN好像没有专门的汇编的渲染,姑且设置成C++)

	.file	"hello.c"
	.text
	.def	__main;	.scl	2;	.type	32;	.endef
	.section .rdata,"dr"
.LC0:
	.ascii "hello world\0"
	.text
	.globl	main
	.def	main;	.scl	2;	.type	32;	.endef
	.seh_proc	main
main:
	pushq	%rbp
	.seh_pushreg	%rbp
	movq	%rsp, %rbp
	.seh_setframe	%rbp, 0
	subq	$32, %rsp
	.seh_stackalloc	32
	.seh_endprologue
	call	__main
	leaq	.LC0(%rip), %rcx
	call	puts
	movl	$0, %eax
	addq	$32, %rsp
	popq	%rbp
	ret
	.seh_endproc
	.ident	"GCC: (x86_64-posix-sjlj-rev0, Built by MinGW-W64 project) 8.1.0"
	.def	puts;	.scl	2;	.type	32;	.endef

接下来我们还可以用:

gcc hello.c -o hello.o

生成 .o 文件,不过到了这一步。这些东西就完全超出我的姿势范围了。


单个文件玩过了,当然还要玩一下多文件才过瘾,让我们准备三个文件: hell2.c func.c func.h ,内容依次如下:

// hello2.c
#include <stdio.h>
#include "func.h"

int main()
{
	say_hello();
}
// func.h
#ifndef FUNC_H
#define FUNC_H

#include <stdio.h>
void say_hello();

#endif
// func.c
#include "func.h"

void say_hello()
{
	printf("hello\n");
}

之后还是进入命令行模式,依次输入以下指令:

gcc -c func.c -o func.o
gcc -E hello2.c -o hello2.i
gcc -S hello2.c -o hello2.s
gcc hello2.c func.o -o hello2.exe

如果没有错误,我们将得到 func.c 编译生成的 .o 文件, hello2.c 经过预处理器和编译器处理之后得到的 .i 和 .s 文件,以及 hello2.c 最终生成的可执行文件。

打开 hello2.i ,我们将看到 #include "func.h" 被预处理器替换之后的情况:

// 上面省略100多行天书
# 1 "C:/mingw64/x86_64-w64-mingw32/include/_mingw_print_pop.h" 1 3
# 1400 "C:/mingw64/x86_64-w64-mingw32/include/stdio.h" 2 3
# 3 "hello2.c" 2
# 1 "func.h" 1






# 6 "func.h"
void say_hello();
# 4 "hello2.c" 2

int main()
{
 say_hello();
}

打开 hello2.s ,我们可以看到“精简”的汇编代码:

	.file	"hello2.c"
	.text
	.def	__main;	.scl	2;	.type	32;	.endef
	.globl	main
	.def	main;	.scl	2;	.type	32;	.endef
	.seh_proc	main
main:
	pushq	%rbp
	.seh_pushreg	%rbp
	movq	%rsp, %rbp
	.seh_setframe	%rbp, 0
	subq	$32, %rsp
	.seh_stackalloc	32
	.seh_endprologue
	call	__main
	call	say_hello
	movl	$0, %eax
	addq	$32, %rsp
	popq	%rbp
	ret
	.seh_endproc
	.ident	"GCC: (x86_64-posix-sjlj-rev0, Built by MinGW-W64 project) 8.1.0"
	.def	say_hello;	.scl	2;	.type	32;	.endef

这篇博客到这里就差不多了,水平有限,欢迎指正!

= ̄ω ̄=

猜你喜欢

转载自blog.csdn.net/weixin_41429999/article/details/83627893