11

简而言之:LLVM/Clang 是否支持“弱”属性?

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

#if defined(HAVE_HWSERIAL0)
  void serialEvent() __attribute__((weak));
  bool Serial0_available() __attribute__((weak));
#endif

我发现它很有趣,并且我读到如果未定义,链接器应将其设置为 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();
}

文件lib.h

#ifndef LIB_FUNC
#define LIB_FUNC

void lib_func();

#endif

文件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;
}

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

MBA-Anton:Weak_issue asmirnov$ ./main
lib_func()
my_weak_func()
finished

但如果我使用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)

要更详细地了解 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

我应该怎么办?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();
}
4

2 回答 2

9

似乎(据我所知),Apple 对弱链接的描述具有误导性。如果定义在链接时实际上可用,我只成功地将函数标记为weak/weak_import。这与通常的 Linux 行为相反,在链接时不需要定义弱链接符号。

例如,以下代码在 Ubuntu 14.04 和 GCC 4.8.2 上编译,但不能在Mac OS X v10.9.5 (Mavericks) 和 Clang 上编译:

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

int main()
{
    if (weakfunc) return weakfunc();
    else        return -1;
}

我发现的最简单的解决方法是明确告诉链接器不要定义有问题的符号。例如,clang test.c -Wl,-U,_myfunc。请注意,符号的名称在 C 和 C++ 之间会有所不同。在 C 中(至少对我来说,我认为这是一致的),符号名称前面有一个下划线,如下所示。在 C++ 中,名称被损坏,所以你会得到类似的东西__Z8weakfuncv(不一定一致 - 我的 Ubuntu 盒子上的损坏名称只有一个前导下划线)。

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

一个更复杂的解决方案是链接到一个提供相关函数实现的虚拟库。例如,如果您将以下内容编译为同一目录中的 libdummy.dylib:

int weakfunc()
{
    return 1;
}

您可以对其进行弱链接

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

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

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

于 2016-01-25T00:11:24.170 回答
6

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

-two_levelnamespace

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

您可以使用 关闭两级命名空间-flat_namespace,但总的来说,我认为将其保持打开状态是一个好习惯。

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

-undefined error

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


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

您可以制作一个 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 ]
...

在哪里:

  • $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

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

于 2018-08-08T21:33:44.757 回答