【问题标题】:Why can a static library be linked despite undefined references (missing `extern "C"`)?尽管引用未定义(缺少`extern“C”`),为什么可以链接静态库?
【发布时间】:2021-03-23 01:52:17
【问题描述】:

我没有需要解决的问题,但对以下问题感到非常困惑:

我正在使用 CMake(在内部使用 gcc)。不确定这是否重要。

假设我有一个项目,它包含一个 C++ 静态库 LibCpp 和一个演示 C++ 可执行文件 DemoCpp,它链接到并使用 LibCpp。 LibCpp 库是从 C 和 C++ 源代码编译而来的。正确的做法是让 LibCpp 的 C++ 代码文件将所有 C 头文件包含为extern "C" {#include "c_header.h"}。如果这样做了,静态库链接正常,可执行 DemoCpp 成功链接到 LibCpp。一切正常。

假设,我忘了输入extern "C",只是将 C 头文件作为 C++ 头文件包含在内。正如预期的那样,整个项目无法链接。然而,这让我感到困惑,当 DemoCpp 尝试链接到 LibCpp 时,链接失败!我原以为它在链接静态库时已经失败了。但是,静态库(例如LibCpp.a 文件)构建得很好,只是不能使用。

这让我很费解,因为要链接静态库(我认为),链接器必须将引用解析为编译为 C 的代码。我认为这也很烦人,因为我不能依赖库仅在库本身设法被链接后才“可链接到”。我错过了什么吗?

你不需要看下面的代码示例,除非你不明白我的问题或者不相信我说的话。

# 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;
}

【问题讨论】:

  • 我很惊讶它进入了链接步骤。原因如下:malloc 需要在 C++ 中进行输出转换,但在 C 中不需要。
  • @EvanHendler 我在问题的任何地方都没有看到 malloc
  • 这不是评论的重点。我确定malloc 存在于依赖项中。如果不是malloc,那么其他一些函数在 C 和 C++ 之间的语法略有不同。包含 C 代码时应始终使用Extern "C"
  • @EvanHendler 我认为根据帖子中没有证明的假设发表评论并不是很有用。
  • @EvanHendler 我认为这并不令人惊讶。离开函数 extern "C" 只会改变你如何使用它,如果你将它传递给需要 extern "C" 函数的东西(例如qsort)。如果你不这样做,那么就没有问题。例如,自定义 void* mymalloc(size_t) 函数在 C++ 中的调用方式相同,无论它是否为 extern "C"

标签: c++ c linker static-libraries


【解决方案1】:

我原以为它在链接静态库时已经失败了。

静态“库”未链接。为方便起见,它的组成对象文件被归档 - 捆绑在 对象归档 中。这就是静态“库”。您可以将其视为打包在“zip”文件中的一堆目标文件,然后链接器可以对其进行解码。

因此,对象存档的唯一链接是目标二进制文件本身被链接时(无论是链接到 .dll/.so 还是可执行文件)。

使用对象存档等同于使用组成对象文件:无需将存档传递给链接器,您可以将其包含的所有对象文件传递给链接器,并获得相同的结果。

在 Unix 上,管理对象档案的工具的典型名称是 ar (archiver) - 确实是一个非常具有描述性的名称。在 Unix 传统中,静态“库”被称为对象档案。静态“库”一词是一个令人困惑的误称,应该被禁止。

通常ar 是一个比链接器小得多的程序——通常小一个数量级。最基本的归档只是添加了标题的串联。

【讨论】:

  • 感谢您的回答!共享库怎么样?我用动态库尝试了同样的事情(通过将SHARED 放入add_library 命令)并仍然得到一个libLibCpp.so 文件。
  • @AdomasBaliuka 在大多数系统上,共享库基本上是一个可执行文件,没有可执行文件的入口点,也没有在标头中设置标志。例如。 .dll.exe 是相同的 PE 格式,具有相同的功能,只有几位(字面意思)不同 - 您可以从 .exe 导入符号!在 Linux 上也是如此:共享库就像“未完成”的可执行文件 - 二进制格式是 ELF,如果需要,您可以从“可执行文件”中导出符号,并让它们由运行时加载的共享库导入。因此,当您认为“共享库”时,请考虑将标志设置为 0 的“可执行文件”
  • 谢谢!您所说的似乎暗示我无法将“缺少的外部 C”库链接为共享库。不过,我也可以这样做。如果您可以使用有关共享库的评论以及共享库链接的原因来更新您的答案,我可以接受您的回答。
  • 您应该问一个关于链接缺少符号的共享库的不同问题,并且请直接:问题与“extern C”无关,而是与符号的存在/不存在有关,因此您可以可以手动不匹配函数名称等并获得相同的结果,而不会引入另一层复杂性。
  • 我认为这意味着共享库也可以在不解析所有符号的情况下进行链接?链接可执行文件时,必须解析所有符号,对吗?
【解决方案2】:

创建库时,并非所有引用都需要解析。如果您的库代码依赖于另一个库怎么办?创建库时不会解析这些引用。当您将库链接到可执行文件时,它们会得到解决,在这种情况下,您还需要将其与其他库链接以提供这些依赖项。

图书馆liba.a:

void from_b();
void from_a()
{
    from_b();
}

libb.a:

void from_b()
{
    ...
}

main.cpp:

void from_a();
int main()
{
    from_a();
}

编译/链接:

g++ main.cpp -la -lb

如果没有-lb,你会在这里得到一个未定义的引用错误。

【讨论】:

  • 在创建库时,并非所有引用都需要解析。 这在技术上是正确的,但非常具有误导性。 创建对象存档时不会解析任何引用(对象存档是静态“库”的正确术语 - 它是自我描述的,不会误导)。创建对象档案只是将对象文件捆绑在一起。没有解决任何问题,没有执行链接,在 Unix 上执行此操作的工具的名称是 ar: for archiver
【解决方案3】:

因为 C++ 支持函数重载,所以函数名称在编译过程中被“修改”,因此它们在目标文件中具有唯一的名称。使用 extern "C" 可以防止这种损坏发生。

如果你在编译时忽略了extern "C",那么call_c_function 期望调用一个名称为add 的C++ 函数,特别是修改后的名称将是_Z3addii。由于 add 存在于 .c 文件中,它具有未损坏的名称而不是已损坏的名称,因此现在 _Z3addiiprintf 一样被视为外部函数,并且与所有其他外部函数一样,它们预计将在可执行文件的链接时间,不是编译时间,也不是创建静态库的时间。

当您创建一个静态库时,您实际上并没有进行链接。您只是将几个目标文件组合到一个更大的存档中。这样,当实际发生链接时,您只需要链接单个文件而不是多个文件。

【讨论】:

  • 我的问题不是我为什么需要extern "C"。我的问题是为什么静态库链接尽管没有使用extern "C"
  • 最初是用标志-c编译的吗?
  • @AdomasBaliuka 静态库在链接时不需要解析外部函数。只有在链接可执行文件时才会最终解决外部问题。
  • @dbush 你为什么不在答案中写下这个?
  • @AdomasBaliuka 我已经更新了有关静态库创建的详细信息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-19
  • 1970-01-01
  • 2014-12-11
相关资源
最近更新 更多