首页 > 系统相关 >Linux操作系统下编译、链接过程详解

Linux操作系统下编译、链接过程详解

时间:2024-07-29 16:53:41浏览次数:13  
标签:文件 gcc 操作系统 -- Wl 编译 详解 Linux 链接

gcc和g++的区别:

gcc和g++是GNU编译器集合中的两个不同的编译器,它们之间的主要区别在于它们所针对的编程语言以及它们的行为和功能。

1. 编译器的目标语言:gcc是用于编译C语言的编译器,而g++是用于编译C++语言的编译器。因此它们分别用于编译不同的源代码文件;

2. 语法支持:gcc和g++对于各自的目标语言有着不同的语法支持。gcc支持C语言的语法和功能,包括C11、C99等版本的标准,而g++则支持C++语言的语法和功能,包括C++11、C++14、C++17等版本的标准;

3. 标准库链接:gcc和g++默认链接的标准库不同。gcc会链接C标准库(libc),g++会链接C++标准库(libstdc++),是因为C和C++标准库在实现和功能上有所不同;

4. 标准扩展:g++提供了对C++语言的一些扩展支持,例如模板、异常处理、运算符重载等C++特有的功能;

5. 链接器行为:g++会自动链接C++运行时支持库(libstdc++),以提供C++语言所需的特定运行时支持,相比之下,使用gcc编译C程序时,默认不会自动链接C运行时的支持库(libc)。

gcc/g++的编译过程:

gcc/g++编译过程有四大步骤:预处理、编译、汇编、链接   ;以下面的C代码做示例:test.cpp

1. 预处理:

