学习GCC编译器工具

介绍

在 Linux 中,GCC 是 GNU Compiler Collection 的缩写,它是一套开源的编译器工具集。GCC 是一个非常强大和广泛使用的编译器,用于编译和构建多种编程语言的源代码,包括 C、C++、Objective-C、Fortran、Ada 和其他一些语言。

GCC 的主要组件包括以下部分:

前端(Frontend):GCC 提供了针对不同编程语言的前端,用于解析和分析特定语言的源代码,并生成中间表示(如抽象语法树)。

优化器(Optimizer):GCC 包含了一个强大的优化器,用于对中间表示进行优化,以改善生成的机器代码的性能和效率。

后端(Backend):GCC 的后端将优化后的中间表示转换为特定目标体系结构的机器代码,以便在目标平台上执行。

除了编译器本身之外,GCC 还包括了一些辅助工具和库,用于与编译器相关的任务,如链接器(ld)、调试器(gdb)、性能分析工具(gprof)等。

GCC 是一个跨平台的编译器工具集,可以在多种操作系统上使用,包括 Linux、Unix、macOS 和 Windows 等。它被广泛应用于开发各种类型的软件和系统,提供了强大的编译和构建能力,使开发人员能够将高级语言代码转换为可执行的机器代码。

编译过程

程序的编译过程是将高级语言代码(如C、C++、Java等)转换为可执行的机器代码的过程。编译过程通常包括以下几个主要步骤:

1.预处理(Preprocessing):在编译过程的第一步,预处理器会对源代码进行处理。它会根据预处理指令(如#include、宏定义等)展开代码,并去除注释、空格等无关内容。预处理器的输出称为预处理文件。

2.编译(Compiling):在这一步骤中,编译器将预处理文件转换为汇编语言(或中间表示)。编译器会对每个源代码文件进行语法分析、语义分析和优化,并生成与目标平台相关的汇编代码或中间表示。这些代码包含了将高级语言代码转换为机器代码所需的指令和数据结构。

3.汇编(Assembling):汇编器接收编译器生成的汇编代码或中间表示,将其转换为机器码(即目标文件)。汇编器会将汇编代码中的每个指令转换为与目标硬件平台兼容的二进制指令。

链接(Linking):链接器将目标文件与其他需要的库文件(如标准库)进行链接,以创建最终的可执行文件。链接器解决符号引用和符号重定位,将不同目标文件之间的引用关系解析为有效的内存地址。最终生成的可执行文件包含了所有必要的代码和数据,可以在操作系统上运行。

在上述过程中,编译器和汇编器是主要的工具,它们负责将高级语言代码转换为底层机器代码。链接器则负责将目标文件和库文件组合在一起,创建最终可执行文件。整个编译过程是由开发者在开发环境中执行的,以生成可在目标系统上运行的程序。

预处理(Preprocessing):

输入文件:源代码文件(通常以扩展名 .c、.cpp、.java 等表示)
输出文件:预处理文件(通常以扩展名 .i 表示)

编译(Compiling):
输入文件:预处理文件
输出文件:汇编文件(通常以扩展名 .s 表示)或中间表示文件(如 LLVM IR 或中间语言文件)

汇编(Assembling):
输入文件:汇编文件或中间表示文件
输出文件:目标文件(通常以扩展名 .o 表示)

链接(Linking):
输入文件:目标文件、库文件
输出文件:可执行文件(或共享库文件等,具体取决于链接的目标)

需要注意的是,具体的文件类型和扩展名可能会因编程语言和编译器而有所不同。上述列出的文件类型和扩展名是一般情况下的常见表示。

此外还有头文件 .h,静态库 .a 或 .lib 经过预处理的C、C++文件,动态库 .so或.dll。

编译过程示范

示例代码 test.c

#include<stdio.h>
#include<stdlib.h>
int main()
{
	printf("hello\n");
	return EXIT_SUCCESS;
}

默认输出可执行文件a.out

gcc test.c

输出可执行文件 test

gcc test.c -o test

预处理 test.c 文件生成 test.i 文件

gcc -E test.c -o test.i

查看test.i文件内的内容:
在这里插入图片描述
这些预处理指令在预处理阶段被添加到了预处理文件中,以帮助编译器了解源代码的来源和所包含的头文件信息。预处理器在处理源代码时会解析这些指令,并根据需要包含相应的头文件内容。预处理文件中的内容可能非常庞大,上述示例仅展示了一小部分内容。实际的预处理文件通常会包含更多的预处理指令和头文件内容,以及源代码中的宏展开、条件编译等预处理操作的结果。

预处理和汇编test.c文件后生成test.s文件

gcc -S hello.c -o test.s

查看test.s的内容
在这里插入图片描述
以下是对其中几个关键部分的解释:

.file “test.c” 表示下面的指令和数据是来自于文件 “test.c”。
.section .rodata 表示下面的内容是放置在只读数据段(.rodata)中。
.LC0: .string “hello!” 定义了一个标号 .LC0,并将字符串 “hello!” 存储在只读数据段中。
.text 表示下面的内容是放置在文本段(.text)中,即程序的指令代码。
.globl main 声明了全局标号 main,表示这是程序的入口函数。
.type main, @function 声明了 main 标号的类型为函数。
main: 标号 main 表示程序的入口函数开始处。
.cfi_startproc 标记函数的开始。
pushq %rbp 将栈帧指针 %rbp 的值压入栈中。
movq %rsp, %rbp 将栈指针 %rsp 的值赋给栈帧指针 %rbp。
movl $.LC0, %edi 将字符串 .LC0 的地址赋给目标寄存器 %edi。
call puts 调用 puts 函数,将 %edi 中的字符串地址传递给它。
movl $0, %eax 将返回值 0 存储在目标寄存器 %eax 中。
popq %rbp 弹出栈帧指针 %rbp 的值。
ret 返回指令,从 main 函数返回。
.size main, .-main 声明 main 函数的大小。
.ident “GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-28)” 包含了编译器版本信息。
.section .note.GNU-stack,“”,@progbits 控制堆栈是否可执行。
这段汇编代码描述了一个简单的程序,它的功能是将字符串 “hello!” 输出到控制台。在函数 main 中,使用 puts 函数打印了字符串。其他指令涉及到栈帧的操作、函数调用、返回等。最后的部分包含了一些附加信息,如编译器版本和堆栈的相关设置。

