引言

距离上一篇博客更新已经过去了大概一两周的时间,而对于 Linux 系统的基本指令以及 Shell 编程的学习其实基本讲解完毕,Linux基础一块的知识就将告一段落了,如果有细节性的知识,我也会及时分享给各位,作为一名正在攀登 Linux C/C++ 开发这座高峰的学习者,最近系统性地学习了 GCC 这个至关重要的工具。它绝不仅仅是一个简单的“编译器命令”,而是一个驱动我们代码变成可执行程序的强大引擎。理解其内部流程:预处理、编译、汇编、链接,对于写出高效、可调试的代码至关重要。而本文即介绍 GCC 这个工具是如何将我们的源码转换为一个可执行性程序的,详细拆解 GCC 编译的每一步。希望能帮助到同样在学习路上的你,也方便自己日后回顾。

1. 初识 GCC:开源的编译基石

1.1 什么是 GCC

GCC的全称是:GNU Compiler Collection (GNU 编译器套件),它的主要作用是将高级语言(C, C++, Objective-C, Fortran, Ada, Go 等)编写的源码翻译为计算机底层能够理解与执行的机器码。或者是转换为更为底层的语言,如汇编语言。

GCC 支持多种操作系统(Linux、Windows、macOS等),同时它并不只是简单的编译器, 更多是一个驱动程序。它本身并不完成所有编译工作,而是根据你给的源代码类型(.c.cpp 等)和参数,智能地调用后台真正的预处理器编译器汇编器链接器等工具来完成整个构建流程。

1.2 GCC 编译流程

当我们编写好了一个源代码文件之后,之前我都不知道程序是如何运行起来的,经过了这一段时间的学习,才对C/C++程序的运行有了一定的理解,当我们编写好了源文件,gcc 将程序编译分为预处理→编译→汇编→链接四个步骤。

1.3 POSIX标准

是由 IEEE (电气和电子工程师协会)指定的一组标准,全称为:可移植操作系统接口(Portable Operating System Interface),定义了不同的操作系统(尤其是类Unix系统)应该为应用程序提供的相同的接口 (API) 和服务。

该标准的核心目的就是促进应用软件与多种类型的操作系统之间的兼容性以及可移植性,也就是说,只要遵循 POSIX 标准编写的程序,理论上可以在任何兼容 POSIX 的操作系统上编译和运行。

1.3.1 POSIX 标准具体内容

系统调用和库内容:定义了操作系统应提供的核心服务,如文件的系统操作、进程管理和线程控制。

Shell 和系统工具:规定了标准命令行接口和一系列基本工具,如 awk 、 echo 等。

程序线程接口 :包含语言、函数等接口规范,使程序能够在任何遵循 POSIX 标准的操作系统中运行。

1.4 安装 GCC

讲了这么多,我们又应该如何安装 GCC 呢?下面我们以 Ubantu22.04 版本的 Linux 操作系统为例,

安装 GCC 的指令如下:

 sudo apt install gcc

根据提示输入y即可。

安装好 GCC 后,我们可以通过如下命令检查安装的gcc版本

gcc --version

2. 揭秘 GCC 编译流程:从 .c/.cpp 到可执行文件的四步舞曲

2.1 实例引入

首先我们在我们 /home/~ 目录下创建一个实例目录

mkdir study_helloworld
cd study_helloworld

 然后我们在目录下编写三个文件,实现最基础的打印 helloworld 的功能。

2.1.1编写源文件
1. main.c
#include "hello.h"int main()
{say_hello();return 0;
}
2. hello.h
#ifndef __HELLO_H__
#define __HELLO_H__void say_hello();#endif
3. hello.c
#include "hello.h"
#include <stdio.h>void say_hello()
{printf("Hello world!\n");
}

我们可以采用如下命令编译可执行文件并执行:

gcc main.c hello.c -o main
./main

可以看到成功输出了 hello world。

其实我们在输入 gcc main.c hello.c -o main 这样一条简单的命令时,背后隐藏着四个精妙的步骤。

