欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux下gcc编译C程序过程

程序员文章站 2022-03-03 22:31:55
...


gcc编译c语言程序过程分为4个步骤:

  • 1.预处理
    通过 -E 命令,生成 . i 文件。
  • 2.编译
    通过 -S 命令,将 . i 文件转换成成 . s 文件。
  • 3.汇编
    通过 -c 命令,将 . s 文件生成 . o 文件
  • 4.链接
    再通过 -o 命令将各个文件链接生成可执行文件(无后缀)。

-o 命令把输出文件输出到指定文件里

以一个 test.c 为例子,输出hello world。

使用cat命令查看文件内容,-n表示显示函数。

cat -n test.c

结果:

1   #include <stdio.h>
2	int main()
3	{
4		printf("hello world!\n"); //输出hello world!
5		return 0;
6   }

通常编译的时候,只需一步即可编译链接生成可执行文件再运行:
方法1

gcc test.c -o test 
./test

上面编译链接过程中的test.c和 -o test可以换位置,结果一样。

方法2

gcc test.c
./a.out

gcc test.i或gcc test.o或gcc test.s都会生成默认可执行文件a.out

预处理

为编译做预备工作,将源程序中含有#的代码替换掉。
比如在源程序中的#include <stdio.h>命令告诉预处理器读取系统头文件<stdio.h>的内容,并把它直接插入到程序文本中。(注意头文件的内容只有声明,没有相关代码的实现,实现的代码在链接阶段获取)
输入命令:

gcc -E test.c -o test.i

如果不加 -o test.i 则表示直接进行预处理并且显示出结果,不会生成 . i 文件。

结果为:

     1	# 1 "test.c"
     2	# 1 "<built-in>"
     3	# 1 "<command-line>"
     4	# 31 "<command-line>"
     5	# 1 "/usr/include/stdc-predef.h" 1 3 4
     6	# 32 "<command-line>" 2
     7	# 1 "test.c"
     8	# 1 "/usr/include/stdio.h" 1 3 4
     9	# 27 "/usr/include/stdio.h" 3 4
    10	# 1 "/usr/include/x86_64-linux-gnu/bits/libc-header-start.h" 1 3 4
    11	# 33 "/usr/include/x86_64-linux-gnu/bits/libc-header-start.h" 3 4
    12	# 1 "/usr/include/features.h" 1 3 4
    13	# 461 "/usr/include/features.h" 3 4
    14	# 1 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 1 3 4
    15	# 452 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 3 4
    16	# 1 "/usr/include/x86_64-linux-gnu/bits/wordsize.h" 1 3 4
    17	# 453 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 2 3 4
    18	# 1 "/usr/include/x86_64-linux-gnu/bits/long-double.h" 1 3 4
    19	# 454 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 2 3 4
    20	# 462 "/usr/include/features.h" 2 3 4
		....(此处省略,都是将stdio.h展开成宏等,头文件中只有声明,没有定义,定义需要在链接阶段去找)
		....
   719	extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
   720	# 858 "/usr/include/stdio.h" 3 4
   721	extern int __uflow (FILE *);
   722	extern int __overflow (FILE *, int);
   723	# 873 "/usr/include/stdio.h" 3 4
   724	
   725	# 2 "test.c" 2
   726	
   727	# 2 "test.c"
   728	int main()
   729	{
   730	 printf("hello world!\n");
   731	 return 0;
   732	}

编译

将源程序语言转换成汇编语言,生成 . o 文件。
输入命令:

gcc -S test.i -o test.s

此处的 -o test.s 可以去掉,不加也会自动生成test.s文件
结果:

     1		.file	"test.c"
     2		.text
     3		.section	.rodata
     4	.LC0:
     5		.string	"hello world!"
     6		.text
     7		.globl	main
     8		.type	main, @function
     9	main:
    10	.LFB0:
    11		.cfi_startproc
    12		endbr64
    13		pushq	%rbp
    14		.cfi_def_cfa_offset 16
    15		.cfi_offset 6, -16
    16		movq	%rsp, %rbp
    17		.cfi_def_cfa_register 6
    18		leaq	.LC0(%rip), %rdi
    19		call	[email protected]
    20		movl	$0, %eax
    21		popq	%rbp
    22		.cfi_def_cfa 7, 8
    23		ret
    24		.cfi_endproc
    25	.LFE0:
    26		.size	main, .-main
    27		.ident	"GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0"
    28		.section	.note.GNU-stack,"",@progbits
    29		.section	.note.gnu.property,"a"
    30		.align 8
    31		.long	 1f - 0f
    32		.long	 4f - 1f
    33		.long	 5
    34	0:
    35		.string	 "GNU"
    36	1:
    37		.align 8
    38		.long	 0xc0000002
    39		.long	 3f - 2f
    40	2:
    41		.long	 0x3
    42	3:
    43		.align 8
    44	4:

汇编

将汇编语言转换成机器语言,生成 . s 文件。

gcc -c test.s -o test.o

此处的 -o test.o可以去掉,不加也会自动生成test.o文件

此处用cat打开test.o可能回乱码,所以使用以下命令:

hexdump test.o

结果:

0000000 457f 464c 0102 0001 0000 0000 0000 0000
0000010 0001 003e 0001 0000 0000 0000 0000 0000
0000020 0000 0000 0000 0000 0310 0000 0000 0000
0000030 0000 0000 0040 0000 0000 0040 000e 000d
0000040 0ff3 fa1e 4855 e589 8d48 003d 0000 e800
0000050 0000 0000 00b8 0000 5d00 68c3 6c65 6f6c
0000060 7720 726f 646c 0021 4700 4343 203a 5528
0000070 7562 746e 2075 2e39 2e33 2d30 3731 6275
0000080 6e75 7574 7e31 3032 302e 2934 3920 332e
0000090 302e 0000 0000 0000 0004 0000 0010 0000
00000a0 0005 0000 4e47 0055 0002 c000 0004 0000
00000b0 0003 0000 0000 0000 0014 0000 0000 0000
00000c0 7a01 0052 7801 0110 0c1b 0807 0190 0000
00000d0 001c 0000 001c 0000 0000 0000 001b 0000
00000e0 4500 100e 0286 0d43 5206 070c 0008 0000
00000f0 0000 0000 0000 0000 0000 0000 0000 0000
0000100 0000 0000 0000 0000 0001 0000 0004 fff1
0000110 0000 0000 0000 0000 0000 0000 0000 0000
0000120 0000 0000 0003 0001 0000 0000 0000 0000
0000130 0000 0000 0000 0000 0000 0000 0003 0003
....
....

无论是windows还是linux,为了方便人类观察,计算机上的二进制数都用十六进制来表示,生成的都是十六进制代码,转化成二进制很方便,所以就等价于生成机器代码。

链接

这个阶段将有关的目标文件彼此相连接,也就是把可执行程序需要的所有的编译过程产生的.o文件组合到一起。比如gcc会在lib文件(实际上就算打包的.o文件集合)寻找函数实现的定义。
输入命令:

gcc test.o -o test

运行

有两种可执行文件,一种是默认生成的a.out(历史遗留原因),一种无后缀的可执行文件,运行哪个结果都一样。
输入命令:

./test.c

结果:

hello world!