预处理->编译->汇编->链接

这是本人的第一篇博客,主要是想记录一些心得,增加印象,如果能给大家提供一些参考就更好了。

水平有限,还请大家批判。

本文全部例子在centos 7上运行,gcc 版本 4.8.5 20150623 (Red Hat 4.8.5-28) (GCC)

 

一个例子(文件名为 hello_damon.c):

#include <stdio.h>

#ifndef KKK
 #pragma message("not define KKK...")
 #define ONE_K 1024
#endif

//I am annotation

/*
 * I am annotation too 
*/

int main(int argc, char **argv)
{
	printf("hello, damon\n");
	printf("ONE_K = %d\n", ONE_K);
	return 0;
}

编译以及运行的结果 :

[damon@localhost work]$ ls
hello_damon.c
[damon@localhost work]$ gcc hello_damon.c 
hello_damon.c:4:10: 附注:#pragma message:not define KKK...
  #pragma message("not define KKK...")
          ^
[damon@localhost work]$ ./a.out 
hello, damon
ONE_K = 1024

以上例子虽然简单,但gcc hello_damon.c 这个命令背后究竟发生了什么?

为何在此之后程序可以被编译成为一个可执行文件?

 

 

正片开始:

gcc hello_damon.c  这个命令可以被分解为如下四步:

一:预处理(也叫做 预编译)

gcc -E hello_damon.c -o hello_damon.i

在执行命令之后我们得到hello_damon.i文件,文本较多,我截取其中一部分来说明问题

.......
.......
         
# 4 "hello_damon.c"
#pragma message("not define KKK...")
# 4 "hello_damon.c"
# 14 "hello_damon.c"
int main(int argc, char **argv)
{
 printf("hello, damon\n");
 printf("ONE_K = %d\n", 1024);
 return 0;
}

在这个文件中我们可以看到

1. # 4 "hello_damon.c" 以及 # 14 "hello_damon.c"  是行号和文件名标识,用于编译器产生调试用的行号以及编译错误或警告时用的错误信息。

2.所有的“#define"被删除,并且展开所有的宏定义,在文件中可以看到printf("ONE_K = %d\n", ONE_K); 被替换为 printf("ONE_K = %d\n", 1024);

3.所有的条件编译信息被处理( #ifndef  #endif等 )

4.#pragma 编译器指令被保留

5.“#include” 指令被处理,该处理是递归的,我在例子仅仅只包含了"<stdio.h>"但是产生的hello_damon.i还是很大,因为"<stdio.h>"还包含其头文件

6.注释被处理( //  以及  /**/)

 

二:编译

gcc -S hello_damon.i -o hello_damon.s

在执行命令之后我们得到hello_damon.s 汇编代码 (注意尾缀是小s,和大写S是有区别的),如下

	.file	"hello_damon.c"
	.section	.rodata
.LC0:
	.string	"hello, damon"
.LC1:
	.string	"ONE_K = %d\n"
	.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	$1024, %esi
	movl	$.LC1, %edi
	movl	$0, %eax
	call	printf
	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-28)"
	.section	.note.GNU-stack,"",@progbits

 

 

三:汇编

gcc -c hello_damon.s -o hello_damon.o

执行该命令之后我们得到目标文件hello_damon.o,该文件已经是二进制机器码不能直接查看,此时可以借助如下命令

objdump -t hello_damon.o

得到输出:

hello_damon.o:     文件格式 elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS*	0000000000000000 hello_damon.c
0000000000000000 l    d  .text	0000000000000000 .text
0000000000000000 l    d  .data	0000000000000000 .data
0000000000000000 l    d  .bss	0000000000000000 .bss
0000000000000000 l    d  .rodata	0000000000000000 .rodata
0000000000000000 l    d  .note.GNU-stack	0000000000000000 .note.GNU-stack
0000000000000000 l    d  .eh_frame	0000000000000000 .eh_frame
0000000000000000 l    d  .comment	0000000000000000 .comment
0000000000000000 g     F .text	0000000000000034 main
0000000000000000         *UND*	0000000000000000 puts
0000000000000000         *UND*	0000000000000000 printf

我们可以在此处直观的查看到函数信息等。

 

四:链接

ld -static crt1.o crti.o crtbeginT.o -start-group -lgcc -lgcc_eh -lc-end-group crtend.o crtn.o hello_damon.o

注意上述命令中的crt1.o、crti.o、crtbegin.o、crtend.o、crtn.o目标文件是需要输入全路径的,此处未用全路径,取决于不同的机器环境,是gcc加入的系统标准启动文件,对于一般应用程序,这些启动是必需的。

简单的说:链接就是将汇编生成的OBJ文件、系统库的OBJ文件、库文件链接起来,最终生成可以在特定平台运行的可执行程序。

链接的细节以及过程相对复杂,我准备另开一篇进行记录总结。

 

总结:

 编译过程可以分为四个步骤:

预处理->编译->汇编->链接

预处理:gcc -E hello_damon.c -o hello_damon.i

编译:    gcc -S hello_damon.i -o hello_damon.s

汇编:    gcc -c hello_damon.s -o hello_damon.o

链接:    ld -static crt1.o crti.o crtbeginT.o -start-group -lgcc -lgcc_eh -lc-end-group crtend.o crtn.o hello_damon.o

 

参考:

《程序员的自我修养》https://book.douban.com/subject/3652388/

gcc程序的编译过程和链接原理 https://blog.csdn.net/czg13548930186/article/details/78331692

 

水平有限,不足与错误之处,还请大家多多指教

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值