当前位置: 首页 > news >正文

Linux操作系统从入门到实战(八)详细讲解编译器gcc/g++编译步骤与动静态库链接

Linux操作系统从入门到实战(八)详细讲解编译器gcc/g++编译步骤与动静态库链接

  • 前言
  • 一、gcc/g++ 背景知识
    • 为什么有gcc/g++?
  • 二、gcc编译过程四步走
    • 第一步:预处理
    • 第二步:编译
    • 第三步:汇编
    • 第四步:链接
    • 总结一下四步流程
  • 三、动态链接与静态链接
    • 1. 为什么需要链接?
    • 2. 静态链接
      • 2.1 静态链接的过程:
      • 2.2 静态链接的优缺点:
    • 3. 动态链接
      • 3.1 动态链接的过程:
      • 3.2 动态链接的优缺点:
    • 4. 什么是"库"?
    • 5. 如何查看程序依赖的动态库?
    • 6. 静态与动态的核心区别
  • 四、 静态库和动态库
    • 1. 静态库与动态库
    • 2. 静态库和动态库长啥样?
      • 2.1 静态库
      • 2.2 动态库
    • 3. 为什么动态库更受欢迎?
    • 4. gcc默认是动态链接
      • 怎么验证这一点?
    • 静态库没安装?这样补上
      • CentOS系统:
    • 扩展
      • 1. 条件编译有什么用?
      • 2. 为什么非要把代码变成汇编?
      • 3. 什么是"编译器自举"?
  • 五、 gcc其他常用选项(了解即可)
    • 第一类:控制编译步骤的"进度开关"
      • 1. -E:只做预处理,不往下走
      • 2. -S:编译到汇编语言就刹车
      • 3. -c:编译到目标代码就停
    • 第二类:控制输出的"文件管理开关"
      • -o:给输出文件起名字
    • 第三类:控制链接方式的"库开关"
      • 1. -static:强制用静态链接
      • 2. -shared:尽量用动态库
    • 第四类:调试和优化的"功能开关"
      • 1. -g:给程序加"调试标记"
      • 2. 优化选项:-O0 到 -O3(字母O,不是数字0)
    • 第五类:控制警告信息的"提示开关"
      • 1. -w:关闭所有警告
      • 2. -Wall:显示所有警告
    • 常用选项速查表


前言

  • 在上一篇博客中,我们探讨了 Vim 编译器的使用方法
  • 而在本篇博客中,我们将开启对 gcc/g++ 编译器的讲解之旅

我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的Linux知识文章专栏
欢迎来阅读指出不足
https://blog.csdn.net/2402_83322742/category_12879535.html?spm=1001.2014.3001.5482


一、gcc/g++ 背景知识

为什么有gcc/g++?

  • GCC(GNU Compiler Collection)是GNU项目开发的编译器集合最初是为C语言设计的(gcc),后来扩展支持了更多语言。
  • G++是GCC中专门用于编译C++代码的前端工具

之所以需要两个工具,是因为:

  1. 语言差异:C++比C多了很多特性(类、模板、异常处理等),需要特殊处理
  2. 链接需求:C++程序默认需要链接C++标准库(如libstdc++),而C程序不需要
  3. 编译选项:g++会自动启用一些C++必需的编译选项
  • 简单来说:编译C代码用gcc,编译C++代码用g++

二、gcc编译过程四步走

第一步:预处理

预处理就像做饭前的备菜环节,主要帮你处理代码里的"杂事":

  • 宏替换:比如你定义了#define PI 3.14,预处理会把代码里所有的PI换成3.14
  • 文件包含:遇到#include <stdio.h>,就会把stdio.h文件里的内容"复制粘贴"到你的代码里
  • 去注释:代码里//或/* */的注释会被删掉,因为电脑不需要看这些说明
  • 条件编译:比如#if 0 ... #endif中间的代码会被暂时去掉
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

