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

gcc编译构建流程-函数未定义问题

1. 函数为定义的问题

我们有一个项目

// main.cpp
#include "add.h"
int main(){add_v1(1,2);add_v2(1,2);
}// add.h
#pragma once
int add_v1(int, int);
int add_v2(int, int);// add.cpp
int add_v1(int a, int b){return a + b;
}

我们进行编译链接

gcc -c main.cpp -o main.o # 编译main.cpp
gcc -c add.cpp -o add.o # 编译add.cpp
gcc main.o add.o -o main # 链接两个目标文件

链接的时候就会报错,我们声明并且使用了add_v2函数,但是并没有实现

/usr/bin/ld: main.o: in function `main':
main.cpp:(.text+0x1c): undefined reference to `add_v2(int, int)'
collect2: error: ld returned 1 exit status

链接器如何检测函数未实现?链接器扫描所有目标文件的重定位表 → 收集未解析符号 → 在符号表中匹配定义 → 若缺失则报错

  • 编译阶段:编译器生成目标文件(.o/.obj),其中包含​​符号表​​(记录函数/变量声明)和​​重定位表​​(记录未解析的符号引用)。
  • 链接阶段:链接器合并所有目标文件,遍历重定位表中的​​未解析符号​​(如函数调用),在符号表中查找匹配的定义。
  • ​​关键动作​​:若符号表中无对应定义,则抛出错误(如 undefined reference)。

我们可以通过readelf工具查看当前的符号表

readelf -s add.o
Symbol table '.symtab' contains 11 entries:Num:    Value          Size Type    Bind   Vis      Ndx Name0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS add.cpp2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 .text3: 0000000000000000     0 SECTION LOCAL  DEFAULT    2 .data4: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 .bss5: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT    1 $x6: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 .note.GNU-stack7: 0000000000000014     0 NOTYPE  LOCAL  DEFAULT    6 $d8: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 .eh_frame9: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 .comment10: 0000000000000000    32 FUNC    GLOBAL DEFAULT    1 _Z6add_v1iireadelf -s main.o
Symbol table '.symtab' contains 13 entries:Num:    Value          Size Type    Bind   Vis      Ndx Name0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS main.cpp2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 .text3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 .data4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 .bss5: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT    1 $x6: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 .note.GNU-stack7: 0000000000000014     0 NOTYPE  LOCAL  DEFAULT    7 $d8: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 .eh_frame9: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 .comment10: 0000000000000000    60 FUNC    GLOBAL DEFAULT    1 main11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z6add_v1ii12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z6add_v2ii

或者直接通过nm查看

nm main.o
0000000000000000 T mainU _Z6add_v1iiU _Z6add_v2iinm add.o
0000000000000000 T _Z6add_v1ii
  • T 该符号位于代码区text section。即这个函数在当前的目标文件中有定以。
  • U 该符号在当前文件中是未定义的,即该符号的定义在别的文件中。例如,当前文件调用另一个文件中定义的函数,在这个被调用的函数在当前就是未定义的;但是在定义它的文件中类型是T。但是对于全局变量来说,在定义它的文件中,其符号类型为C,在使用它的文件中,其类型为U。

main.o中引用了两个函数,一个add_v1,一个add_v2,这两个函数都U,即未在当前的main.o中定义,因此链接的过程中会从其他的目标文件中查找,需要查询其他的文件,add.o中只定义了一个add_v1的函数,所以调用add_v2就会报为定义。

我们在add.cpp中定义一下,此时再进行链接就不会报错了

// add.cpp
int add_v1(int a, int b){return a + b;
}
// add.cpp
int add_v2(int a, int b){return a + b + 1;
}

动态库未定义

main函数中没有任何的打印,我们添加一下

// main.cpp
#include "add.h"
#include <iostream>
int main(){int v1 = add_v1(1,2);int v2 = add_v2(1,2);std::cout << v1 << " " << v2 << std::endl;
}

然后再执行编译链接

gcc -c main.cpp -o main.o # 编译main.cpp
gcc -c add.cpp -o add.o # 编译add.cpp
gcc main.o add.o -o main # 链接两个目标文件

你会发现还是链接阶段报错了

