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

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.lqws.cn/news/84817.html

相关文章:

  • UE特效Niagara性能分析
  • 数据资产评估进阶:精读资产评估专家指引第9号——数据资产评估指导【附全文阅读】
  • 【Godot引擎】如何使用内置的全局搜索功能提升开发效率
  • selenium-自动更新谷歌浏览器驱动
  • Redis-6.2.9 cluster集群部署和扩容缩容
  • bismark OT CTOT OB CTOB 以及mapping后的bam文件中的XG,XR列的含义
  • 调试的本质:从混沌走向秩序
  • 双指针题解——反转字符串中的单词【LeetCode】
  • FastAPI安全认证:从密码到令牌的魔法之旅
  • 嵌入式Linux 期末复习指南(下)
  • Mysql水平分表(基于Mycat)及常用分片规则
  • Spring 5 响应式编程:构建高性能全栈应用的关键
  • hooks组件-useState
  • 吴恩达机器学习笔记(1)—引言
  • 设计模式——访问者设计模式(行为型)
  • wow Warlock shushia [Dreadsteed]
  • 地图 APP 和购物 APP 是最急切上 AI的地方
  • Artificial Analysis2025年Q1人工智能发展六大趋势总结
  • ThreadLocal ,底层原理,强引用,弱引用,内存泄漏
  • Vue3(watch,watchEffect,标签中ref的使用,TS,props,生命周期)
  • FastAPI+Pyomo实现线性回归解决饮食问题
  • 函数调用的机器级实现(二):栈帧的访问与切换机制
  • 极客时间:用 FAISS、LangChain 和 Google Colab 模拟 LLM 的短期与长期记忆
  • 【springcloud】快速搭建一套分布式服务springcloudalibaba(四)
  • python爬虫:Ruia的详细使用(一个基于asyncio和aiohttp的异步爬虫框架)
  • Langchian - 自定义提示词模板 提取结构化的数据
  • 【redis实战篇】第七天
  • 在 Linux 服务器上无需 sudo 权限解压/打包 .7z 的方法(实用命令)
  • 小团队如何落地 Scrum 模型:从 0 到 1 的实战指南
  • rabbitmq Direct交换机简介