传递库链接中的未定义引用
Undefined reference in transitive library linking
我有一个大型库 B
,它对配置对象 MY_CONFIG
执行一些操作。该库 link 是针对提供主要功能的更大的库 C
编写的。最后,C
针对可执行文件 main
进行了 linked。
配置对象 MY_CONFIG
对于每个计算单元都是唯一的。由于目标是为所有系统生成一个 C
库,我目前正在尝试生成一个仅包含定义的配置对象的附加库 A
。
换句话说,所有计算单元最终得到相同的C
,但不同的A
库。
然而,问题是,当我尝试 link main 对抗 C
和 A
时,我得到一个 undefined reference
linker 错误。 =35=]
让我们用一些代码来说明这个例子:
A.cpp
#include "B.hpp"
Config MY_CONFIG = {
.id=2
};
B.hpp
int get_config_id ();
typedef struct Config {
int id;
} Config;
B.cpp
#include "B.hpp"
extern Config MY_CONFIG;
int get_config_id () {
Config work = MY_CONFIG;
return work.id;
}
C.hpp
void print_config();
C.cpp
#include "B.hpp"
#include <iostream>
void print_config() {
std::cout << "Config ID: " << get_config_id() << std::endl;
}
main.cpp
#include <iostream>
#include "C.hpp"
int main() {
print_config();
return 0;
}
CMakeLists.txt
project(config)
cmake_minimum_required(VERSION 3.16)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pthread")
add_library(A STATIC A/A.cpp)
target_include_directories(A PUBLIC B)
add_library(B STATIC B/B.cpp)
target_include_directories(B PUBLIC B)
add_library(C STATIC C/C.cpp)
target_include_directories(C PUBLIC C)
target_link_libraries(C B)
add_executable(main main.cpp)
# Apparently, this is not possible. But why?
target_link_libraries(main C A)
如您所见,我在 B.cpp 中为 MY_CONFIG
使用了 extern
关键字。我的直觉是,一旦构建了可执行文件 main,它就解决了。由于MY_CONFIG
的定义在A.cpp
中,我也link主要针对A。编译时出现如下错误:
/usr/bin/ld: libB.a(B.cpp.o): in function `get_config_id()':
B.cpp:(.text+0xa): undefined reference to `MY_CONFIG'
collect2: error: ld returned 1 exit status
如果我link用A 来对抗C,那么它工作得很好。但这是我想避免的事情,因为我需要为所有计算系统提供单独的 C。一个重要的旁注:我只能使用静态库(没有共享对象)。
我的意图通常是不可能的还是我错过了什么?
而不是
target_link_libraries(main C A)
你可以使用
target_link_libraries(main C B A)
这将在 linker 的命令行中的库之间产生正确的顺序。因此,您将克服“未定义引用”错误。
解释
库 B
使用符号(具体来说,变量)MY_CONFIG
,它在库 A
中定义。因此,为了 link 成功生成可执行文件,您需要:
- 确保
A.a
在 link 用户的命令行中位于 B.a
之后。
- 使用选项,消除 linker 的排序敏感性。
您可以使用 -Wl,--whole-archive
选项按照第二种方式进行操作。参见例如.
为了遵循第一种方式,即确保库的 order 为 link,应该考虑到 CMake 提供了两种强制排序方式:
链接 库之间:
target_link_libraries(B A)
这是最方便和可扩展的方式。它确保在 linker 的命令行中 A.a
将在 last B.a
.
之后
但是这个link年龄不适合你的目的。
target_link_libraries
中的库顺序。例如
target_link_libraries(main <...> B A <...>)
将确保 至少一个 B
后跟 A
。
这种方式比第一种方式 保证 少。例如。该命令不会阻止 CMake 生成 linker 命令行 <...> B.a A.a B.a <...>
,这将再次导致未定义的引用错误。但是 CMake 不会在没有需要的情况下在命令行中复制库。
例如,而不是
target_link_libraries(main C A)
你可以使用
target_link_libraries(main C B A)
因此 linker 命令行将完全按照以下顺序包含库:
ld <...> C.a B.a A.a
请注意,在给定的命令行中 B.a
跟在 C.a
之后,这与您 CMakeLists.txt
中的另一个 link 年龄相关:
target_link_libraries(C B)
所以 CMake 不会再向其中添加 B.a
。
link年龄
target_link_libraries(C B)
你目前在你的代码中只有确保B.a
会在linker命令行中某个地方之后C.a
。
有额外的
target_link_libraries(main C A)
您可以预期 CMake 会生成正确的命令行
ld <...> C.a B.a A.a
满足link年龄。
但是完全允许 CMake 生成另一个命令行:
ld <...> C.a A.a B.a
其中 B.a
没有紧跟在 C.a
之后。
在您的案例中,正是该命令行导致了“未定义引用”错误。
CMake 对静态库之间的循环依赖没有问题。只需声明实际的依赖关系,CMake 就会处理其余的事情。这是我修改后的 CMakeLists.txt:
cmake_minimum_required(VERSION 3.22)
project(config)
set(CMAKE_CXX_STANDARD 11)
add_library(A STATIC A/A.cpp)
target_include_directories(A PUBLIC "${CMAKE_CURRENT_LIST_DIR}/A")
target_link_libraries(A PRIVATE B)
add_library(B STATIC B/B.cpp)
target_include_directories(B PUBLIC "${CMAKE_CURRENT_LIST_DIR}/B")
target_link_libraries(B PRIVATE A)
add_library(C STATIC C/C.cpp)
target_include_directories(C PUBLIC "${CMAKE_CURRENT_LIST_DIR}/C")
target_link_libraries(C PRIVATE B)
add_executable(main main.cpp)
target_link_libraries(main PRIVATE C)
使用您的确切来源,我可以像这样 运行 构建:
$ cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=Release
...
$ cmake --build build --verbose
[1/8] /usr/bin/c++ -I/path/to/B -I/path/to/A -O3 -DNDEBUG -std=gnu++11 -MD -MT CMakeFiles/B.dir/B/B.cpp.o -MF CMakeFiles/B.dir/B/B.cpp.o.d -o CMakeFiles/B.dir/B/B.cpp.o -c /path/to/B/B.cpp
[2/8] /usr/bin/c++ -I/path/to/A -I/path/to/B -O3 -DNDEBUG -std=gnu++11 -MD -MT CMakeFiles/A.dir/A/A.cpp.o -MF CMakeFiles/A.dir/A/A.cpp.o.d -o CMakeFiles/A.dir/A/A.cpp.o -c /path/to/A/A.cpp
[3/8] : && /usr/bin/cmake -E rm -f libB.a && /usr/bin/ar qc libB.a CMakeFiles/B.dir/B/B.cpp.o && /usr/bin/ranlib libB.a && :
[4/8] : && /usr/bin/cmake -E rm -f libA.a && /usr/bin/ar qc libA.a CMakeFiles/A.dir/A/A.cpp.o && /usr/bin/ranlib libA.a && :
[5/8] /usr/bin/c++ -I/path/to/C -O3 -DNDEBUG -std=gnu++11 -MD -MT CMakeFiles/main.dir/main.cpp.o -MF CMakeFiles/main.dir/main.cpp.o.d -o CMakeFiles/main.dir/main.cpp.o -c /path/to/main.cpp
[6/8] /usr/bin/c++ -I/path/to/C -I/path/to/B -O3 -DNDEBUG -std=gnu++11 -MD -MT CMakeFiles/C.dir/C/C.cpp.o -MF CMakeFiles/C.dir/C/C.cpp.o.d -o CMakeFiles/C.dir/C/C.cpp.o -c /path/to/C/C.cpp
[7/8] : && /usr/bin/cmake -E rm -f libC.a && /usr/bin/ar qc libC.a CMakeFiles/C.dir/C/C.cpp.o && /usr/bin/ranlib libC.a && :
[8/8] : && /usr/bin/c++ -O3 -DNDEBUG CMakeFiles/main.dir/main.cpp.o -o main libC.a libB.a libA.a libB.a libA.a && :
$ ./build/main
Config ID: 2
如您所见,最后的 link 行生成 C B A B A
。后两者不会造成任何伤害,因为它们实际上永远不会被搜索。
如果想打破这个循环,可以把B中A需要的那部分拆成另一个库,让两者都依赖。
现在我有以下文件:
// A/A.cpp
#include "common.hpp"
Config MY_CONFIG = {
.id=2
};
// common/common.hpp
typedef struct Config {
int id;
} Config;
// B/B.cpp
#include "B.hpp"
#include "common.hpp"
extern Config MY_CONFIG;
int get_config_id () {
Config work = MY_CONFIG;
return work.id;
}
// B/B.hpp
int get_config_id ();
然后像这样调整构建:
cmake_minimum_required(VERSION 3.22)
project(config)
set(CMAKE_CXX_STANDARD 11)
add_library(common INTERFACE)
target_include_directories(common INTERFACE "${CMAKE_CURRENT_LIST_DIR}/common")
add_library(A STATIC A/A.cpp)
target_include_directories(A PUBLIC "${CMAKE_CURRENT_LIST_DIR}/A")
target_link_libraries(A PRIVATE common)
add_library(B STATIC B/B.cpp)
target_include_directories(B PUBLIC "${CMAKE_CURRENT_LIST_DIR}/B")
target_link_libraries(B PRIVATE common A)
add_library(C STATIC C/C.cpp)
target_include_directories(C PUBLIC "${CMAKE_CURRENT_LIST_DIR}/C")
target_link_libraries(C PRIVATE B)
add_executable(main main.cpp)
target_link_libraries(main PRIVATE C)
并且当我们构建时,我们看到 link 顺序是正确的并且重复的库已经消失:
$ cmake --build build --verbose
[1/8] /usr/bin/c++ -I/path/to/A -I/path/to/common -O3 -DNDEBUG -std=gnu++11 -MD -MT CMakeFiles/A.dir/A/A.cpp.o -MF CMakeFiles/A.dir/A/A.cpp.o.d -o CMakeFiles/A.dir/A/A.cpp.o -c /path/to/A/A.cpp
[2/8] /usr/bin/c++ -I/path/to/B -I/path/to/common -I/path/to/A -O3 -DNDEBUG -std=gnu++11 -MD -MT CMakeFiles/B.dir/B/B.cpp.o -MF CMakeFiles/B.dir/B/B.cpp.o.d -o CMakeFiles/B.dir/B/B.cpp.o -c /path/to/B/B.cpp
[3/8] : && /usr/bin/cmake -E rm -f libA.a && /usr/bin/ar qc libA.a CMakeFiles/A.dir/A/A.cpp.o && /usr/bin/ranlib libA.a && :
[4/8] : && /usr/bin/cmake -E rm -f libB.a && /usr/bin/ar qc libB.a CMakeFiles/B.dir/B/B.cpp.o && /usr/bin/ranlib libB.a && :
[5/8] /usr/bin/c++ -I/path/to/C -O3 -DNDEBUG -std=gnu++11 -MD -MT CMakeFiles/main.dir/main.cpp.o -MF CMakeFiles/main.dir/main.cpp.o.d -o CMakeFiles/main.dir/main.cpp.o -c /path/to/main.cpp
[6/8] /usr/bin/c++ -I/path/to/C -I/path/to/B -O3 -DNDEBUG -std=gnu++11 -MD -MT CMakeFiles/C.dir/C/C.cpp.o -MF CMakeFiles/C.dir/C/C.cpp.o.d -o CMakeFiles/C.dir/C/C.cpp.o -c /path/to/C/C.cpp
[7/8] : && /usr/bin/cmake -E rm -f libC.a && /usr/bin/ar qc libC.a CMakeFiles/C.dir/C/C.cpp.o && /usr/bin/ranlib libC.a && :
[8/8] : && /usr/bin/c++ -O3 -DNDEBUG CMakeFiles/main.dir/main.cpp.o -o main libC.a libB.a libA.a && :
我有一个大型库 B
,它对配置对象 MY_CONFIG
执行一些操作。该库 link 是针对提供主要功能的更大的库 C
编写的。最后,C
针对可执行文件 main
进行了 linked。
配置对象 MY_CONFIG
对于每个计算单元都是唯一的。由于目标是为所有系统生成一个 C
库,我目前正在尝试生成一个仅包含定义的配置对象的附加库 A
。
换句话说,所有计算单元最终得到相同的C
,但不同的A
库。
然而,问题是,当我尝试 link main 对抗 C
和 A
时,我得到一个 undefined reference
linker 错误。 =35=]
让我们用一些代码来说明这个例子:
A.cpp
#include "B.hpp"
Config MY_CONFIG = {
.id=2
};
B.hpp
int get_config_id ();
typedef struct Config {
int id;
} Config;
B.cpp
#include "B.hpp"
extern Config MY_CONFIG;
int get_config_id () {
Config work = MY_CONFIG;
return work.id;
}
C.hpp
void print_config();
C.cpp
#include "B.hpp"
#include <iostream>
void print_config() {
std::cout << "Config ID: " << get_config_id() << std::endl;
}
main.cpp
#include <iostream>
#include "C.hpp"
int main() {
print_config();
return 0;
}
CMakeLists.txt
project(config)
cmake_minimum_required(VERSION 3.16)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pthread")
add_library(A STATIC A/A.cpp)
target_include_directories(A PUBLIC B)
add_library(B STATIC B/B.cpp)
target_include_directories(B PUBLIC B)
add_library(C STATIC C/C.cpp)
target_include_directories(C PUBLIC C)
target_link_libraries(C B)
add_executable(main main.cpp)
# Apparently, this is not possible. But why?
target_link_libraries(main C A)
如您所见,我在 B.cpp 中为 MY_CONFIG
使用了 extern
关键字。我的直觉是,一旦构建了可执行文件 main,它就解决了。由于MY_CONFIG
的定义在A.cpp
中,我也link主要针对A。编译时出现如下错误:
/usr/bin/ld: libB.a(B.cpp.o): in function `get_config_id()':
B.cpp:(.text+0xa): undefined reference to `MY_CONFIG'
collect2: error: ld returned 1 exit status
如果我link用A 来对抗C,那么它工作得很好。但这是我想避免的事情,因为我需要为所有计算系统提供单独的 C。一个重要的旁注:我只能使用静态库(没有共享对象)。
我的意图通常是不可能的还是我错过了什么?
而不是
target_link_libraries(main C A)
你可以使用
target_link_libraries(main C B A)
这将在 linker 的命令行中的库之间产生正确的顺序。因此,您将克服“未定义引用”错误。
解释
库 B
使用符号(具体来说,变量)MY_CONFIG
,它在库 A
中定义。因此,为了 link 成功生成可执行文件,您需要:
- 确保
A.a
在 link 用户的命令行中位于B.a
之后。 - 使用选项,消除 linker 的排序敏感性。
您可以使用 -Wl,--whole-archive
选项按照第二种方式进行操作。参见例如
为了遵循第一种方式,即确保库的 order 为 link,应该考虑到 CMake 提供了两种强制排序方式:
链接 库之间:
target_link_libraries(B A)
这是最方便和可扩展的方式。它确保在 linker 的命令行中
之后A.a
将在 lastB.a
.但是这个link年龄不适合你的目的。
target_link_libraries
中的库顺序。例如target_link_libraries(main <...> B A <...>)
将确保 至少一个
B
后跟A
。这种方式比第一种方式 保证 少。例如。该命令不会阻止 CMake 生成 linker 命令行
<...> B.a A.a B.a <...>
,这将再次导致未定义的引用错误。但是 CMake 不会在没有需要的情况下在命令行中复制库。
例如,而不是
target_link_libraries(main C A)
你可以使用
target_link_libraries(main C B A)
因此 linker 命令行将完全按照以下顺序包含库:
ld <...> C.a B.a A.a
请注意,在给定的命令行中 B.a
跟在 C.a
之后,这与您 CMakeLists.txt
中的另一个 link 年龄相关:
target_link_libraries(C B)
所以 CMake 不会再向其中添加 B.a
。
link年龄
target_link_libraries(C B)
你目前在你的代码中只有确保B.a
会在linker命令行中某个地方之后C.a
。
有额外的
target_link_libraries(main C A)
您可以预期 CMake 会生成正确的命令行
ld <...> C.a B.a A.a
满足link年龄。
但是完全允许 CMake 生成另一个命令行:
ld <...> C.a A.a B.a
其中 B.a
没有紧跟在 C.a
之后。
在您的案例中,正是该命令行导致了“未定义引用”错误。
CMake 对静态库之间的循环依赖没有问题。只需声明实际的依赖关系,CMake 就会处理其余的事情。这是我修改后的 CMakeLists.txt:
cmake_minimum_required(VERSION 3.22)
project(config)
set(CMAKE_CXX_STANDARD 11)
add_library(A STATIC A/A.cpp)
target_include_directories(A PUBLIC "${CMAKE_CURRENT_LIST_DIR}/A")
target_link_libraries(A PRIVATE B)
add_library(B STATIC B/B.cpp)
target_include_directories(B PUBLIC "${CMAKE_CURRENT_LIST_DIR}/B")
target_link_libraries(B PRIVATE A)
add_library(C STATIC C/C.cpp)
target_include_directories(C PUBLIC "${CMAKE_CURRENT_LIST_DIR}/C")
target_link_libraries(C PRIVATE B)
add_executable(main main.cpp)
target_link_libraries(main PRIVATE C)
使用您的确切来源,我可以像这样 运行 构建:
$ cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=Release
...
$ cmake --build build --verbose
[1/8] /usr/bin/c++ -I/path/to/B -I/path/to/A -O3 -DNDEBUG -std=gnu++11 -MD -MT CMakeFiles/B.dir/B/B.cpp.o -MF CMakeFiles/B.dir/B/B.cpp.o.d -o CMakeFiles/B.dir/B/B.cpp.o -c /path/to/B/B.cpp
[2/8] /usr/bin/c++ -I/path/to/A -I/path/to/B -O3 -DNDEBUG -std=gnu++11 -MD -MT CMakeFiles/A.dir/A/A.cpp.o -MF CMakeFiles/A.dir/A/A.cpp.o.d -o CMakeFiles/A.dir/A/A.cpp.o -c /path/to/A/A.cpp
[3/8] : && /usr/bin/cmake -E rm -f libB.a && /usr/bin/ar qc libB.a CMakeFiles/B.dir/B/B.cpp.o && /usr/bin/ranlib libB.a && :
[4/8] : && /usr/bin/cmake -E rm -f libA.a && /usr/bin/ar qc libA.a CMakeFiles/A.dir/A/A.cpp.o && /usr/bin/ranlib libA.a && :
[5/8] /usr/bin/c++ -I/path/to/C -O3 -DNDEBUG -std=gnu++11 -MD -MT CMakeFiles/main.dir/main.cpp.o -MF CMakeFiles/main.dir/main.cpp.o.d -o CMakeFiles/main.dir/main.cpp.o -c /path/to/main.cpp
[6/8] /usr/bin/c++ -I/path/to/C -I/path/to/B -O3 -DNDEBUG -std=gnu++11 -MD -MT CMakeFiles/C.dir/C/C.cpp.o -MF CMakeFiles/C.dir/C/C.cpp.o.d -o CMakeFiles/C.dir/C/C.cpp.o -c /path/to/C/C.cpp
[7/8] : && /usr/bin/cmake -E rm -f libC.a && /usr/bin/ar qc libC.a CMakeFiles/C.dir/C/C.cpp.o && /usr/bin/ranlib libC.a && :
[8/8] : && /usr/bin/c++ -O3 -DNDEBUG CMakeFiles/main.dir/main.cpp.o -o main libC.a libB.a libA.a libB.a libA.a && :
$ ./build/main
Config ID: 2
如您所见,最后的 link 行生成 C B A B A
。后两者不会造成任何伤害,因为它们实际上永远不会被搜索。
如果想打破这个循环,可以把B中A需要的那部分拆成另一个库,让两者都依赖。
现在我有以下文件:
// A/A.cpp
#include "common.hpp"
Config MY_CONFIG = {
.id=2
};
// common/common.hpp
typedef struct Config {
int id;
} Config;
// B/B.cpp
#include "B.hpp"
#include "common.hpp"
extern Config MY_CONFIG;
int get_config_id () {
Config work = MY_CONFIG;
return work.id;
}
// B/B.hpp
int get_config_id ();
然后像这样调整构建:
cmake_minimum_required(VERSION 3.22)
project(config)
set(CMAKE_CXX_STANDARD 11)
add_library(common INTERFACE)
target_include_directories(common INTERFACE "${CMAKE_CURRENT_LIST_DIR}/common")
add_library(A STATIC A/A.cpp)
target_include_directories(A PUBLIC "${CMAKE_CURRENT_LIST_DIR}/A")
target_link_libraries(A PRIVATE common)
add_library(B STATIC B/B.cpp)
target_include_directories(B PUBLIC "${CMAKE_CURRENT_LIST_DIR}/B")
target_link_libraries(B PRIVATE common A)
add_library(C STATIC C/C.cpp)
target_include_directories(C PUBLIC "${CMAKE_CURRENT_LIST_DIR}/C")
target_link_libraries(C PRIVATE B)
add_executable(main main.cpp)
target_link_libraries(main PRIVATE C)
并且当我们构建时,我们看到 link 顺序是正确的并且重复的库已经消失:
$ cmake --build build --verbose
[1/8] /usr/bin/c++ -I/path/to/A -I/path/to/common -O3 -DNDEBUG -std=gnu++11 -MD -MT CMakeFiles/A.dir/A/A.cpp.o -MF CMakeFiles/A.dir/A/A.cpp.o.d -o CMakeFiles/A.dir/A/A.cpp.o -c /path/to/A/A.cpp
[2/8] /usr/bin/c++ -I/path/to/B -I/path/to/common -I/path/to/A -O3 -DNDEBUG -std=gnu++11 -MD -MT CMakeFiles/B.dir/B/B.cpp.o -MF CMakeFiles/B.dir/B/B.cpp.o.d -o CMakeFiles/B.dir/B/B.cpp.o -c /path/to/B/B.cpp
[3/8] : && /usr/bin/cmake -E rm -f libA.a && /usr/bin/ar qc libA.a CMakeFiles/A.dir/A/A.cpp.o && /usr/bin/ranlib libA.a && :
[4/8] : && /usr/bin/cmake -E rm -f libB.a && /usr/bin/ar qc libB.a CMakeFiles/B.dir/B/B.cpp.o && /usr/bin/ranlib libB.a && :
[5/8] /usr/bin/c++ -I/path/to/C -O3 -DNDEBUG -std=gnu++11 -MD -MT CMakeFiles/main.dir/main.cpp.o -MF CMakeFiles/main.dir/main.cpp.o.d -o CMakeFiles/main.dir/main.cpp.o -c /path/to/main.cpp
[6/8] /usr/bin/c++ -I/path/to/C -I/path/to/B -O3 -DNDEBUG -std=gnu++11 -MD -MT CMakeFiles/C.dir/C/C.cpp.o -MF CMakeFiles/C.dir/C/C.cpp.o.d -o CMakeFiles/C.dir/C/C.cpp.o -c /path/to/C/C.cpp
[7/8] : && /usr/bin/cmake -E rm -f libC.a && /usr/bin/ar qc libC.a CMakeFiles/C.dir/C/C.cpp.o && /usr/bin/ranlib libC.a && :
[8/8] : && /usr/bin/c++ -O3 -DNDEBUG CMakeFiles/main.dir/main.cpp.o -o main libC.a libB.a libA.a && :