gcc编译构建流程
0. 项目结构
/home/pi/test/
├── src/
│ ├── add/
│ │ ├── add.cpp
│ │ ├── add.h
│ └── log/
│ ├── log.cpp
│ ├── log.h
│ ├── data.h
├── main.cpp
main.cpp代码
// main.cpp
#include "log.h"
#include "add.h"
int main() {return 0;
}
add.cpp和add.h
// add.h
#pragma once
void add(int a, int b);// add.cpp
#include "log.h"
#include "add.h"
void add(int a, int b) {log_function(a+b);
}
log.cpp和log.h
// data.h
#pragma once
struct LogData{int logData;
};
// log.h
#pragma once
void log_function(int res);// log.cpp
#include <iostream>
#include "log.h"
#include "data.h"
void log_function(int res) {std::cout << res << std::endl;
}
使用gcc将上面的项目编译成一个二进制文件。
1. 编译阶段
GCC支持分离式编译(Separate Compilation),这是C/C++语言开发中管理大型项目的核心机制。其本质在于将编译过程拆分为独立编译目标文件和统一链接两个阶段。使用 -c 选项时,GCC仅执行预处理、编译、汇编三个阶段,生成.o目标文件。例如:
gcc -c main.c -o main.o # 生成未链接的二进制目标文件
- 每个源文件独立编译,互不干扰
- 输出文件包含机器码和未解析符号表(函数/变量引用)
1.1 构建每个cpp的目标文件
由于是分离式编译的,我们直接对每个cpp文件执行gcc命令即可
gcc -c main.cpp -o main.o
gcc -c log.cpp -o log.o
gcc -c add.cpp -o add.o
编译的时候报错,说是找不到头文件。仔细想想,确实没有告诉gcc去哪里找这个头文件
gcc -c main.cpp -o main.o
main.cpp:1:10: fatal error: log.h: No such file or directory1 | #include "log.h"| ^~~~~~~
1.2 头文件搜索
在C/C++开发中,GCC编译器对头文件的搜索路径遵循特定规则,其顺序因包含方式(#include<>或#include"")和编译参数的不同而变化。
- 双引号包含(#include “header.h”)优先搜索当前源文件所在目录,如果是尖括号包含(#include <header.h>),则不会搜索当前源文件下的所在目录
- 通过-I参数显式指定的路径(按命令行中的书写顺序)
- 环境变量C_INCLUDE_PATH(C语言)或CPLUS_INCLUDE_PATH(C++)定义的路径
- 系统默认路径:/usr/local/include、/usr/include、GCC版本相关路径(如/usr/lib/gcc/x86_64-linux-gnu/9/include),平台架构特定路径(如/usr/include/x86_64-linux-gnu)
1.3 为每个cpp指定头文件搜索路径
由于每个目标文件是单独生成的,每个目标文件搜索的头文件也可以单独指定了
gcc -c main.cpp -o main.o -I src/add -I src/log
指定-I src/add
可以从src/add
目录下搜索到add.h
文件,指定-I src/log
可以从src/log
目录下搜索到log.h
文件,然后执行add.cpp
的构建
gcc -c add.cpp -o add.o -I src/log
cc1plus: fatal error: add.cpp: No such file or directory
发现找不到add.cpp文件,我们需要进入到src/add
目录下才能找到
cd src/add
gcc -c add.cpp -o add.o -I src/log
add.cpp:1:10: fatal error: log.h: No such file or directory1 | #include "log.h"| ^~~~~~~
但是发现找不到log.h
文件,明明已经指定了log的头文件搜索路径呀,为啥会不对呢?这是因为我们-I src/log
其实指定的是相对路径
1.4 相对路径搜索
所谓的相对路径,其实都是相对于工作目录而言的,啥是工作目录呢?就是gcc执行命令的目录。例如我们是进入到src/add
目录下执行的gcc命令,那么工作目录就是/home/pi/test/src/add
,-I src/log
目录其实就变成了/homg/pi/test/src/add/src/log
(工作目录/相对目录)下搜索log.h
文件,很明显不存在。
一种办法从工作目录定位到其他目录,此时-I ../../src/log
其实就是/homg/pi/test/src/add/../../src/log
,也就是/home/pi/test/src/log
。
cd src/add
gcc -c add.cpp -o add.o -I ../../src/log
另一种办法就是使用绝对路径,但是当你把项目挪到其他地方编译时,就需要把-I
所有的路径都改一下。
cd src/add
gcc -c add.cpp -o add.o -I /home/pi/test/src/log
最常用的办法就是直接指定add.cpp
的路径,还是在test
目录下编译
gcc -c src/add/add.cpp -o src/add/add.o -I src/log
gcc -c src/log/log.cpp -o src/log/log.o
还可以发现,编译log.cpp
的时候并没有指定log.h
和data.h
的路径,因为默认会从当前cpp的路径下搜索相应的头文件。
我猜测,当我们-c src/log/log.cpp的时候,其实相当于隐含的指定了src/log的搜索路径
1.5 统一指定搜索路径
gcc main.cpp src/log/log.cpp src/add/add.cpp -o main
当同时编译多个文件的时候,其实还是单个单个文件编译的,相当于
gcc -c main.cpp -o main.o
gcc -c src/log/log.cpp -o log.o
gcc -c src/add/add.cpp -o add.o
可以看到编译main.cpp的时候并没有指定log.h
和add.h
的搜索路径,依然会报错。通过添加-I src/log -I src/add
解决
gcc main.cpp src/log/log.cpp src/add/add.cpp -o main -I src/log -I src/add
但这其实就变成了每个cpp文件都使用了统一的头文件搜索路径,例如log.cpp我们并不需要指定-I src/add
的搜索路径。慎用!!!
gcc -c main.cpp -o main.o -I src/log -I src/add
gcc -c src/log/log.cpp -o log.o -I src/log -I src/add
gcc -c src/add/add.cpp -o add.o -I src/log -I src/add
1.6 编译阶段不验证符号定义
上面的命令其实会报错,我们生成二进制时会将所有的目标文件.o合并成最终的可执行文件。gcc是编译c语言的,但是我们使用了std::cout
这个函数,这个是c++语言的,在链接的时候gcc从c语言中找不到这个函数,所以报错。
pi@raspberrypi:~/test $ gcc main.cpp src/log/log.cpp src/add/add.cpp -o main -I src/log -I src/add
/usr/bin/ld: /tmp/cc53TCMJ.o: in function `log_function(int)':
log.cpp:(.text+0x14): undefined reference to `std::cout'
/usr/bin/ld: log.cpp:(.text+0x18): undefined reference to `std::cout'
/usr/bin/ld: log.cpp:(.text+0x1c): undefined reference to `std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)'
/usr/bin/ld: log.cpp:(.text+0x20): undefined reference to `std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)'
/usr/bin/ld: log.cpp:(.text+0x24): undefined reference to `std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)'
/usr/bin/ld: log.cpp:(.text+0x28): undefined reference to `std::ostream::operator<<(std::ostream& (*)(std::ostream&))'
/usr/bin/ld: /tmp/cc53TCMJ.o: in function `__static_initialization_and_destruction_0(int, int)':
log.cpp:(.text+0x6c): undefined reference to `std::ios_base::Init::Init()'
/usr/bin/ld: log.cpp:(.text+0x80): undefined reference to `std::ios_base::Init::~Init()'
/usr/bin/ld: log.cpp:(.text+0x84): undefined reference to `std::ios_base::Init::~Init()'
collect2: error: ld returned 1 exit status
问题是为啥编译的时候(gcc -c)不会报错呢?gcc -c 的作用是生成目标文件(.o),仅执行预处理、编译和汇编,不进行链接。在此阶段:
- 语法检查:GCC 检查代码的语法合法性(如括号匹配、分号缺失等)。
- 符号声明验证:确认函数调用是否符合已声明的原型(如参数类型、返回值类型)。
- 不验证符号定义:编译器不会检查函数是否在其他文件中定义,也不会解析外部符号的地址
例如以下代码:
// main.c
void func(); // 声明但未定义
int main() {func(); // 调用未定义的函数return 0;
}
执行 gcc -c main.c 时,编译器仅确认 func 的声明存在,但不会检查其是否实现,因此不会报错。
1.7 编译阶段的符号表作用
1.7.1 标识和区分程序中的实体
在程序代码中存在各种实体,如变量、函数等。符号表会为每个实体分配一个独一无二的标识符,比如变量int a;,其对应的标识符就是a。通过这个标识符,编译器能够区分不同的变量、函数,避免混淆。
符号表还能记录这些实体的作用域。例如,在函数f()中定义的局部变量b,其作用域仅限于f()函数内部,符号表可以帮助编译器明确这一点。
1.7.2 进行类型检查
代码中可能会存在类型错误,比如将整数赋值给指针变量。符号表存储了每个变量和函数的类型信息 。例如,如果变量x为double类型,函数func的返回类型是int。当代码中出现x = func();这样的语句时,编译器可以借助符号表中记录的类型来判断这个赋值操作是否合法,若类型不匹配,就发出警告或报错。
1.7.3 名字解析
当函数调用另一个函数,或者在某个作用域中引用其他作用域的变量(如全局变量被局部作用域引用)时,编译器需要通过符号表来寻找对应的实体。例如,在函数main()中调用函数g()。编译器首先会在符号表中查找g()函数的定义,找到后才能确定其参数列表、返回类型等信息,进而生成正确的调用指令。
1.8 链接阶段的符号表作用
1.8.1 帮助符号解析和地址分配
在链接过程中,多个对象文件(由源文件经编译生成)需要被整合到一个可执行文件中。不同对象文件内部可能引用了其他对象文件中定义的符号(如函数或全局变量) 。符号表提供了一个全局索引来定位这些被引用的符号。
例如,假设在对象文件file1.o中有对函数h()的引用,而函数h()是在对象文件file2.o中定义的。链接器通过查看两个对象文件的符号表来确定符号h在file2.o中的具体位置,并且在最终的可执行文件中为h分配正确的地址,以便在运行时正确地跳转到函数h()的代码处。
1.8.2 支持重定位操作
在生成可执行文件之前,链接器需要将对象文件中的数据和代码重定位到合适的内存地址位置。符号表中的信息(如符号的偏移地址等)对于计算重定位后的地址至关重要。比如,全局变量y在对象文件的符号表中记录了其在该对象文件中的相对偏移地址,链接器结合这个偏移地址和程序的加载基地址,就能计算出变量y在最终可执行文件中的准确地址。
1.9 调试和运行阶段的符号表作用
1.9.1 调试工具的基础
当开发者使用调试器(如 GDB)调试程序时,符号表提供了调试所需的关键信息。调试器可以通过符号表来反向定位代码中的变量和函数,从而允许开发者在运行时查看变量的值、对函数
1.9.2 进行单步执行等操作。
例如,开发者设置断点在函数k()的入口处,调试器依据符号表找到函数k()在可执行文件中的地址,当程序运行到该地址时就会暂停。开发者还可以通过符号表查询函数k()内部的局部变量名称和地址,方便查看这些变量的运行时数据。
1.9.3 动态符号表在运行时的作用
对于动态链接的程序,运行时加载的共享库(如.so文件)中的符号也需要通过符号表来解析。当程序调用共享库中的函数时,运行时环境(如动态链接器)利用共享库的符号表来找到函数的正确入口点,确保程序能够正确访问这些外部库的功能。
相关文章:
gcc编译构建流程
0. 项目结构 /home/pi/test/ ├── src/ │ ├── add/ │ │ ├── add.cpp │ │ ├── add.h │ └── log/ │ ├── log.cpp │ ├── log.h │ ├── data.h ├── main.cppmain.cpp代码 // main.cpp #include "log.h&quo…...

Maven 中央仓库操作指南
Maven 中央仓库操作指南 登录注册 在 Maven Central 登录(注册)账号。 添加命名空间 注册 通过右上角用户菜单跳转到命名空间管理页面: 注册命名空间: 填入你拥有的域名并注册: 刚提交的命名空间状态是Unverified…...

BUUCTF——RCE ME
BUUCTF——RCE ME 进入靶场 <?php error_reporting(0); if(isset($_GET[code])){$code$_GET[code];if(strlen($code)>40){die("This is too Long.");}if(preg_match("/[A-Za-z0-9]/",$code)){die("NO.");}eval($code); } else{highlight…...
clickhouse-1-特性及docker化安装
clickhouse-1-特性及docker化安装 1.核心特性1.1.列式存储与高效压缩1.2.向量化执行引擎1.3.分布式架构与高可用性1.4.多样化的表引擎1.5.实时处理能力2.安装2.1 拉取镜像2.2 创建容器3.连接4.使用4.1.创建数据库5.其他5.1 primary key5.2 ENG…...
Docker核心笔记
一、概述 1、架构 Docker容器基于镜像运行,容器共享宿主机的内核,不会加载额外内核,通过Namespaces(环境隔离)和Cgroups(资源控制)实现隔离,Cgroups会限容器使用资源并控制优先级和统计数据。隔离后的容器仅包含应用所需的用户态依赖 2、安装 安装先卸载再安装,使用的yum…...
log日志最佳实践
log日志最佳实践 1、占位符的使用2、延迟计算 1、占位符的使用 在进行日志打印的时候,推荐使用占位符进行字符串打印,而不是直接使用字符串拼接。原因: 这样可以避免不必要的字符串拼接。使用占位符时,实际字符串拼接由日志框架…...

FreeRTOS--消息队列
一、简介 消息队列是FreeRTOS中用于任务与任务或任务与中断之间数据交换的一种机制,采用FIFO(先进先出)方式管理数据,也可以采用LIFO(后进先出)方式。有点类似全局变量。 1.1 那为什么不直接使用全局变量&a…...

三步快速部署一个本地Windows/Linux大语言模型ChatGLM(环境配置+权重下载+运行)
前言: 最近刚拿到实验室一个装了3张3090显卡的服务器账号,感觉不用来霍霍有点浪费,于是有了部署一个大语言模型的想法,除去下载权重和传文件到服务器上可能也就用了十分钟不到(这下看懂为啥python受众现在这么广了&…...
深入解析Spring Boot与Redis的缓存集成实践
深入解析Spring Boot与Redis的缓存集成实践 引言 在现代Web应用开发中,缓存技术是提升系统性能的重要手段之一。Redis作为一种高性能的内存数据库,广泛应用于缓存场景。本文将详细介绍如何在Spring Boot项目中集成Redis,并探讨其在实际开发…...
leetcode105.从中序与前序遍历序列构造二叉树:前序定根与中序分治的递归重建术
一、题目深度解析与核心挑战 在二叉树的重建问题中,"从中序与前序遍历序列构造二叉树"是一道考察递归分治思想的经典题目。题目要求我们根据一棵二叉树的前序遍历序列和中序遍历序列,重建出该二叉树的原始结构。这道题的核心难点在于如何利用…...
Python二级考试
目录 一、核心知识模块 1. 程序结构 2. 循环结构 3. 组合数据类型 4. 函数与模块 二、重点算法 1. 排序算法 2. 查找算法 三、文件操作 1. 基础文件处理 四、备考建议 五、典型易错点 以下是Python二级考试的复习要点整理,分为知识模块和备考建议&#…...

DeepSeek联网Google搜索引擎
目录: 1、使用背景2、实现代码3、Gradio 的 yield 机制 1、使用背景 比如所有易建联是什么时候退役的?使用大模型对这种实事回答不准确,需要通过联网搜索处理。 正确答案应该是2023年8月29日退役。 2、实现代码 # import gradio as gr# d…...
理论物理:为什么在极低温(接近绝对零度)时,经典理论失效?
经典理论应该是指经典力学和经典统计物理吧,比如牛顿力学、麦克斯韦-玻尔兹曼分布这些。而到了接近绝对零度的时候,物质的状态会发生什么变化呢?比如说超流性、超导性,或者玻色-爱因斯坦凝聚这些现象,这些在经典理论里好像没法解释。 因为在极低温下,粒子的热运动减弱,…...

奈雪小程序任务脚本
功能概述 该脚本用于自动完成奈雪点单小程序的每日任务,包括: 自动检测 Token 有效性自动签到(如果未签到)获取用户基础信息(昵称、手机号)查询当前奈雪币余额记录连续签到天数支持多账号执行,…...

上海医日健集团物联网专利技术领跑智慧药房赛道
在智慧医疗蓬勃发展的浪潮中,上海医日健集团凭借其卓越的创新能力与强大的技术实力,在智慧药房领域崭露头角。集团自主研发的物联网专利技术,正以前所未有的优势,重塑智慧药房运营模式,引领行业迈向新的发展高度。 上…...

基于Java+MySQL实现(Web)图书借阅管理系统
图书借阅管理系统(前后台) 1 需求分析 图书借阅管理系统是模拟学校图书馆实现的一个具有前后台的 Web 系统.对于读者,能够提供全文检索,个性化推荐,借阅等功能.对于管理员,能够提供可视化数据分析,信息管理等功能. 2 技术栈 前端: Layui,jQuery,echarts 后端:Spring Boot,…...

SAR ADC的功耗设计
SAR ADC 由比较器、逻辑和DAC组成,功耗比可能是3:6:1,对于低功耗设计来说,我们需要尽量让DAC的功耗最小,这里来探讨一下CDAC的功耗计算方法。 CDAC从状态1切换到状态2时,需要从Vref buffer上抽拉电荷。C是状态2时连接Vref的总电容,V2就是状态2时接Vref的电容上的电压…...

PP-OCRv5
目录 PP-OCRv5官方效果如下 C封装、C#调用效果 项目 代码 下载 PP-OCRv5官方效果如下 C封装、C#调用效果 项目 代码 using Newtonsoft.Json; using OpenCvSharp; using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; usi…...

nginx的一些配置的意思
1.用这个端口可以访问到nginx 2.工作进程,设置成和cpu核心数一样即可 3.每个工作进程的最大网络连接数。 4.主机名称 设置反向代理时,把server_name设置成ip。 5.反向代理进行转发,localhost指的是nginx所在的机器。 关键字proxy_pass。 …...

Agent模型微调
这篇文章讲解: 把 Agent 和 Fine-Tuning 的知识串起来,在更高的技术视角看大模型应用;加深对 Agent 工作原理的理解;加深对 Fine-Tuning 训练数据处理的理解。 1. 认识大模型 Agent 1.1 大模型 Agent 的应用场景 揭秘Agent核心…...
Android-OkHttp与Retrofit学习总结
OkHttp核心机制与工作流程 面试官:能简单介绍一下OkHttp的工作流程吗? 候选人: 好的,OkHttp的工作流程大致可以分为几个步骤。首先,我们需要创建一个OkHttpClient实例,通常会用建造者模式来配置…...
移远三款主流5G模块RM500U,RM520N,RG200U比较
文章目录 概要一、技术架构差异1. 3GPP协议版本2. 芯片平台与性能3. 频段覆盖与区域适配4. 协议增强与特殊功能 二、功能与应用定位1. 网络兼容性2. 封装与接口扩展 三、典型应用场景总结 概要 本文介绍下移远两款主流5G模块RM500U RM520N RG200U。 一…...
C++引用以及和指针的区别
C++ 引用 引用(reference)是 C++ 中的一种变量类型,是另一个变量的别名。一旦引用被初始化,就不能再改变它所指向的对象。 引用的特点 必须初始化:声明引用时必须立即对其进行初始化。不可更改绑定:一旦引用绑定到某个变量,就不能再指向其他变量。语法简洁:使用引用不…...
firfox 国外版和国内版本账号不互通问题处理
https://blog.csdn.net/sinat_37891718/article/details/147445621 现在国际服的火狐浏览器修改使用国内的账号服务器,需要先在搜索框输入about:config 中改变三项配置,然后重启浏览器,才能正常使用国内的火狐账号服务器 identity.fxaccount…...

Linux基本指令篇 —— whoami指令
whoami 是 Linux 和 Unix 系统中一个简单但实用的命令,全称 Who Am I(我是谁)。它的功能是显示当前登录用户的用户名。以下是关于 whoami 的详细解析: 目录 1. 基本用法 2. 命令特点 3. 实际应用场景 场景 1:脚本中…...
用go从零构建写一个RPC(3)--异步调用+多路复用实现
在前两个版本中,我们实现了基础的客户端-服务端通信、连接池、序列化等关键模块。为了进一步提升吞吐量和并发性能,本版本新增了 异步发送机制 和 多路复用支持,旨在减少资源消耗、提升连接利用率。 代码地址:https://github.com/…...

力扣395做题笔记
题目链接 力扣395 第一次尝试 class Solution {public int longestSubstring(String str, int k) {char[] s str.toCharArray();int n s.length;int[] cnts new int[256];int ans 0;for (int r 0, l 0; r < n; r ) { cnts[s[r]];if (cnts[s[r]] > k) { ans Mat…...
Python-numpy中常用的统计函数及转换函数
numpy中常用的统计函数 numpy中常用统计函数numpy普通统计函数忽略 NaN 值进行统计百分位数 numpy中形状转换函数重塑数组(reshape)展平数组(flatten/ravel)转置(transpose/T) 数据类型的转换使用astype()转…...
【C语言干货】free细节
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、为啥*phead free掉了之后,为啥下面还 提示:以下是本篇文章正文内容,下面案例可供 可以用? 前言参考 一、为…...
网络安全-等级保护(等保) 2-0 等级保护制度现行技术标准
################################################################################ 第二章:现行等保标准要求,通过表格方式详细拆分了等保的相关要求。 GB 17859-1999 计算机信息系统 安全保护等级划分准则【现行】 GB/T22240-2020 《信息安全技术 网…...