操作方法

  • gcc -E hello.c -o hello.i命令,就能得到预处理后的文件hello.i
  • 这里的-E就像告诉gcc:“做完预处理就停下,别往下走了”,-o是指定输出文件的名字。

在这里插入图片描述

第二步:编译

  • 预处理完的代码还是C语言,电脑还是看不懂
  • 编译阶段就像把中文翻译成"电脑能懂的半成品语言"(汇编语言)。

这一步会先检查你的代码写得对不对:

  • 有没有少写分号?
  • 变量是不是没定义就用了?
  • 函数调用的参数对不对?

如果有错误,会报错让你修改;没错误的话,就生成汇编代码。

操作方法
gcc -S hello.i -o hello.s命令,生成.s后缀的汇编文件。
在这里插入图片描述

  • -S的意思是"只编译到汇编语言,别继续往下做"。
  • 汇编代码里会有很多movadd之类的指令,这是更接近电脑操作的语言。

在这里插入图片描述

第三步:汇编

  • 汇编语言电脑还是不能直接执行,得变成二进制的机器码(0和1)才行
  • 这一步就像把"半成品语言"翻译成"电脑的母语"。

操作方法
gcc -c hello.s -o hello.o命令,生成.o后缀的目标文件
-c的作用是"把汇编代码转成二进制目标文件",.o文件里都是0和1组成的机器码,但现在还不能直接运行。
在这里插入图片描述

在这里插入图片描述

第四步:链接

  • 我们的代码可能用到了别人写的功能(比如printf函数),这些功能放在其他的库文件里。
  • 链接阶段就像组装机器,把我们的目标文件和需要的库文件拼在一起,形成一个能直接运行的程序

操作方法
gcc hello.o -o hello命令,生成可执行文件(Windows里是.exe,Linux里没有后缀)。
现在我们输入./hello(Linux),就能看到程序运行的结果了!
在这里插入图片描述

总结一下四步流程

  1. hello.c(源代码)→预处理→hello.i(预处理后的代码)
  2. hello.i→编译→hello.s(汇编代码)
  3. hello.s→汇编→hello.o(二进制目标文件)
  4. hello.o→链接→hello(可执行文件)

三、动态链接与静态链接

1. 为什么需要链接?

  • 想象我们搭积木,每个源文件(.c)都是一个单独的积木块,编译后变成目标文件(.o)。
  • 这些积木块(.c)各自有不同的功能,但单独一块没法用——比如main.c里调用了add.c里的加法函数,可main.o里并没有这个函数的具体实现。

链接的作用就是:把这些零散的积木块(.o文件)拼在一起,让它们能相互配合工作,最后形成一个完整的"模型"(可执行程序)。

2. 静态链接

静态链接就像搭积木时用胶水把所有积木块粘死——一旦粘好,每个零件都成了整体的一部分,再也分不开。

