cxx 强符号弱符号
六月 21, 2022 [c++] #c++cxx 强符号弱符号
c++强符号与弱符号
编译器默认函数和初始化了的全局变量为强符号,未初始化的全局变量为弱符号,也可以通过GCC "attribute((weak))"来定义任何一个强符号变为弱符号。 GCC 中,我们也可以通过使用"attribute((weakref))"这个扩展关键字来声明一个对外部函数的引用为弱引用。(attribute((weak)))void foo();
链接器链接规则
不允许强符号被多次定义,如果有多个强符号定义,则链接器报符号重定义 如果一个符号在某个目标文件中是强符号,在其他文件中都是弱符号,那么选择强符号 如果一个符号在所有目标都符号,那么选择其中占用空间最大的一个
用途
这种弱符号与弱引用对于库来说十分有用,比如库中定义的弱符号可以被用户定义的强符号所覆盖,从而使得程序可以使用自定义版本的库函数, 或者程序可以对某些扩展功能模块的引用定义为弱引用,当我们将扩展模块与程序链接在一起的时,功能模块就可以正常使用。 如果我们去掉某些模块,那么程序也可以正常链接,只是缺少了相应的功能,这使得程序的功能更加容易裁剪和组合。
example
//main.c
#include <stdio.h>
__attribute__((weak)) void show_msg()
{
printf("weaksymbol test.\n");
}
int main()
{
show_msg();
return 0;
}
//show.c
#include <stdio.h>
void show_msg()
{
printf("strongsymbol test.\n");
}
gcc main.c ./a.out weaksymbol test.
gcc main.c show.c ./a.out strongsymbol test.
tcmalloc 也是利用强弱符号的规则,可以仅需要链接-ltcmalloc就可以替glibc分配方法
强弱符号总结
针对强弱符号的概念,链接器就会按照如下规则处理与选择被多次定义的全局符号:
不允许强符号被多次定义 (即不同的目标文件中不能有同名的强符号),如果有多个强符号定义,则链接器报符号重复定义错误。 如果一个符号在某个目标文件中是强符号,在其他文件中都是弱符号,那么选择强符号。 如果一个符号在所有目标文件中都是弱符号,那么选择其中占用空间最大的一个。 对于最后一点,如果两个文件同时定义了 global_var 变量,类型分别为 int 和 double ,也就是分别占用 4 和 8 字节,那么 global_var 符号最终占用的是 8 字节。
注意,尽量不要使用多个不同类型的弱符号,否则容易导致很难发现的程序错误。
强弱引用
一些对外部文件的符号引用,如果在被链接成可执行文件时,没有找到相关的符号,那么链接器就会报符号未定义错误,这种被称为强引用 (Strong Reference)。
与之相对应还有一种弱引用 (Weak Reference),如果可以找到符号则连接,否则连接器会默认设置为 0 或者一个特殊值。
weakref
在 GCC 中,可以使用 attribute((weakref)) 来声明对一个外部函数的引用为弱引用。
/* main.c */
#include <stdio.h>
static void foo() __attribute__((weakref("bar")));
int main(void)
{
if (foo) {
foo();
}
return 0;
}
/* other.c */
#include <stdio.h>
void bar()
{
printf("Hello World!!!\n");
}
注意,在声明弱引用时需要使用 static 关键字。 如果通过 gcc main.c 进行编译则不会打印任何东西,而通过 gcc main.c other.c 编译会看到相关的 Hello World!!! 输出。
weak_alias
在 glibc 代码中,可以看到很多对函数的属性修饰,包括了各种 alias 的定义,这里简单看下 weak_alias 的相关内容。 函数的属性定义在 include/libc-symbols.h 头文件中,其中 weak_alias 的定义如下。
/* Define ALIASNAME as a weak alias for NAME.
If weak aliases are not available, this defines a strong alias. */
# define weak_alias(name, aliasname) _weak_alias (name, aliasname)
# define _weak_alias(name, aliasname) \
extern __typeof (name) aliasname __attribute__ ((weak, alias (#name)));
可以参考 glibc 中的一个实际应用,也就是 gettimeofday() 。
# include <sysdep.h>
# include <errno.h>
int __gettimeofday (struct timeval *tv, struct timezone *tz)
{
return INLINE_SYSCALL (gettimeofday, 2, tv, tz);
}
libc_hidden_def (__gettimeofday)
weak_alias (__gettimeofday, gettimeofday)
libc_hidden_weak (gettimeofday)
example
// lib.cc
#include <stdio.h>
void foobar() __attribute__ ((weak));
void foobar(void)
{
printf("libfoobar test\n");
}
// app.cc
#include <stdio.h>
void foobar(void)
{
printf("app test\n");
}
// main.cc
#include <stdio.h>
void foobar(void);
int main(void)
{
foobar();
return 0;
}
// makefile
all:
gcc -c foobar.c
ar crv libfoobar.a foobar.o
#gcc main.c app.c libfoobar.a -o foobar
gcc main.c libfoobar.a -o foobar
首先生成一个静态库,可以通过 nm libfoobar.a 看到定义了一个 Weak 类型的 foobar() 函数。
----- 直接使用app.c中的代码 $ gcc libfoobar.a main.c app.c -o foobar $ ./foobar app test
----- 如果不使用app.c中的代码 $ gcc libfoobar.a main.c -o foobar $ ./foobar libfoobar test 在使用时需要注意顺序,如果 libfoobar.a 中包含了 weak 函数,那么应该放在最后,否则会报错 undefined reference
总结
这种弱符号和弱引用对于库来说十分有用,比如库中定义的弱符号可以被用户定义的强符号所覆盖,从而使得程序可以使用自定义版本的库函数。
或者程序可以对某些扩展功能模块的引用定义为弱引用,当用户将扩展模块与程序链接在一起时,功能模块就可以正常使用;如果我们去掉了某些功能模块,那么程序也可以正常链接,只是缺少了相应的功能,这使得程序的功能更加容易裁剪和组合。