预处理是读取c源程序,对其中的伪指令(以#开头的指令,也就是宏)和特殊符号进行“替代”处理;经过此处理,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义跟没有经过预处理的源文件是相同的,仍然是C文件,只是内容有所不同。

预处理的过程主要处理包括以下过程:

  • 将所有的#define删除,并且展开所有的宏定义;
  • 处理所有的条件预编译指令,比如#if #ifdef #elif #else #endif等;
  • 处理#include 预编译指令,将被包含的文件插入到该预编译指令的位置;
  • 删除所有注释 “//”和”/* */”;
  • 添加行号和文件标识,以便编译时生成调试用的行号及编译错误警告行号;
  • 保留所有的#pragma编译器指令,因为编译器需要使用它们。

预处理命令生成.i文件:gcc/g++ -E test.cpp -o test.i   //-E只对其预处理,-o给文件取别名;

2. 编译:

 编译所做的工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码(计算机所认识的01语言)。

 编译命令生成.s汇编文件:gcc/g++  -S test.i -o test.s(-S只进行编译和汇编,生成汇编代码而不进行链接)

 使用不同的交叉编译器编译同一个test.i文件生成的汇编文件也不相同,这也是C语言可移植性的一种体现。

3. 汇编:

汇编的过程实际上指把汇编语言代码翻译成目标机器指令的过程,对于被翻译系统处理的每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。

目标文件由段组成,通常一个目标文件中至少有两个段:

  • 代码段(文本段):该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般不可写;
  • 数据段:主要存放程序中要用到的各种常量、全局变量、静态的数据。一般数据段都是可读,可写,可执行的;

汇编命令生成.o文件:gcc/g++ -c test.s -o test.o    //-c汇编文件,不链接

4. 链接:

 链接的主要工作就是将有关的目标文件(如:库函数、其他程序中的变量等)彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体,也就是生成可执行程序

根据指定的库函数的链接方式的不同,链接处理可分为两种:

  • 静态链接

  gcc/g++ test.o -o test-static//静态链接编译

  • 动态链接(运行时把所需要的文件进行链接,程序的可扩展性和兼容性更高)

 gcc/g++ test.o -o test//动态链接编译

静态链接:静态链接是指在编译阶段直接把静态库加入到可执行文件中去,这样导致的结果是可执行文件会比较大(即在编译时就进行链接)

动态链接:动态链接是指在链接阶段仅仅只加入一些描述信息,而程序执行时再从操作系统中把相应的动态库加载到内存中去(即在运行时进行链接)

编译参数说明:

gcc/g++支持多种参数,这些参数用于控制编译过程、指定文件和目录、选择编译模式等,下面是一些常用的编译器参数和解释:

1. 编译器控制参数:

-c:只进行编译,生成目标文件而不进行链接;

-E:只进行预处理,生成预处理后的原代码;

-S:只进行编译和汇编,生成汇编代码而不进行链接;

-o <文件>:指定生成的目标文件或可执行文件的名称。

2. 语言和标准库参数:

-std=<标准>:指定要使用的语言标准,如-std=c++11、-std=c++17等;

-nostdinc:禁止使用默认的标准库头文件;

-nostdlib:禁止使用默认的标准库;

-l<库>:链接所指定的库文件,如-lm表示链接数学库;

-L<目录>:指定要链接库文件的搜索路径;

-I:指定需要的头文件。

3. 调试和优化参数:

-g:生成调试信息,用于gdb调试;

-O<级别>:指定优化级别,如:-O0(禁止优化)或-O2(启动常用的优化);

-Wall:显示所有警告信息;

-Werror:见所有警告视为错误。

4. 预处理器、汇编器、链接器参数:

汇编器是将高级编程语言源代码转换为机器码的工具

-Wp<参数>-Wpreprocessor <参数>将参数传递给预处理器,如:

-Wp,-D<宏>:编译时定义一个宏;

-Wp,-U<宏>:编译时取消定义一个宏;

-Wp,-I<目录>:指定头文件的搜索目录;

-Wp,-include<文件>:在编译之前先包含所指定的文件;

-Wa<参数>-Wassembler <参数>将参数传递给汇编器,如:

-Wa,-ahl:生成带有行号的汇编代码(包括C/C++源代码行号);

-Wa,-gstabs:生成包含调试信息的汇编代码;

-Wa,-mfpu=vfp:指定FPU(浮点计算单元选项);

-Wa,-march=armv8-a:指定CPU架构选项、-Wa,-march=<架构>:指定其他架构选项;

-Wl<参数>-Wlinker <参数>将参数传递给链接器,如:

-Wl,--as--needed(告诉链接器仅在需要具体库时才链接该库,此时链接器就不再强制遵循命令行参数的链接顺序);

-Wl,--no-as-needed(确保特定库的始终在其他库之前链接,可使用该参数);

-Wl,--start-group和-Wl,--end-group这两个参数用于将链接的范围定义在一对花括号之间,并且可以保证范围内的链接库按照正确的顺序进行链接。

5. 其他参数:

-v显示详细的编译器输出信息;

-save-temps:保留中间文件,不删除;

-save-temps=<args>:不删除中间文件,是一个目录名;

-no-canonical-prefixes:构建相对于其他gcc组件的前缀时,不规范化路径;

-pipe:使用管道而不是中间文件;

-time:计时每个子进程的执行时间;

-pthread:表示为多线程程序需要链接线程库。

参数-fPIC的作用:

-fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。

gcc -shared -fPIC -o 1.so 1.c

PIC使得.so文件的代码段变为真正意义上的共享,如果不加-fPIC,则加载.so文件的代码段时,代码段引用的数据对象需要重定位, 重定位会修改代码段的内容,这就造成每个使用这个.so文件代码段的进程在内核里都会生成这个.so文件代码段的copy。每个copy都不一样,取决于这个.so文件代码段和数据段内存映射的位置。也就是不加fPIC编译出来的so,是要再加载时根据加载到的位置再次重定位的(因为它里面的代码并不是位置无关代码)如果被多个应用程序共同使用,那么它们必须每个程序维护一份.so的代码副本了(因为.so被每个程序加载的位置都不同,显然这些重定位后的代码也不同,当然不能共享)我们总是用fPIC来生成.so,也从来不用fPIC来生成.a;fPIC与动态链接可以说基本没有关系,libc.so一样可以不用fPIC编译,只是这样的so必须要在加载到用户程序的地址空间时重定向所有表目。因此,不用fPIC编译.so并不总是不好。

Linux与链接器相关的编译选项说明:

链接组:

-Wl,--start-group -Wl,--whole-archive -Wl,--no-whole-archive -Wl,--end-group是一组常用的编译选项,用于控制链接器对库的链接顺序:

  • -Wl,--start-group:表示开始一个新的链接组,在该组中的库将被循环链接,直到遇到--end-group;

  • -Wl,--whole-archive:用于告诉编译器对接下来的所有库都执行完整链接,而不仅仅是解决未定义符号;

  • -Wl,--no-whole-archive:结束完整链接的范围,后续的库将恢复到默认的按需链接;

  • -Wl,--end-group:表示结束当前链接组。

禁止自动添加链接库的依赖项:

-Wl,--no-as-needed是一个编译选项,用于告诉编译器在链接过程中不要根据符号的依赖关系自动添加被链接库的依赖项:

默认情况下,链接器会根据目标文件和库之间的符号依赖关系来确定需要链接的库。

这意味着如果一个库中的某个符号被目标文件使用,链接器会自动将该库添加到最终生成的可执行文件或共享库中。

然而,有时候我们可能希望禁止链接器根据这种自动依赖机制添加库的依赖项。使用-Wl,--no-as-needed 选项可以实现这一点。

它告诉链接器不要自动添加被链接库的依赖项,即使目标文件使用了该库中的符号。这样可以避免由于库之间的依赖顺序导致的链接错误。

请注意,-Wl,--no-as-needed 是一个链接器选项,需在编译命令中加入该选项才能生效。

减小输出文件的大小并提高程序运行效率:

-Wl,--gc-sections -Wl,-rpath都是与链接器相关的编译选项

  • -Wl,--gc-sections:这个选项时用于告诉编译器在最终生成的可执行文件或共享库中去除未使用的代码和数据段(garbage collection of sections),通过启用该选项,可以减小输出文件的大小,去除未使用的代码和数据,提高程序的运行效率。

  • -Wl,-rpath:这个选项用于指定运行时库搜索路径(runtime library search path),当运行生成可执行文件或共享库时,系统会搜索指定的路径以及查找所需的动态链接库,可以通过使用该选项来指定自定义的运行时库搜索路径,以确保程序在运行时能够正确地找到所需的库文件。

注意:-Wl,--gc-sections 和 -Wl,-rpath 通常作为传递给编译器的参数,其中 -Wl 是将选项传递给链接器的方式。

gcc编译时开启编译选项实现代码的静态扫描

使用gcc编译程序时可以开启特定的编译选项来实现对代码的静态扫描-赋值解决编译警告,使得代码安全性更高:

  • -Wall:开启常见的警告;

  • -g:开启调试打印,支持gdb调试;

  • -Olevel:优化等级level为0~3,3为最高;

  • -Werror:编译警告变为编译错误,会导致编译不通过;

  • -w:禁止所有警告;

  • -fanalyzer:开启静态分析(gcc 10版本及以上);

  • -Wno-error=:指定某个警告不变成错误;

  • -Wfatal-errors:有一个编译error就终止;

  • -Wextra:开启除-Wall以外的多个警告;

  • -fanalyzer-verbose-edges:打印fanlyzer底层的调试信息,控制流(不常用);

  • -fanalyzer-verbose-state-changes:打印fanalyzer底层调试信息,状态变化(不常用);

  • -fanalyzer-verbosity=level:设置打印等级level为0~4,4最详细;

  • -Wshadow:同名变量在不同作用域被定义;

  • -Winline:某些函数inline失败时打印;

  • -Wduplicated-branches:if和else分支执行语句内容一样;

  • -Wduplicated-cond:if条件重复;

  • -Walloc-size-larger-than=byte-size:分配堆内存大于某个数值byte-size时;

  • -Wlarger-than=byte-size:单个栈变量内存大于某个数值byte-size时;

  • -Wframe-larger-than=byte-size:栈帧大小大于某个数值byte-size时;

  • -Wstack-usage=byte-size:函数调用栈内存大于某个数值byte-size时;

  • -Wunsafe-loop-optimizations:循环无法被优化;

  • -Wpedantic:是否符合ANSI/ISO C标准;

  • -Wunused-macros:已定义但是位使用的宏定义;

  • -Wcast-qual:可读写双指针转换成const类型;

  • -fno-inline:禁止内联展开。

静态库和动态库的优缺点:

静态库和动态库是在编程中常用的两种库文件形式。它们的区别在于链接方式和引用方式不同:

• 静态库:在编译时将库文件的代码完全拷贝到可执行程序中,因此无需依赖外部库文件。以 .a 或 .lib 作为扩展名。

• 动态库:在程序运行时动态加载库文件,程序只需要引用动态库的入口即可使用其中的函数或资源。以 .so 或 .dll 作为扩展名。

以下是静态库和动态库的优缺点:

静态库的优点:

• 使用简单,方便移植,无需关注依赖情况。

• 执行速度快,因为代码已经被完整地嵌入到可执行程序中。

• 可以避免由于库文件版本变化导致的兼容性问题,保证程序稳定性。

静态库的缺点:

• 相对占用更多的磁盘空间和内存,尤其是在多个程序共享同一个库时会重复消耗系统资源。

• 更新库或修改源代码后需要重新编译和链接,修改后的程序必须重新发布给用户。

动态库的优点:

• 共享库可以被多个程序共用,节省磁盘空间和内存空间。

• 对于多个程序使用相同的库,在系统中只需存在一份动态库。

• 更新库和修改程序时,只需要替换可执行文件或者动态库即可,不必重新编译链接。

动态库的缺点:

• 要求开发者安装正确版本的库文件,且无法控制用户环境下的库文件版本,存在兼容性风险。

• 相对于静态库而言,会存在一定的运行时开销,由于需要在每次程序运行时加载和卸载库文件。

标签:文件,gcc,操作系统,--,Wl,编译,详解,Linux,链接
From: https://blog.csdn.net/qq_43630324/article/details/140774599

相关文章

  • linux shell read 按列读取txt文本
    前言全局说明一、说明通常情况下,如果文本里有多列数据,会先读入,然后用grep和awk先拆分成行,在拆分成列。这样费时费力,遇到特殊字符行,还不好处理。在解决别的问题时候,无意发现read有直接按列读取的功能。二、文件2.1存放两列数据的文件文件名:list.txt1libCommonA......
  • Linux shell mktemp -d命令生成临时文件
    前言全局说明一、说明二、mktemp命令2.1创建临时文件mktemp2.1创建临时目录mktemp-d三、命令行示例mktempll/tmp/tmp.fvi5gFbDgr四、sh脚本使用4.1创建tmpfile=$(mktemp)4.2删除rm"$tmpfile"免责声明:本号所涉及内容仅供安全研究与教学使用......
  • linux科研武器库 - 文件数量统计 - ls -l | grep "^-" | wc -l
    使用场景:文件数量统计,在科研场景中,更多是用于检验、核对数据集的样本数量,防止数据遗漏等意外情况。常用命令:ls-l|grep"^-"|wc-l作用:统计当前目录下,文件的个数(不包括目录/文件夹)ls-lR|grep"^-"|wc-l作用:统计当前目录下,文件的个数(包括子目录中的文件)ls-l......
  • T3/A40i支持Linux-5.10新内核啦,Docker、Qt、Python统统升级!
    自2021年创龙科技推出全志国产化率100%的T3/A40i工业核心板后,不到两年时间已超过800家工业客户选择创龙科技T3/A40i平台。随着客户产品的不断升级与迭代,部分“能源电力”、“工业自动化”行业客户对T3/A40i的Linux版本提出了更高要求,主要涉及Docker、Qt、Python等组件特性。秉持......
  • linux 打印my.txt文件的第10-15行
    #打印my.txt文件的第10-15行sed=streameditorsed-n'10,15p'my.txt#打印my.txt文件的第10-15行awk是三个人名字各取了一个字母awk'NR>=10&&NR<=20'my.txt常用的awk命令示例:打印文件的所有行:awk'{print$0}'filename打印文件的第10行到第20行:awk'NR>=10......
  • TCP协议详解
    TCP协议详解TCP(TransmissionControlProtocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。它在网络通信中扮演着至关重要的角色,尤其是在需要保证数据完整性和顺序性的应用场景中。以下是对TCP协议的详细解析,包括其工作原理、特点、应用场合以及关......
  • AI大模型Prompt提示词工程使用详解
    AI大模型Prompt提示词工程使用详解在人工智能(AI)的浩瀚宇宙中,大型预训练模型(LargeLanguageModels,LLMs)如GPT系列、BERT等,以其卓越的自然语言处理(NLP)能力,正逐步改变着人类与机器交互的方式。这些模型不仅能够理解和生成人类语言,还能在多种任务上展现出惊人的创造力和适应......
  • rsync命令详解
     rsync命令是Linux和其他Unix-like系统上一个非常强大的命令行工具,主要用于数据同步和文件传输。它的名字是"remotesync"的缩写,但不仅限于远程同步,也支持本地文件和目录之间的同步。rsync的主要优势在于其高效的增量传输方式,即只传输源和目标之间发生变化的文件块,而不是整个文......
  • 如何根据Linux Kernel Mailing List打patch
    Linux内核正在不断开发和改进。每天的补丁都会提交到Linux内核邮件列表(LKML)。其中一些补丁被接受并合并到主流Linux内核中,供用户使用,而其他补丁则永远无法使用。有时从LKML获取补丁是有用的,例如,如果你在内核中开发,或者只是因为你想保持在前沿。另一个原因可能是,您需要向LKML提出......
  • SoC片上系统详解
    在当今这个科技日新月异的时代,SoC(SystemonChip,系统级芯片或片上系统)作为集成电路技术的巅峰之作,正逐步渗透到我们生活的方方面面。从智能手机到智能家居,从工业控制到物联网设备,SoC以其独特的技术优势和广泛的应用场景,引领着科技潮流,推动着社会进步。本文将从技术角度深入剖析SoC......