/usr/bin/ld: main.o: in function `main':
main.cpp:(.text+0x2c): undefined reference to `std::cout'
/usr/bin/ld: main.cpp:(.text+0x30): undefined reference to `std::cout'
/usr/bin/ld: main.cpp:(.text+0x34): undefined reference to `std::ostream::operator<<(int)'
/usr/bin/ld: main.cpp:(.text+0x48): 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: main.cpp:(.text+0x50): undefined reference to `std::ostream::operator<<(int)'
/usr/bin/ld: main.cpp:(.text+0x54): 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: main.cpp:(.text+0x58): 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: main.cpp:(.text+0x5c): undefined reference to `std::ostream::operator<<(std::ostream& (*)(std::ostream&))'
/usr/bin/ld: main.o: in function `__static_initialization_and_destruction_0(int, int)':
main.cpp:(.text+0xa0): undefined reference to `std::ios_base::Init::Init()'
/usr/bin/ld: main.cpp:(.text+0xb4): undefined reference to `std::ios_base::Init::~Init()'
/usr/bin/ld: main.cpp:(.text+0xb8): undefined reference to `std::ios_base::Init::~Init()'
collect2: error: ld returned 1 exit status

我们在main中引入了iostream库,并调用了库中的cout方法,但是链接的时候发现没有找到,这是系统默认的函数函数呀。

  1. 编译的时候并没有报错,我们也没有指定iostream头文件的搜索路径,说明在默认的路径下找到了iostream的头文件,
  2. 在链接的时候也没有指定库的查找路径,最后也是从默认路径下查找库才对,不应该没有呀

后来突然意识到cpp的编译链接应该使用g++

g++ -c main.cpp -o main.o # 编译main.cpp
g++ -c add.cpp -o add.o # 编译add.cpp
g++ main.o add.o -o main # 链接两个目标文件

gcc和g++链接行为

我的第一个想法时,gcc和g++在搜索库的时候路径不一样,但是发现其实一样

# 查看 gcc 默认库路径
gcc --print-search-dirs | grep libraries
# 查看 g++ 默认库路径
g++ --print-search-dirs | grep libraries

无论是 gcc 还是 g++,编译器在链接阶段查找库文件(如 -lm、-lpthread)时,默认搜索以下系统路径:

  • ​​/usr/lib​​: 系统标准库目录(如 libc.so、libm.so)。
  • /usr/local/lib​​: 用户手动安装的第三方库目录(如自定义编译的 libfoo.so)。
  • ​​/lib 或架构相关路径(如 /lib/x86_64-linux-gnu)​​: 基础系统库和硬件架构相关的库。

虽然搜索路径相同,但 gcc 和 g++ 在​​链接时默认链接的库​​不同,使用 gcc 编译 C++ 代码需显式链接 C++ 库,使用 g++ 编译 C++ 代码自动链接 C++ 库​​

​​编译器​​ ​​默认链接的库​​ ​​是否需要手动指定 C++ 库​​
gcc仅 C 标准库(libc.so)是(需 -lstdc++)
g++C++ 标准库(libstdc++.so) 和 C 标准库
gcc -c main.cpp -o main.o # 编译main.cpp
gcc -c add.cpp -o add.o # 编译add.cpp
gcc main.o add.o -o main -lstdc++ # 链接两个目标文件 
http://www.xdnf.cn/news/10748.html

相关文章:

  • BayesFlow:基于神经网络的摊销贝叶斯推断框架
  • 数据库技术
  • 蓝云APP:云端存储,便捷管理
  • leetcode刷题日记——二叉树的层次遍历
  • 【数学 逆序对 构造】P12386 [蓝桥杯 2023 省 Python B] 混乱的数组|普及+
  • deepseek原理和项目实战笔记2 -- deepseek核心架构
  • 量子物理:深入学习量子物理的基本概念与应用
  • 量子计算在大模型微调中的技术突破
  • CAN通讯协议中各种参数解析
  • P5684 [CSP-J2019 江西] 非回文串 题解
  • BUUCTF之[ACTF2020 新生赛]BackupFile
  • 【latex】易遗忘的表达
  • Vue:组件
  • 分班 - 华为OD统一考试(JavaScript 题解)
  • 【操作系统·windows快捷键指令】
  • sql注入补充——get注入
  • 322. 零钱兑换
  • Day10
  • 【C盘瘦身】给DevEco Studio中HarmonyOSEmulator(鸿蒙模拟器)换个地方,一键移动给C盘瘦身
  • 企业级开发中的 maven-mvnd 应用实践
  • 深度理解与剖析:Odoo系统邮箱配置指南
  • 给stm32cubeide编译出来的bin文件追加crc32
  • 算法训练第六天
  • 检索器组件深入学习与使用技巧 BaseRetriever 检索器基类
  • SystemVerilog—Interface在class中的使用
  • 【DSP数字信号处理】期末复习笔记(一)
  • 交换机、路由器配置
  • Jackson 数值转科学计数法问题分析与解决方案
  • 第一篇:揭示模型上下文协议(MCP):AI的通用连接器
  • MySQL日志