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方法,但是链接的时候发现没有找到,这是系统默认的函数函数呀。
- 编译的时候并没有报错,我们也没有指定iostream头文件的搜索路径,说明在默认的路径下找到了iostream的头文件,
- 在链接的时候也没有指定库的查找路径,最后也是从默认路径下查找库才对,不应该没有呀
后来突然意识到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++ # 链接两个目标文件