2.1 静态链接的过程:

  1. 每个.c文件单独编译成.o目标文件(比如add.cadd.omain.cmain.o
  2. 静态链接器把所有.o文件"合并打包",生成一个可执行程序
  3. 这个可执行程序里包含了所有需要的代码(自己写的+调用的库函数,比如printf

2.2 静态链接的优缺点:

优点

  • 运行快:因为所有代码都在一个文件里,程序启动时不用临时找零件
  • 独立运行:拿到这个可执行程序,随便复制到其他电脑,只要系统兼容就能直接跑

缺点

  • 太占空间:如果多个程序都用了printf函数,每个程序里都会有一份printf的代码。就像每个房子都单独装一个一模一样的水龙头,其实完全可以共用
  • 更新麻烦:如果printf函数有bug需要修复,所有用静态链接的程序都得重新编译打包,不能只更新printf这一个零件

静态链接示例
1. 创建源文件

// add.c - 加法函数实现
int add(int a, int b) {return a + b;
}
// main.c - 主程序,调用add函数
#include <stdio.h>
int add(int a, int b);  // 函数声明int main() {int result = add(3, 4);printf("3 + 4 = %d\n", result);return 0;
}

2. 编译与静态链接步骤

# 1. 编译源文件为目标文件
gcc -c add.c -o add.o
gcc -c main.c -o main.o# 2. 创建静态库(可选)
ar rcs libadd.a add.o# 3. 静态链接(方式一:直接链接目标文件)
gcc main.o add.o -o static_program1# 3. 静态链接(方式二:链接静态库)
gcc main.o -L. -ladd -o static_program2

3. 验证静态链接结果

# 查看依赖关系(无动态库依赖)
ldd static_program2
# 输出示例:
#     not a dynamic executable

3. 动态链接

动态链接是为了解决静态链接的"浪费"问题而发明的

  • 它像用可拆卸的螺丝组装积木——程序本身不包含所有零件,而是在运行时才去"借"需要的零件。

3.1 动态链接的过程:

  1. 源文件还是编译成.o目标文件
  2. 链接时不把库函数的代码打包进程序,只记录"我需要某某函数,在某某库文件里"
  3. 程序运行时,系统会找到对应的"共享库"(比如libc.so.6),临时把需要的函数代码"链接"进来使用

3.2 动态链接的优缺点:

优点

  • 节省空间:多个程序可以共用一共享库。比如10个程序都用printf,只需要一份libc.so.6文件,所有程序共享它
  • 更新方便:如果printf函数修复了bug,只需要更新libc.so.6这一个文件,所有用动态链接的程序下次运行时自动用上新功能

缺点

  • 依赖库文件:如果电脑里没有对应的共享库(比如libc.so.6丢了),程序会报错"找不到某某库"
  • 启动稍慢:运行时需要临时找库文件并链接,比静态链接多了一点点准备时间(一般用户感觉不到)

动态链接示例
1. 创建源文件(与静态示例相同)

// add.c - 加法函数实现
int add(int a, int b) {return a + b;
}
// main.c - 主程序,调用add函数
#include <stdio.h>
int add(int a, int b);  // 函数声明int main() {int result = add(3, 4);printf("3 + 4 = %d\n", result);return 0;
}

2. 编译与动态链接步骤

# 1. 编译源文件为位置无关代码(PIC)
gcc -fPIC -c add.c -o add.o# 2. 创建动态库
gcc -shared -o libadd.so add.o# 3. 动态链接
gcc main.c -L. -ladd -o dynamic_program# 4. 运行前需设置动态库搜索路径(临时方法)
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.

3. 验证动态链接结果

# 查看依赖关系
ldd dynamic_program
# 输出示例:
#     linux-vdso.so.1 (0x00007ffd9b7fb000)
#     libadd.so => ./libadd.so (0x00007f8c3f9fc000)
#     libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8c3f81a000)
#     /lib64/ld-linux-x86-64.so.2 (0x00007f8c3f9fe000)

4. 什么是"库"?

  • 前面总提到"库文件",其实库就是一堆现成函数的集合,像一个工具包。

比如:

  • 你写printf("Hello")时,自己的代码里只写了调用命令,并没有实现"怎么在屏幕上显示文字"的具体代码
  • 这些具体代码早就被专家写好,放在了libc.so.6这个库文件里(Linux系统下)
  • 链接的作用就是告诉程序:“你要的printflibc.so.6里,到时候去找它”

5. 如何查看程序依赖的动态库?

Linux系统里有个超好用的命令ldd,可以查看一个程序依赖哪些动态库。比如:

ldd hello

运行后会显示类似这样的内容:

在这里插入图片描述

linux-vdso.so.1 =>  (0x00007fffeb1ab000)
libc.so.6 => /lib64/libc.so.6 (0x00007ff776af5000)
/lib64/ld-linux-x86-64.so.2 (0x00007ff776ec3000)

这说明hello程序运行时需要libc.so.6等库文件,如果这些文件丢失,程序就跑不起来。

6. 静态与动态的核心区别