这段汇编代码是可执行代码的低级表示,可以被汇编器转换成机器码,并在计算机上执行。

也可以由预处理文件编译到汇编文件

gcc -S hello.i -o hello.s

经过预处理、编译、汇编到目标文件

gcc -c hello.c

从编译文件到目标文件

gcc -c hello.i -o hello.o

从汇编文件到目标文件

gcc -c hello.s -o hello.o

由目标文件到可执行文件

gcc hello.o -o hello

编译多个文件时

gcc hello.c -o hello.o
gcc goodbay.c -o goodbay.o
gcc goodbay.o hello.o - o prog

./prog就行了

GCC常用选项

语言选项:
-c:只编译源文件,生成目标文件而不进行链接。
-E:只进行预处理,输出预处理后的源代码。
-S:只进行编译和汇编,生成汇编代码而不进行链接。
-o :指定输出文件的名称。

调试选项:
-g:生成调试信息,用于调试程序。
-ggdb:生成更详细的调试信息,供使用GDB调试器进行调试。

优化选项:
-O0:关闭优化。
-O1:启用基本优化。
-O2:启用更高级的优化。
-O3:启用所有优化。
-Os:优化代码尺寸。
-Ofast:启用尽可能高的优化,可能违反标准的严格要求。

警告选项:
-Wall:启用大多数警告信息。
-Werror:将警告视为错误,导致编译过程中的警告中止编译。
-Wextra:启用额外的警告信息。
-Wpedantic:启用符合标准的警告信息。

目标选项:
-march=:指定目标处理器的架构。
-m32:生成32位目标文件。
-m64:生成64位目标文件

使用静态库

在Linux中,静态库(Static Library)是一种包含已编译目标代码的文件,它是编译器在链接阶段将目标代码合并到可执行文件中的一种方式。静态库通常具有以下特点:

文件类型:静态库文件通常使用扩展名.a(Archive)作为文件类型标识,例如libexample.a。

