gcc编译构建流程

news2025/5/27 13:48:49

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 directory
    1 | #include "log.h"
      |          ^~~~~~~

1.2 头文件搜索

在C/C++开发中,GCC编译器对头文件的搜索路径遵循特定规则,其顺序因包含方式(#include<>或#include"")和编译参数的不同而变化。

  1. 双引号包含(#include “header.h”)​​优先搜索当前源文件所在目录,如果是尖括号包含(#include <header.h>)​​,则不会搜索当前源文件下的所在目录
  2. 通过-I参数显式指定的路径(按命令行中的书写顺序)
  3. 环境变量C_INCLUDE_PATH(C语言)或CPLUS_INCLUDE_PATH(C++)定义的路径
  4. 系统默认路径:/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 directory
    1 | #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.hdata.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.hadd.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文件)中的符号也需要通过符号表来解析。当程序调用共享库中的函数时,运行时环境(如动态链接器)利用共享库的符号表来找到函数的正确入口点,确保程序能够正确访问这些外部库的功能。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Maven 中央仓库操作指南

Maven 中央仓库操作指南 登录注册 在 Maven Central 登录&#xff08;注册&#xff09;账号。 添加命名空间 注册 通过右上角用户菜单跳转到命名空间管理页面&#xff1a; 注册命名空间&#xff1a; 填入你拥有的域名并注册&#xff1a; 刚提交的命名空间状态是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…

FreeRTOS--消息队列

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

三步快速部署一个本地Windows/Linux大语言模型ChatGLM(环境配置+权重下载+运行)

前言&#xff1a; 最近刚拿到实验室一个装了3张3090显卡的服务器账号&#xff0c;感觉不用来霍霍有点浪费&#xff0c;于是有了部署一个大语言模型的想法&#xff0c;除去下载权重和传文件到服务器上可能也就用了十分钟不到&#xff08;这下看懂为啥python受众现在这么广了&…

DeepSeek联网Google搜索引擎

目录&#xff1a; 1、使用背景2、实现代码3、Gradio 的 yield 机制 1、使用背景 比如所有易建联是什么时候退役的&#xff1f;使用大模型对这种实事回答不准确&#xff0c;需要通过联网搜索处理。 正确答案应该是2023年8月29日退役。 2、实现代码 # import gradio as gr# d…

奈雪小程序任务脚本

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

上海医日健集团物联网专利技术领跑智慧药房赛道

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

基于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.工作进程&#xff0c;设置成和cpu核心数一样即可 3.每个工作进程的最大网络连接数。 4.主机名称 设置反向代理时&#xff0c;把server_name设置成ip。 5.反向代理进行转发&#xff0c;localhost指的是nginx所在的机器。 关键字proxy_pass。 …

Agent模型微调

这篇文章讲解&#xff1a; 把 Agent 和 Fine-Tuning 的知识串起来&#xff0c;在更高的技术视角看大模型应用&#xff1b;加深对 Agent 工作原理的理解&#xff1b;加深对 Fine-Tuning 训练数据处理的理解。 1. 认识大模型 Agent 1.1 大模型 Agent 的应用场景 揭秘Agent核心…

Linux基本指令篇 —— whoami指令

whoami 是 Linux 和 Unix 系统中一个简单但实用的命令&#xff0c;全称 Who Am I&#xff08;我是谁&#xff09;。它的功能是显示当前登录用户的用户名。以下是关于 whoami 的详细解析&#xff1a; 目录 1. 基本用法 2. 命令特点 3. 实际应用场景 场景 1&#xff1a;脚本中…

力扣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…

WebSocket(看这一篇就够了)

文章目录 WebSocket 基本概念什么是WebSocket?为什么需要 WebSocket&#xff1f;与 HTTP 协议的区别WebSocket协议的原理WebSocket工作流程WebSocket 数据帧结构和控制帧结构。JavaScript 中 WebSocket 对象的属性和方法&#xff0c;以及如何创建和连接 WebSocket。webSocket简…

旧物回收小程序:让闲置焕发光彩,为生活增添价值

你是否常常为家中堆积如山的闲置物品而烦恼&#xff1f;那些曾经心爱的物品&#xff0c;如今却成了占据空间的“鸡肋”&#xff0c;丢弃可惜&#xff0c;留着又无处安放。别担心&#xff0c;一款旧物二手回收小程序将为你解决这一难题&#xff0c;让闲置物品重新焕发光彩&#…

数学建模MathAI智能体-2025电工杯A题实战

题目&#xff1a; 光伏电站发电功率日前预测问题 光伏发电是通过半导体材料的光电效应&#xff0c;将太阳能直接转化为电能的技术。光伏电站是由众多光伏发电单元组成的规模化发电设施。 光伏电站的发电功率主要由光伏板表面接收到的太阳辐射总量决定&#xff0c;不同季节太阳…

C# Windows Forms应用程序-002

目录 项目结构 主类和命名空间 构造函数和析构函数 初始化组件 (InitializeComponent) 按钮点击事件处理程序 主程序入口点 项目截图&#xff1a; 完整代码&#xff1a; 项目结构 这个项目是一个简单的C# Windows Forms应用程序&#xff0c;获取指定文件的根信息…

理解计算机系统_线程(八):并行

前言 以<深入理解计算机系统>(以下称“本书”)内容为基础&#xff0c;对程序的整个过程进行梳理。本书内容对整个计算机系统做了系统性导引,每部分内容都是单独的一门课.学习深度根据自己需要来定 引入 接续理解计算机系统_并发编程(10)_线程(七):基于预线程化的…

【MySQL】09.索引

索引是用来提高数据库的性能的&#xff0c;但查询速度的提高是以插入、更新、删除的速度为代价的&#xff0c;这些写操作&#xff0c;增加了大量的IO。所以它的价值在于提高一个海量数据的检索速度。 1. 认识磁盘 MySQL 给用户提供存储服务&#xff0c;而存储的都是数据&…