对比项静态链接动态链接
代码存放所有代码打包进可执行程序程序只存"地址",运行时找库
文件大小较大较小
共享性不共享,每个程序一份共享,多个程序用同一个库
更新方式必须重新编译程序只更新库文件即可
独立性复制到任何地方都能运行依赖系统里的库文件

四、 静态库和动态库

1. 静态库与动态库

  • 前面我们讲了静态链接和动态链接的原理,其实它们背后都依赖"库文件"。
  • 就像工具箱分两种:一种是一次性打包带走的,另一种是大家共用的。
  • 下面我们具体说说这两种库的区别

2. 静态库和动态库长啥样?

库文件就像提前做好的"功能模块包",里面装着各种现成的函数(比如打印、计算等)。但根据链接方式不同,库分两种

2.1 静态库

  • 特点编译链接时,会把库里面的所有代码都复制到你的程序里,相当于"买工具回家"
  • 后缀名
    • Linux系统里叫.a(比如libmath.a,a可以理解为"archive归档")
    • Windows系统里叫.lib(注意:Windows的.lib可能是静态库或动态库的导入库,这里只说静态库的情况)
  • 后果:生成的可执行文件比较大,但一旦生成,就再也不需要原来的库文件了

2.2 动态库

  • 特点编译链接时,只记录库的位置不复制代码,程序运行时才去"借"功能,相当于"用的时候去工具间拿"
  • 后缀名
    • Linux系统里叫.so(比如libc.so.6,so是"shared object共享对象"的缩写)
    • Windows系统里叫.dll(比如kernel32.dll,dll是"dynamic link library动态链接库"的缩写)
  • 后果:生成的可执行文件小,但运行时必须有对应的动态库在场,否则会报错

3. 为什么动态库更受欢迎?

  • 假设网吧有100台电脑,都要装同款游戏
  • 如果用"静态库思路",每台电脑都得把游戏需要的所有库文件(比如图形处理、声音处理的库)复制一份,100台电脑就有100份相同的库

太浪费硬盘空间了。

  • 但用"动态库思路",网吧服务器上只存一份动态库(.so或.dll),100台电脑运行游戏时都去服务器"借用"这份库,不用重复存储。这
  • 样不仅省空间,以后游戏库更新了,只需要改服务器上的那一份,所有电脑都能用上新功能——这就是动态库的优势!

我们平时用的软件、手机APP,大多像网吧的游戏一样,依赖动态库运行

4. gcc默认是动态链接

当你用gcc hello.o -o hello生成可执行文件时,gcc悄悄做了件事:默认使用动态库链接。也就是说,你的hello程序里并没有包含printf这些函数的具体代码,而是依赖系统里的libc.so.6动态库。

怎么验证这一点?

file命令查看可执行文件的属性:

file hello

如果看到类似"dynamically linked"(动态链接)的字样,就说明这是动态链接的程序。

在这里插入图片描述

如果想强制用静态链接,需要加-static参数:

gcc -static hello.o -o hello_static

这时生成的hello_static会包含所有需要的代码(包括libc静态库),文件体积会大很多,但复制到其他Linux系统时,不用管对方有没有libc.so.6都能运行。

静态库没安装?这样补上

很多云服务器或精简系统里,默认没装C/C++静态库(因为大家常用动态库)。如果编译静态链接程序时报错"找不到静态库",可以手动安装:

CentOS系统:

yum install glibc-static libstdc++-static -y

这条命令会安装C语言静态库(glibc-static)和C++静态库(libstdc++-static)。

扩展

1. 条件编译有什么用?

简单说,就是让同一套代码能适应不同场景。比如:

  • 写一个程序,想在Windows和Linux上都能跑,用#ifdef _WIN32#else#endif区分不同系统的代码
  • 调试时想打印详细信息,发布时去掉这些信息,用#ifdef DEBUG控制

2. 为什么非要把代码变成汇编?

汇编语言是"人和电脑的中间翻译官":

  • 高级语言(C、Python)好写但电脑看不懂
  • 机器码(0和1)电脑看得懂但人没法写
  • 汇编语言介于两者之间,既接近机器码,又比机器码好理解,是编译器翻译的必经步骤

