llvm/clang是否支持弱链接的"弱"属性?

4nt*_*ine 6 c++ linker llvm clang weak-linking

简言之:确实llvm/ clang支持"弱"属性?

我正在学习一些Arduino库源(HardwareSerial.cpp更详细),我发现了一些weak我之前从未使用过的有趣属性:

#if defined(HAVE_HWSERIAL0)
  void serialEvent() __attribute__((weak));
  bool Serial0_available() __attribute__((weak));
#endif
Run Code Online (Sandbox Code Playgroud)

我发现它很有趣,我已经读过,如果没有定义,链接器应该将它设置为NULL.

但是在我的测试中,clang我无法使用它.

lib.cpp:

#include "lib.h"
#include <stdio.h>

void my_weak_func() __attribute__((weak));

void lib_func() {
    printf("lib_func()\n");

    if (my_weak_func)
        my_weak_func();
}
Run Code Online (Sandbox Code Playgroud)

lib.h:

#ifndef LIB_FUNC
#define LIB_FUNC

void lib_func();

#endif
Run Code Online (Sandbox Code Playgroud)

main.cpp中:

#include "lib.h"
#include <stdio.h>

#ifdef DEFINE_WEAK
void my_weak_func() {
    printf("my_weak_func()\n"); 
}
#endif