2.2 步骤的详细介绍

2.2.1 预处理

预处理的主要任务是对源代码进行文本层次的加工处理,将它们转换成编译器可以识别的形式。

主要进行的操作如下:

  • 头文件包含 (#include): 将被 #include 指定的头文件(.h)内容完整地复制并插入到 #include 指令所在的位置。形成“单一”的、庞大的源文件。

  • 宏展开 (#define): 查找源代码中所有通过 #define 定义的宏,并将其原地替换为定义的值或代码片段。

  • 条件编译 (#ifdef#if#endif#else#elif): 根据指定的条件(通常是宏定义是否存在或值)决定保留或删除某部分代码块。常用于平台适配、功能开关。

  • 删除注释: 移除所有单行 (//) 和多行 (/* ... */) 注释,减少后续处理负担。

被进行了预处理的源文件仍然是纯文本文件,其拓展名一般为.i (C) 或 .ii (C++),同时我们也可以单独进行预处理操作:

# 输出预处理后的C代码到hello.i
gcc -E hello.c -o hello.i# 输出预处理后的C代码到main.i
gcc -E main.c -o main.i

-E:Expand(展开)的缩写,该参数指定gcc执行预处理操作

.i:intermediate(中间的)的缩写,预处理后的源文件通常以.i作为后缀。

执行了上述指令之后我们便可以查看生成的 main.i 的预处理文件

你会看到所有头文件都被塞进来、宏都被替换掉、注释消失了、条件编译后的代码保留下来了。预处理器处理后的文件通常会比原始源文件大,因为它会展开宏和包含其他文件的内容。

2.2.2 编译 

该步骤的主要任务是将预处理后的源代码(.i / .ii翻译成特定处理器架构的汇编语言代码。

主要进行的操作是

  • 语法分析: 检查代码是否符合 C/C++ 语言的语法规则。遇到语法错误会在此阶段报错(syntax error)。

  • 语义分析: 进行更深入的检查,确保代码在逻辑上是有意义的。遇到类型不匹配、未声明标识符等问题会在此阶段报错。

  • 词法分析: 将源代码拆分成有意义的单词,如关键字、标识符、运算符、常量等。

  • 生成中间表示: 编译器内部会将代码转换成一种或多种中间表示形式,便于进行优化和分析。

  • 生成汇编代码: 将优化后的中间表示转换为目标处理器架构的汇编指令。这些指令是机器指令的人类可读(勉强可读)的助记符形式。

经过编译处理之后的文件成为汇编文件后缀名为.s ,这也是一个纯文本文件,你可以用文本编辑器打开查看,里面是像 movlcalladdq 这样的汇编指令。

执行下面的命令对刚刚生成的预处理文件进行单独编译操作:

# 将预处理后的C代码编译成汇编代码hello.s
gcc -S hello.i -o hello.s# 将预处理后的C代码编译成汇编代码main.s
gcc -S main.i -o main.s

-S:Source(源代码)的缩写,该参数指定gcc将预处理后的源码编译为汇编语言。

.s:Assembly Source(汇编源码)的缩写,通常编译后的汇编文件以.s作为后缀。

可以看到里面的内容都是汇编语言的代码。

2.2.3 汇编

该步骤的主要任务是将汇编语言文件翻译成机器指令,并打包成特定格式的目标文件 (Object File)。

在这一过程中所进行的主要操作是:

  • 逐行解析: 读取汇编文件中的每一条指令和数据定义。

  • 生成机器码: 将每条汇编指令一对一地翻译成对应处理器架构的二进制机器指令。这是 CPU 真正能直接执行的代码。

  • 处理数据: 为程序中定义的全局变量、静态变量分配初始存储空间或预留空间。

  • 生成符号表: 创建目标文件内部的符号表 (Symbol Table)。这个表记录了该文件中定义的符号(如函数名、全局变量名)及其位置(地址),以及该文件中引用但未在此文件中定义的符号。

  • 生成重定位信息: 记录文件中那些在链接阶段才能确定最终地址的位置。这些位置在目标文件中是临时的或为0的,需要链接器后续修正。

经过汇编操作的文件是目标文件,通常扩展名为 .o (Linux/Unix) 或 .obj (Windows)。注意这里不同的操作系统的后缀名不同,这是一个二进制文件,包含机器指令,但还不是最终可运行的程序。

执行下面的命令对刚刚生成的汇编文件进行单独汇编操作:

gcc -c main.s -o main.o
gcc -c hello.s -o hello.o

-c:可以被理解为Compile or Assemble(编译或汇编),该参数可以指定gcc将汇编代码翻译为机器码,但不做链接。此外,该参数也可以用于将.c文件直接处理为机器码,同样不做链接

-o:Object的缩写,通常汇编得到的机器码文件以.o为后缀。

到这里,生成的已经是二进制文件了,就不可以使用文本编辑器直接查看该文件了。可以通过下面指令查看 main.o 的内容。

objdump -s main.o

2.2.4 链接

这个阶段由链接器完成,该步骤的主要任务是将一个或者多个目标文件,以及所需的库文件组合到一起,通过解析符号之间引用关系、分配最终的内存地址,生成一个完整的、可直接加载到内存中执行的可执行性文件或者库文件。可以说该步骤的内容最为复杂。

下面介绍三种不同的链接方式:

1. 静态链接 (-static 或默认链接 .a 文件):

 将静态库中实际被用到的目标文件代码完整地拷贝到最终的可执行文件中。优点:程序独立性强,运行时不需要库文件存在。缺点:可执行文件体积大,库更新需重新链接整个程序。

gcc -static main.o hello.o -o main

-static该参数指示编译器进行静态链接,而不是默认的动态链接。使用这个参数,GCC会尝试将所有用到的库函数直接链接到最终生成的可执行文件中,包括C标准库(libc)、数学库(libm)和其他任何通过代码引用的外部库。

2. 动态链接

动态库 (共享库,.so 文件) 中符号的引用信息记录到可执行文件中。运行时由操作系统的动态链接器 负责在程序加载或运行时,将所需的动态库加载到内存,并完成最终的重定位(地址绑定)。优点:可执行文件小,节省内存(多个程序可共享同一份库代码),库更新方便。缺点:程序运行时依赖库文件存在且版本兼容。

方式一:

gcc main.o hello.o -o main

没有添加-static关键字,gcc默认执行动态链接,即glibc库文件没有包含到可执行文件中。

3. 混合链接

有时候需要用到某些静态库静态链接,有时候有需要动态链接,混合链接则结合二者的优点。

执行下面的指令可以将hello.o编译为静态链接库libhello.a

ar crv libhello.a hello.o
# ar:归档命令,用于处理静态库文件。
# crv: ar命令的选项 
# c:创建归档文件
# r:替换归档文件中现有的文件或者向归档文件中添加新文件。
# v:详细模式(verbose mode)

结语:

理解 GCC 编译的四步流程(预处理->编译->汇编->链接)是 Linux C/C++ 开发者的一项基本功。之后我会相继介绍 Makefile 文件的编写,C/C++的动态链接库与静态链接库等区别。最初我只知道点击编辑器上边的 run 按钮就能运行程序,现在明白了这背后精妙的四步转换。多动手实践,,是巩固这些知识的最佳途径希望这篇博客能对正在学习 C/C++ 编程的同学有所帮助,如有错误或不足之处,欢迎在评论区留言指正。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/95544.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/95544.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/95544.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

云服务器运行持续强化学习COOM框架的问题

1 环境要求 下载地址&#xff1a;https://github.com/TTomilin/COOM tensorflow 2.11以上 python 3.9以上 tensorflow2.12.0&#xff0c;需要安装tensorflow-probability0.19 2 修改代码 COOM/wrappers/reward.py 将 from gym import RewardWrapper修改为 from gymnasium impor…

MyBatis Interceptor 深度解析与应用实践

MyBatis Interceptor 深度解析与应用实践 一、MyBatis Interceptor概述 1.1 什么是MyBatis Interceptor MyBatis Interceptor&#xff0c;也称为MyBatis 插件&#xff0c;是 MyBatis 提供的一种扩展机制&#xff0c;用于在 MyBatis 执行 SQL 的过程中插入自定义逻辑。它类似…

【自动化测试】Web自动化测试 Selenium

&#x1f525;个人主页&#xff1a; 中草药 &#x1f525;专栏&#xff1a;【Java】登神长阶 史诗般的Java成神之路 测试分类 了解各种各样的测试方法分类&#xff0c;不是为了墨守成规按照既定方法区测试&#xff0c;而是已了解思维为核心&#xff0c;并了解一些专业名词 根…

2025 电赛 C 题完整通关攻略:从单目标定到 2 cm 测距精度的全流程实战

摘要 2025 年全国大学生电子设计竞赛 C 题要求“仅用一颗固定摄像头”在 5 s 内完成 100 cm~200 cm 距离、误差 ≤2 cm 的单目测距&#xff0c;并实时显示功耗。本文整合国一选手方案、CSDN 高分博文、B 站实测视频及官方说明&#xff0c;给出从硬件选型→离线标定→在线算法→…

Day 10: Mini-GPT完整手写实战 - 从组件组装到文本生成的端到端实现

Day 10-2: Mini-GPT完整手写实战 - 从组件组装到文本生成的端到端实现 📚 今日学习目标 掌握GPT架构组装:将Transformer组件组装成完整的生成模型 理解生成式预训练:掌握自回归语言建模的核心机制 端到端代码实现:从数据预处理到模型训练的完整流程 文本生成实战:训练Mi…

深入解析Prompt缓存机制:原理、优化与实践经验

深入解析Prompt缓存机制&#xff1a;原理、优化与实践经验 概述 在大型语言模型应用中&#xff0c;API请求的延迟和成本始终是开发者关注的核心问题。Prompt缓存&#xff08;Prompt Caching&#xff09;技术通过智能地复用重复内容&#xff0c;有效减少了API响应时间和运行成本…

CV 医学影像分类、分割、目标检测,之【3D肝脏分割】项目拆解

CV 医学影像分类、分割、目标检测&#xff0c;之【3D肝脏分割】项目拆解第1行&#xff1a;from posixpath import join第2行&#xff1a;from torch.utils.data import DataLoader第3行&#xff1a;import os第4行&#xff1a;import sys第5行&#xff1a;import random第6行&a…

Mybatis学习笔记(七)

Spring Boot集成 简要描述&#xff1a;MyBatis-Plus与Spring Boot的深度集成&#xff0c;提供了自动配置、启动器等特性&#xff0c;大大简化了配置和使用。 核心概念&#xff1a; 自动配置&#xff1a;基于条件的自动配置机制启动器&#xff1a;简化依赖管理的starter配置属性…

机器人伴侣的智能升级:Deepoc具身智能模型如何重塑成人伴侣体验

引言&#xff1a;机器人伴侣市场的技术变革需求随着人工智能技术的飞速发展和人们情感需求的多元化&#xff0c;机器人成人伴侣市场正在经历前所未有的增长。传统机器人伴侣已经能够满足基础的交互需求&#xff0c;但在智能化、情感化和个性化方面仍存在明显不足。这正是深算纪…

metabase基础使用技巧 (dashboard, filter)

这是metabase系列分享文章的第2部分。本文将介绍metabase的基础概念和使用介绍 question question是metabase中提供的通过UI化操作就能实现简单的 快捷 直接的BI查询。 点击右侧的New -> Question即可创建Question&#xff0c;可以理解为一个格式化的查询&#xff1a; 这里…

机器人成人伴侣的智能化升级:Deepoc具身模型赋能沉浸式体验

引言&#xff1a;成人机器人市场的技术革新需求随着人工智能和机器人技术的快速发展&#xff0c;成人陪伴机器人行业正经历从简单机械运动到智能化交互的转型。据市场研究数据显示&#xff0c;全球成人机器人市场规模预计将在2026年突破100亿美元&#xff0c;年复合增长率保持在…

Go语言企业级权限管理系统设计与实现

最近跟着学长再写河南师范大学附属中学图书馆的项目&#xff0c;学长交给了我一个任务&#xff0c;把本项目的权限管理给吃透&#xff0c;然后应用到下一个项目上。 我当然是偷着乐呐&#xff0c;因为读代码的时候&#xff0c;总是莫名给我一种公费旅游的感觉。 本来就想去了解…

Java应用快速部署Tomcat指南

将Java应用部署到Apache Tomcat服务器是开发Web应用过程中常见的任务。Tomcat是一个免费且开源的Servlet容器,它为Java应用提供了运行环境。本文将介绍如何准备你的Java应用,并将其部署到Tomcat服务器上。 Java 应用部署 tomcat 的根目录结构 Tomcat中默认网站根目录是$CAT…

Java 学习笔记(基础篇2)

1. 分支结构① if 语句&#xff1a;(1) 双分支&#xff1a;if (条件) {// 语句体1 } else {// 语句体2 }(2) 多分支if (条件1) {// 语句体1 } else if (条件2) {// 语句体2 } else {// 语句体N }② switch 语句&#xff1a;(1) 语法&#xff1a;如果都不是&#xff08;default&…

谷歌云代理商:用 AI 启航,Gemini 重塑旅游酒店行业新体验

本文由谷歌云谷歌地图官方授权代理商、高级合作伙伴 CloudAce云一 整理发布。谷歌云谷歌地图在中国授权代理商名单&#xff1a;Cloud Ace云一&#xff0c;全球20分公司&#xff0c;国内核心城市多个据点&#xff0c;谷歌云与谷歌地图代理商、顶级合作伙伴&#xff08;Premier P…

springboot+vue实现通过poi完成excel

前端1、按钮<el-buttontype"text"size"mini"click"handleExport">导出</el-button>2、方法//导出async handleExport() {if (!this.activityId) {this.$message.warning(活动ID不存在);return;}try {this.loading true;const res …

JMeter性能测试详细版(适合0基础小白学习--非常详细)

01性能测试的概念 02性能测试的概念 基准测试 负载测试 稳定性测试 其他&#xff1a;并发测试、压力测试、回归测试等 压力测试就是在系统强负载的情况下&#xff0c;是否会出现功能隐患问题&#xff0c;出现问题后是否可以尽快恢复 负载测试和压力测试的区别: 1,核心目标不…

QT6(创建第一个QT项目)

编写第一个QT项目 QT官网 安装完QT后的界面 创建第一个项目 这里我们选择第一个就好 下一步 下一步 选择CMake&#xff0c;QMake是QT的CMAKE&#xff08;现在官方自己都不推荐了&#xff09; 下一步 选择QWidget我们先创建一个最简单的窗口程序 QMainWindow&#xff1a;主窗…

Golang指针操作

在 Go 语言&#xff08;Golang&#xff09;中&#xff0c;* 和 & 是与指针相关的两个重要操作符。 理解它们对于掌握 Go 的内存管理和函数参数传递机制非常关键。 文章目录一、& 操作符&#xff1a;取地址&#xff08;Address-of&#xff09;示例&#xff1a;二、* 操…

微服务从0到1

微服务从0到1实施步骤与注意事项一、核心实施步骤‌‌需求分析与架构设计‌‌明确业务边界‌&#xff1a;根据业务模块&#xff08;如用户管理、订单系统&#xff09;划分服务职责&#xff0c;避免服务职责重叠或耦合‌。‌定义接口契约‌&#xff1a;通过 OpenAPI/Swagger 规范…