3. 什么是"编译器自举"?

简单说就是"自己编译自己":

  • 最早的编译器可能是用机器码或汇编写的
  • 后来人们用C语言写了C编译器,再用这个编译器编译自己的源代码,得到新的编译器
  • 就像用自己做的面包机烤面包,最后面包机可以做出能烤出自己的面包——听起来绕,但这是编译器发展的重要方式

五、 gcc其他常用选项(了解即可)

第一类:控制编译步骤的"进度开关"

还记得gcc编译的四步流程吗?(预处理→编译→汇编→链接)这些选项能让编译过程在指定步骤停下,方便我们查看中间结果。

1. -E:只做预处理,不往下走

  • 作用:只执行预处理(处理#include#define等),做完就停,不生成可执行文件
  • 特点:默认不生成文件,需要用-o指定输出文件,否则结果会直接打印在屏幕上
  • 例子
    gcc -E hello.c -o hello.i
    hello.c预处理后存成hello.i(对应编译第一步)

2. -S:编译到汇编语言就刹车

  • 作用:从源代码一直处理到生成汇编语言,不进行后续的汇编和链接
  • 适用场景:想看看C代码对应的汇编指令长啥样时用
  • 例子
    gcc -S hello.c -o hello.s
    直接把hello.c编译成汇编文件hello.s(对应编译第二步)

3. -c:编译到目标代码就停

  • 作用:走完预处理、编译、汇编三步,生成二进制的目标文件(.o),不进行链接
  • 特点:生成的.o文件不能直接运行,但可以用来后续链接成可执行程序
  • 例子
    gcc -c hello.c -o hello.o
    生成目标文件hello.o(对应编译第三步)

第二类:控制输出的"文件管理开关"

这类选项主要用来指定输出文件的名字或格式,最常用的就是-o

-o:给输出文件起名字

  • 作用:指定生成文件的名称,避免gcc自动起默认名(比如默认生成a.out)
  • 用法-o后面直接跟你想取的文件名
  • 例子
    gcc hello.c -o myprogram
    把编译结果存成myprogram(而不是默认的a.out);
    gcc hello.o -o run 把目标文件链接成名为run的可执行程序

第三类:控制链接方式的"库开关"

这类选项决定程序是用静态库还是动态库链接,和我们之前讲的静态/动态链接直接相关。

1. -static:强制用静态链接

  • 作用:不管系统里有没有动态库,都强制使用静态库链接,把所有代码打包进可执行文件
  • 效果:生成的文件体积大,但可以独立运行(不用依赖系统里的动态库)
  • 例子
    gcc hello.c -static -o hello_static
    生成静态链接的hello_static,复制到其他Linux系统不用怕缺库

2. -shared:尽量用动态库

  • 作用:告诉gcc优先使用动态库链接,生成的文件体积小(默认其实就是动态链接)
  • 注意:生成的程序运行时需要系统里有对应的动态库,否则会报错
  • 例子
    gcc hello.c -shared -o hello_shared
    (实际效果和默认编译差不多,主要用于制作动态库时)

第四类:调试和优化的"功能开关"

这类选项能帮我们调试程序或让程序运行得更快。

1. -g:给程序加"调试标记"

  • 作用:生成调试信息(就像给程序加了"定位器"),让GDB等调试工具能找到代码对应的位置
  • 适用场景:写代码时难免出错,加了-g就能用调试器一步步看程序运行过程
  • 例子
    gcc hello.c -g -o hello_debug
    生成带调试信息的程序,之后可以用gdb hello_debug进行调试

2. 优化选项:-O0 到 -O3(字母O,不是数字0)

  • 作用:控制编译器对代码的优化程度,就像照片美颜的不同级别
    • -O0:不优化(默认),编译快,适合调试(代码和你写的几乎一致)
    • -O1:基础优化,让程序跑快一点,编译时间增加不多
    • -O2:中级优化,比-O1优化得更细致,适合发布程序
    • -O3:最高级优化,会对代码做深度调整(比如循环优化、函数内联),编译慢但程序可能更快
  • 例子
    gcc hello.c -O3 -o hello_fast
    生成优化到最高级的程序,运行速度可能比默认编译的快

第五类:控制警告信息的"提示开关"

这类选项决定编译器是否提醒你代码里的潜在问题,就像老师批改作业时的评语。

1. -w:关闭所有警告

  • 作用:不管代码里有多少不规范的地方,编译器都不提示警告
  • 不推荐:新手最好别用,警告往往是帮你发现错误的好机会

2. -Wall:显示所有警告

  • 作用:开启所有常见警告(Wall可以理解为"All Warnings"),比如变量定义了不用、函数没返回值等
  • 推荐用法:写代码时加上-Wall,让编译器当你的"纠错小助手"
  • 例子
    gcc hello.c -Wall -o hello
    如果代码里有int a;但没用到a,编译器会提示warning: unused variable ‘a’

常用选项速查表

选项作用一句话总结适用场景
-E只做预处理,输出.i文件查看宏替换、头文件包含结果
-S生成汇编代码.s文件学习C代码和汇编的对应关系
-c生成目标文件.o多文件编译时单独处理每个文件
-o指定输出文件名不想用默认的a.out时
-static静态链接,程序独立运行需要移植程序到其他系统时
-g生成调试信息需要用GDB调试程序时
-O3最高级优化发布程序时让运行更快
-Wall显示所有警告写代码时检查潜在问题

以上就是这篇博客的全部内容,下一篇我们将继续探索Linux的更多精彩内容。

我的个人主页
欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的Linux知识文章专栏
欢迎来阅读指出不足
https://blog.csdn.net/2402_83322742/category_12879535.html?spm=1001.2014.3001.5482

非常感谢您的阅读,喜欢的话记得三连哦

在这里插入图片描述

http://www.lryc.cn/news/587628.html

相关文章:

  • Rocket常见问题及解决方案
  • H2 与高斯数据库兼容性解决方案:虚拟表与类型处理
  • 第12章:【系统架构设计师】系统架构设计-数据流风格
  • Oracle中的INSTR函数
  • 衡石科技技术手册--仪表盘过滤控件详解
  • 空间智能-李飞飞团队工作总结(至2025.07)
  • Spring Cloud分布式配置中心:架构设计与技术实践
  • 2025前端面试题
  • (懒人救星版)CNN_Kriging_NSGA2_Topsis(多模型融合典范)深度学习+SCI热点模型+多目标+熵权法 全网首例,完全原创,早用早发SCI
  • 【前端:Typst】--let关键字的用法
  • ethers.js-5–和solidity的关系
  • Popover API 实战指南:前端弹层体验的原生重构
  • 七、深度学习——RNN
  • C语言-流程控制
  • 详解从零开始实现循环神经网络(RNN)
  • 使用 keytool 在服务器上导入证书操作指南(SSL 证书验证错误处理)
  • kafka的部署
  • Android系统的问题分析笔记 - Android上的调试方式 bugreport
  • 论文阅读:WildGS-SLAM:Monocular Gaussian Splatting SLAM in Dynamic Environments
  • 深入浅出Kafka Consumer源码解析:设计哲学与实现艺术
  • Angular 框架下 AI 驱动的企业级大前端应用开
  • Kafka 时间轮深度解析:如何O(1)处理定时任务
  • 【Python】-实用技巧5- 如何使用Python处理文件和目录
  • 计算机网络通信的相关知识总结
  • 基于GA遗传优化的多边形拟合算法matlab仿真
  • vscode/cursor怎么自定义文字、行高、颜色
  • PHP password_hash() 函数
  • 仓储智能穿梭车:提升仓库效率50%的自动化核心设备
  • Ubuntu系统下Conda的详细安装教程与Python多版本管理指南
  • 【软件架构】软件体系结构风格实现