为什么可以链接静态库,尽管有未定义的引用(缺少 `extern "C"`)?
Why can a static library be linked despite undefined references (missing `extern "C"`)?
我没有需要解决的问题,但对以下问题感到非常困惑:
我正在使用 CMake(内部使用 gcc)。不确定这是否重要。
假设我有一个项目,由一个 C++ 静态库 LibCpp 和一个演示 C++ 可执行文件 DemoCpp 组成,link 并使用 LibCpp。 LibCpp 库是从 C 和 C++ 源代码编译而来的。正确的做法是让 LibCpp 的 C++ 代码文件将所有 C 头文件包含为 extern "C" {#include "c_header.h"}
。如果这样做,静态库 link 没问题,可执行文件 DemoCpp 成功 link 到 LibCpp。一切正常。
假设,我忘记放入 extern "C"
,只将 C 头文件作为 C++ 头文件包含在内。正如预期的那样,整个项目未能 link。然而,这让我感到困惑,当 DemoCpp 尝试 link 到 LibCpp 时 linking 失败! 我本以为它已经在 linking 静态库。但是,静态库(例如 LibCpp.a
文件)构建得很好,就是不能使用。
这让我很困惑,因为对于 link 静态库(我认为),linker 必须将引用解析为编译为 C 的代码。这也很烦人在我看来,因为我不能依赖图书馆“link能够”,因为图书馆本身设法 linked。我错过了什么吗?
你不需要看下面的代码示例,除非你不明白我的问题或者不相信我说的。
# CMakeLists.txt
cmake_minimum_required(VERSION 3.17)
project(LibraryLinkerEvil)
# Toggle this line to see that linking fails for DemoCpp BUT NOT FOR LibCpp!!!
add_compile_definitions(USE_EXTERN_C)
set(CMAKE_CXX_STANDARD 17)
add_library(LibCpp cpp_lib.cpp util.c)
add_executable(DemoCpp main.cpp)
target_link_libraries(DemoCpp PRIVATE LibCpp)
// cpp_lib.cpp
#ifdef USE_EXTERN_C
extern "C" {
#endif
#include "util.h"
#ifdef USE_EXTERN_C
}
#endif
int call_c_function(int a, int b) {
return add(a,b);
}
// main.cpp
#include <iostream>
int call_c_function(int a, int b); // could be in a separate C++ header.
int main() {
std::cout << "Hello, World! " << call_c_function(2,2) << std::endl;
return 0;
}
// util.h (C header)
#ifndef LIBRARYLINKEREVIL_UTIL_H
#define LIBRARYLINKEREVIL_UTIL_H
int add(int a, int b);
#endif //LIBRARYLINKEREVIL_UTIL_H
// util.c (C source file)
int add(int a, int b) {
return a+b;
}
因为 C++ 支持函数重载,函数名称在编译过程中被“破坏”,因此它们在目标文件中具有唯一的名称。使用 extern "C"
可以防止发生这种情况。
如果您在编译时遗漏了 extern "C"
,那么 call_c_function
将调用名称为 add
的 C++ 函数,特别是经过处理的名称将是 _Z3addii
.由于 add
存在于 .c 文件中,它具有未损坏的名称而不是损坏的名称,因此现在 _Z3addii
被认为是一个外部函数,就像 printf
一样,并且像所有其他外部函数一样它们预计将在可执行文件的 link 时解析,而不是编译时,而不是在创建静态库时。
创建静态库时,您实际上并没有 linking。您只是将几个目标文件分组到一个更大的档案中。这样,当 linking 实际发生时,您只需要 link 一个文件而不是多个文件。
并非所有引用都需要在创建库时解析。如果您的库代码依赖于另一个库怎么办?创建库时不会解析这些引用。当您 link 将库添加到您的可执行文件时,它们会得到解决,在这种情况下,您还需要 link 它与其他库一起提供这些依赖项。
图书馆liba.a
:
void from_b();
void from_a()
{
from_b();
}
库 libb.a
:
void from_b()
{
...
}
main.cpp:
void from_a();
int main()
{
from_a();
}
Compiling/linking:
g++ main.cpp -la -lb
如果没有 -lb
,您会在此处遇到未定义的引用错误。
I would have expected it to fail already at linking the static library.
未链接静态“库”。其组成的 object 文件已存档 - 为方便起见,将其捆绑在一个 object 存档 中。这就是静态“库”。您可以将其视为一堆 object 文件打包在一个“zip”文件中,然后链接器可以对其进行解码。
因此,唯一链接 object 存档的过程是链接目标二进制文件本身(将其链接到 .dll/.so 或可执行文件中)。
object 存档的使用等同于组成 object 文件的使用:您可以将所有 object 文件传递给链接器,而不是将存档传递给链接器它包含,结果相同。
在 Unix 上,管理 object 档案的工具的典型名称是 ar
(ar
chiver) - 确实是一个非常具有描述性的名称。在 Unix 传统中,静态“库”被称为 object archives。术语静态“库”是一个令人困惑的用词不当,应该被禁止。
通常 ar
是一个比链接器小得多的程序——通常小一个数量级。最基本的存档只是添加了 header 的串联。
我没有需要解决的问题,但对以下问题感到非常困惑:
我正在使用 CMake(内部使用 gcc)。不确定这是否重要。
假设我有一个项目,由一个 C++ 静态库 LibCpp 和一个演示 C++ 可执行文件 DemoCpp 组成,link 并使用 LibCpp。 LibCpp 库是从 C 和 C++ 源代码编译而来的。正确的做法是让 LibCpp 的 C++ 代码文件将所有 C 头文件包含为 extern "C" {#include "c_header.h"}
。如果这样做,静态库 link 没问题,可执行文件 DemoCpp 成功 link 到 LibCpp。一切正常。
假设,我忘记放入 extern "C"
,只将 C 头文件作为 C++ 头文件包含在内。正如预期的那样,整个项目未能 link。然而,这让我感到困惑,当 DemoCpp 尝试 link 到 LibCpp 时 linking 失败! 我本以为它已经在 linking 静态库。但是,静态库(例如 LibCpp.a
文件)构建得很好,就是不能使用。
这让我很困惑,因为对于 link 静态库(我认为),linker 必须将引用解析为编译为 C 的代码。这也很烦人在我看来,因为我不能依赖图书馆“link能够”,因为图书馆本身设法 linked。我错过了什么吗?
你不需要看下面的代码示例,除非你不明白我的问题或者不相信我说的。
# CMakeLists.txt
cmake_minimum_required(VERSION 3.17)
project(LibraryLinkerEvil)
# Toggle this line to see that linking fails for DemoCpp BUT NOT FOR LibCpp!!!
add_compile_definitions(USE_EXTERN_C)
set(CMAKE_CXX_STANDARD 17)
add_library(LibCpp cpp_lib.cpp util.c)
add_executable(DemoCpp main.cpp)
target_link_libraries(DemoCpp PRIVATE LibCpp)
// cpp_lib.cpp
#ifdef USE_EXTERN_C
extern "C" {
#endif
#include "util.h"
#ifdef USE_EXTERN_C
}
#endif
int call_c_function(int a, int b) {
return add(a,b);
}
// main.cpp
#include <iostream>
int call_c_function(int a, int b); // could be in a separate C++ header.
int main() {
std::cout << "Hello, World! " << call_c_function(2,2) << std::endl;
return 0;
}
// util.h (C header)
#ifndef LIBRARYLINKEREVIL_UTIL_H
#define LIBRARYLINKEREVIL_UTIL_H
int add(int a, int b);
#endif //LIBRARYLINKEREVIL_UTIL_H
// util.c (C source file)
int add(int a, int b) {
return a+b;
}
因为 C++ 支持函数重载,函数名称在编译过程中被“破坏”,因此它们在目标文件中具有唯一的名称。使用 extern "C"
可以防止发生这种情况。
如果您在编译时遗漏了 extern "C"
,那么 call_c_function
将调用名称为 add
的 C++ 函数,特别是经过处理的名称将是 _Z3addii
.由于 add
存在于 .c 文件中,它具有未损坏的名称而不是损坏的名称,因此现在 _Z3addii
被认为是一个外部函数,就像 printf
一样,并且像所有其他外部函数一样它们预计将在可执行文件的 link 时解析,而不是编译时,而不是在创建静态库时。
创建静态库时,您实际上并没有 linking。您只是将几个目标文件分组到一个更大的档案中。这样,当 linking 实际发生时,您只需要 link 一个文件而不是多个文件。
并非所有引用都需要在创建库时解析。如果您的库代码依赖于另一个库怎么办?创建库时不会解析这些引用。当您 link 将库添加到您的可执行文件时,它们会得到解决,在这种情况下,您还需要 link 它与其他库一起提供这些依赖项。
图书馆liba.a
:
void from_b();
void from_a()
{
from_b();
}
库 libb.a
:
void from_b()
{
...
}
main.cpp:
void from_a();
int main()
{
from_a();
}
Compiling/linking:
g++ main.cpp -la -lb
如果没有 -lb
,您会在此处遇到未定义的引用错误。
I would have expected it to fail already at linking the static library.
未链接静态“库”。其组成的 object 文件已存档 - 为方便起见,将其捆绑在一个 object 存档 中。这就是静态“库”。您可以将其视为一堆 object 文件打包在一个“zip”文件中,然后链接器可以对其进行解码。
因此,唯一链接 object 存档的过程是链接目标二进制文件本身(将其链接到 .dll/.so 或可执行文件中)。
object 存档的使用等同于组成 object 文件的使用:您可以将所有 object 文件传递给链接器,而不是将存档传递给链接器它包含,结果相同。
在 Unix 上,管理 object 档案的工具的典型名称是 ar
(ar
chiver) - 确实是一个非常具有描述性的名称。在 Unix 传统中,静态“库”被称为 object archives。术语静态“库”是一个令人困惑的用词不当,应该被禁止。
通常 ar
是一个比链接器小得多的程序——通常小一个数量级。最基本的存档只是添加了 header 的串联。