GCC介绍

GCC(GNU Compiler Collection) 是 GNU工具链的主要组成部分,一套以 GPL 和 LPGL许可证发布的程序语言编译器,由 Richard Stallman 于 1985 年开发

GCC原名是 GNU C语言编译器,因为它原本只能处理C语言,但是现在的 GCC 不仅可以编译C、C++ 和 Object-C,还可以通过不同的前端模块支持各种语言,包括 Java、Fortan、Ada、Pascal、Go 和 D语言等

GCC 的编译过程可以分为四个阶段:预处理(Pre-Processing)、编译(Compiling)、汇编(Assembling)以及链接(Linking)

以 C 语言为例,从源文件的编译到可执行文件的运行,整个过程大致如下:
C语言的编译执行流程

编译阶段解析

gcc 作为一个命令,直接编译 c source 文件,默认会生成 .out 文件,gcc 通过 -o 制定输出文件比如

gcc mainTestSuites.c -o test

下面以一个简单的C代码为例,说明程序的编译流程:

#include <stdio.h>

const char* str = "hello world";
const char astr[] = {"this is array"};

int array[] = {0};

#define TESTMARCO  "testmacro"

const char* testFunction(int para) {
	int temp;
	int atemp[10] = {0};
	if(para) {
		return str;
	} else {
		return TESTMARCO;
	}
}

int main(int argc, char *argv[]) {
	printf("main function run .\n");
	testFunction(10);
	
	return 0;
}

编译预处理(pre-processing)

-E 选项只进行预处理(不要编译、汇编或链接),默认输出 mainTestsuites.i 文件

gcc -E hello.c -o hello.i

编译预处理主要处理那些源文件中以 “#” 开头的预编译指令。主要处理规则如下:

  • 将所有的 #define 删除,并且展开所有的宏定义
  • 处理所有的条件预编译指令,比如 #if #else #ifdef #elif #endif
  • 删除所有的注释
  • 处理 #include 预编译指令,将被包含的文件插入到该预编译指令的位置,注意这个过程是递归进行的,也就是说被包含的文件还有可能包含其他文件
  • 添加行号和文件名识别,比如 #2 “”hello.c” 2 以便于编译器产生调试用的行号信息以及编译时产生编译错误或者警告时能够显示行号
  • 保留所有的 #pragma 编译器指令,因为编译器要使用它们

实例如下:

extern char *ctermid (char *__s) __attribute__ ((__nothrow__ , __leaf__));
# 846 "/usr/include/stdio.h" 3 4
extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));

extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;

extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 864 "/usr/include/stdio.h" 3 4
extern int __uflow (FILE *);
extern int __overflow (FILE *, int);
# 879 "/usr/include/stdio.h" 3 4
# 2 "mainTestsuite.c" 2
# 3 "mainTestsuite.c"
const char* str = "hello world";
const char astr[] = {"this is array"};

int array[] = {0};

const char* testFunction(int para) {
 int temp;
 int atemp[10] = {0};
 if(para) {
  return str;
 } else {
  return "testmacro";
 }
}

int main(int argc, char *argv[]) {
 printf("main function run .\n");

 printf("testFunction return: %s \n", testFunction(10));

 return 0;
}

编译(compiling)

编译的过程就是将预处理完的文件进行一系列的语法分析,词法分析,语义分析优化后生成对应的汇编代码,这个过程是程序构建的核心部分,也是最复杂的部分

gcc -S 只进行预处理和编译,不进行汇编和链接 生成的是汇编语言文件

 gcc -S hello.c -o hello.s

也可以由 hello.i 生成 hello.s 汇编文件

gcc -S hello.i -o hello.s

也可以直接使用 cc1 命令生成
/usr/lib/gcc/x86_64-pc-cygwin/11/cc1 mainTestsuite.c
使用 cc1 编译源文件
gcc 的 -S 选项本质上就是调用 cc1 命令执行编译过程

实际生成的汇编代码如下:

	.cfi_endproc
.LFE0:
	.size	testFunction, .-testFunction
	.section	.rodata
.LC2:
	.string	"main function run ."
.LC3:
	.string	"testFunction return: %s \n"
	.text
	.globl	main
	.type	main, @function
main:
.LFB1:
	.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	$.LC2, %edi
	call	puts
	movl	$10, %edi
	call	testFunction
	movq	%rax, %rsi
	movl	$.LC3, %edi
	movl	$0, %eax
	call	printf
	movl	$0, %eax
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1:
	.size	main, .-main
	.ident	"GCC: (Uos 8.3.0.3-3+rebuild) 8.3.0"
	.section	.note.GNU-stack,"",@progbits

汇编(assembling)

汇编器的功能就是将汇编代码转换成机器可以执行的指令,每一条汇编指令几乎都对应一个机器指令

gcc -c 只进行预处理,编译和汇编,不进行链接 生成的是 .o 目标文件

gcc -c hello.c -o hello.o

也可以由 hello.i 或 hello.s 生成目标文件 hello.o

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

也可以直接使用 as(assembly) 指令

as mainTestsuite.s -o demo.o

链接(linking)

将生成的 hello.o 文件链接成 hello 可执行文件

gcc hello.o -o hello 

生成静态库

首先生成 汇编后的文件

gcc -c testsuite.c -o testsuite.o
ar rcs libtestsuite.a  testsuite.o

在使用的时候需要链接静态库:

gcc hello.c -static libfoo.a -o hello

也可以使用 -L 制定库的搜索目录,并使用 -l 制定库的名称

gcc hello.c -static -L . -ltestsuite - o hello

生成动态库

可以将其编译动态库/共享库(由于动态库可以被多个进程加载,所以需要 -fPIC 选项生成位置无关的代码)

 gcc foo.c -shared -fPIC -o libfoo.so

也可以使用 -L 和 -l 选项指定库的路径和名称

 gcc hello.c -L. -lfoo -o hello
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