存储目标代码:静态库包含一组已编译的目标代码文件(.o文件),这些文件是在编译源代码时生成的。每个目标代码文件都对应着一个源代码文件的编译结果。

符号表:静态库中的每个目标代码文件都包含符号表(Symbol Table),其中包含了函数、变量和其他符号的信息。这些符号表用于链接器解析符号引用,以确保在可执行文件中正确地定位和使用库中的函数和变量。

链接顺序:在链接时,静态库中的目标代码文件按照库的索引顺序被链接到可执行文件中。这意味着如果一个目标代码文件依赖于另一个目标代码文件中定义的符号,那么被依赖的目标代码文件必须在依赖它的目标代码文件之前被链接。

独立性:静态库是与可执行文件独立的,它包含了可执行文件所需的所有目标代码。这意味着当可执行文件运行时,静态库的代码已经被完全合并到可执行文件中,不再依赖于原始的静态库文件。

使用静态库的主要优点是简化了程序的部署和分发,因为可执行文件已经包含了所需的代码,不需要单独传递和安装静态库文件。然而,静态库的缺点是占用了更多的磁盘空间,并且在多个可执行文件之间共享静态库代码的更新变得更加复杂。相比之下,动态库(Dynamic Library)在运行时被加载和链接,可以在多个可执行文件之间共享,减少了磁盘空间的占用和更新的复杂性。

创建一个自己的静态库

编译源代码:使用GCC编译器将源代码编译成目标代码文件(.o文件)。运行以下命令:

gcc -c world.c -o world.o

创建静态库:使用ar命令创建静态库。运行以下命令:

ar rcs world.a world.o

使用静态库:现在你可以在其他程序中使用你创建的静态库。在编译时,将静态库链接到可执行文件中。假设你有一个名为main.c的源文件,使用libexample.a静态库。运行以下命令:

gcc test.c -o main -L. world.a

注意的是我的world和test文件在一起。
在这里插入图片描述

使用动态库

在Linux中,动态库(Dynamic Library)是一种包含共享目标代码的文件,它在运行时被动态加载到内存中,并与可执行文件共享。与静态库相比,动态库具有以下特点:

文件类型:动态库文件通常使用扩展名.so(Shared Object)作为文件类型标识,例如libexample.so。

共享目标代码:动态库包含一组已编译的共享目标代码,这些代码可以被多个可执行文件共享。不同的可执行文件可以在运行时加载同一个动态库,并共享其中的代码段。

符号表:动态库中的符号表包含了函数、变量和其他符号的信息,这些符号可以在运行时由链接器解析。可执行文件在加载动态库时会使用动态链接器将符号引用解析为实际地址,以便正确执行。

独立性和共享性:动态库是与可执行文件独立的,它可以被多个可执行文件共享使用。当多个可执行文件链接到同一个动态库时,它们将共享该库的代码段,减少了磁盘空间的占用,并提供了更好的可维护性和更新性。

动态加载:与静态库不同,动态库在程序运行时才会被动态加载到内存中。动态库可以由程序根据需要进行加载和卸载,这使得程序可以在运行时动态加载不同的库,提供了更大的灵活性和可扩展性。

使用动态库的主要优点是减少了可执行文件的大小,因为共享的代码段只需要在内存中存在一份副本。此外,动态库的更新更加方便,只需要替换动态库文件而不需要重新编译可执行文件。然而,动态库的缺点是在运行时需要动态链接器进行符号解析和加载,稍微增加了一些运行时开销。

总的来说,动态库在Linux系统中扮演着重要的角色,提供了共享和重用代码的机制,使得软件的开发、分发和维护更加灵活和高效。

首先不跟位置相关的目标文件

gcc -c -fPIC your_source_file.c -o your_object_file.o

创建共享库:将目标文件链接成共享库(动态库)。使用以下命令创建动态库

gcc -shared -o libyourlibrary.so your_object_file.o

安装动态库:将生成的动态库安装到系统目录中,以便其他程序可以访问和使用它。使用以下命令将动态库复制到适当的系统目录中(通常是/usr/lib或/usr/local/lib):

sudo cp libyourlibrary.so /usr/lib

就行了

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值