int main() {

    lib_func();

    printf("finished\n");
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

如果我使用g++ lib.cpp main.cpp -o main -DDEFINE_WEAK我能够使用它:

MBA-Anton:Weak_issue asmirnov$ ./main
lib_func()
my_weak_func()
finished
Run Code Online (Sandbox Code Playgroud)

但如果我使用g++ lib.cpp main.cpp -o main我无法链接该应用程序:

Undefined symbols for architecture x86_64:
  "my_weak_func()", referenced from:
      lib_func() in lib-ceb555.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
Run Code Online (Sandbox Code Playgroud)

更详细的关于clang:

MBA-Anton:Weak_issue asmirnov$ g++ --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/include/c++/4.2.1
Apple LLVM version 6.1.0 (clang-602.0.53) (based on LLVM 3.6.0svn)
Target: x86_64-apple-darwin14.3.0
Thread model: posix
Run Code Online (Sandbox Code Playgroud)

我该怎么办?weakllvm/clang 是否支持属性?

PS.我已经尝试以Apple描述的方式重写lib.cpp,但仍然是相同的链接器错误:

#include "lib.h"
#include <stdio.h>

extern void my_weak_func() __attribute__((weak_import));

void lib_func() {
    printf("lib_func()\n");

    if (my_weak_func != NULL)
        my_weak_func();
}
Run Code Online (Sandbox Code Playgroud)

Ale*_*lec 5

苹果公司对弱链接的描述似乎(据我所知)是令人误解的。如果定义实际上在链接时可用,则仅将函数标记为weak / weak_import才成功。这与通常的Linux行为相反,在Linux行为中,无需在链接时定义弱链接符号。

例如,以下代码在带有gcc 4.8.2的Ubuntu 14.04上编译,但不能在带有clang的OS X 10.9.5上编译

/* test.c */
int weakfunc() __attribute__((weak));

int main()
{
    if (weakfunc) return weakfunc();
    else        return -1;
}
Run Code Online (Sandbox Code Playgroud)

我找到的最简单的解决方法是显式地告诉链接器使有问题的符号保持未定义状态。例如,clang test.c -Wl,-U,_myfunc。请注意,符号名称在C和C ++之间会有所不同。在C语言中(至少对我来说,我认为这是一致的),符号名称前面带有一个下划线,如下所示。在C ++中,名称是乱码的,因此您会得到类似的信息__Z8weakfuncv(不一定是一致的-我在Ubuntu盒子上的乱码后的名称只有一个下划线)。

按照这种方法,如果函数是在运行时定义的(例如,通过设置DYLD_INSERT_LIBRARIES环境变量预加载的库,或者如果共享库依赖项的版本在运行时与构建时不同),则符号将被解析,并且函数根据需要调用。如果未在运行时定义符号,则对函数的检查将失败,我们将继续根据需要返回-1。

稍微复杂些的解决方案是链接到提供有关功能实现的虚拟库。例如,如果在同一目录中将以下内容编译为libdummy.dylib:

int weakfunc()
{
    return 1;
}
Run Code Online (Sandbox Code Playgroud)

您可以弱链接

clang test.c -weak_library ./libdummy.dylib -flat_namespace

然后在链接时定义符号,因此链接器满意,并将在生成的二进制文件中将其标记为弱链接。通过将libdummy.dylib与-weak_library标准-l/-L链接而不是标准链接进行链接,库依赖性本身很弱,因此即使libdummy.dylib在运行时不可用,可执行文件仍将运行。

-flat_namespace参数告诉链接器使用“平面”名称空间,而不是“两级”名称空间,这显然是OS X上的默认名称空间。在两级名称空间中,每个链接的符号都标有来自它的库,因此,如果没有此链接器,链接器将仅接受来自名为libdummy.dylib的库的弱函数版本。请注意,在第一种将符号标记为未定义的情况下,该符号被视为来自平面名称空间,因为链接器不知道它在运行时可能位于哪个库中。

  • 我倾向于同意存在一个错误,但我认为它存在于 ld 的 OS X 版本中,而不是 clang 中。如果您使用这些解决方法检查生成的二进制文件(“dyldinfo -build a.out”),我相信它将将该符号显示为“弱导入”,这就是您想要的。问题在于,如果在构建时未定义弱导入符号,则 ld 无法正确处理该符号。 (2认同)

zne*_*eak 5

它因设计而失败,因为链接器没有足够的信息。具体来说,由于两个默认链接器设置的组合,它不起作用:

-two_levelnamespace

-two_levelnamespace指示链接器按名称和库安装路径绑定外部符号。使用时,链接器根据它在链接时找到符号的位置将符号与库相关联,给定传递的库集。如果链接器没有找到该符号,则它不会知道它来自哪个库。

您可以使用 关闭两级命名空间-flat_namespace,但总的来说,我认为保留它是一个很好的做法。

Linux 的 ld.so 不支持二级命名空间,所以这不是问题。假设每个未定义的符号在某个库中都有一个定义,以便在运行时被发现。

-undefined error

-undefined设置决定了如何处理在链接时没有可见定义的符号,默认值是错误输出。另一个明智的选择是dynamic_lookup,它告诉动态链接器自己找出符号的位置。


更改这些设置中的任何一个都可以解决您的问题,但它是严厉的。您还可以告诉链接器对特定符号使用动态查找,并error通过传递-U _my_weak_functold-Wl,-U,_my_weak_funcClang(告诉它将其传递给链接器)来保持默认值。该_符号名的前缀是必要的。

您可以制作一个 tbd 文件并使用它代替动态库来告诉链接器如果实现了弱符号,将在何处找到弱符号,而不是强制弱函数使用动态查找。Apple 为其库和框架使用 tbd 文件,这使得弱链接起作用。不过,这个过程有点乏味,因为 Apple 不提供自动为库创建 tbd 文件的工具。您需要将以下格式的文件作为库传递给编译器:

--- !tapi-tbd-v3
archs:           [ $ARCH ]
uuids:           [ '$ARCH: $UUID' ]
platform:        $ARCH
install-name:    $INSTALL_PATH
current-version: $CURRENT_VERSION
objc-constraint: none
exports:         
  - archs:           [ $ARCH ]
    symbols:         [ _my_weak_func ]
...
Run Code Online (Sandbox Code Playgroud)

在哪里:

  • $ARCH 是您要构建的事物的架构名称(例如“x86_64”,不带引号)
  • $UUID 可以查询 otool -l $path_to_your_lib | grep -A 2 LC_UUID
  • $INSTALL_PATH 和 $CURRENT_VERSION 可以查询 otool -l $path_to_your_lib | grep -A 4 LC_ID_DYLIB

这将使链接器知道哪个库应该包含您的弱符号。

  • 实际上苹果就提供了这样的工具。它位于 Xcode 的深处,就像大多数一样:`/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/tapi stubify &lt;&lt;&lt;your lib path&gt;&gt;` (2认同)