良许Linux教程网 干货合集 __weak 和 __attribute__((weak)) 关键字的使用

__weak 和 __attribute__((weak)) 关键字的使用

今天在使用 Keil 编译器时,遇到了一个奇怪的问题,有 __weak 关键字的函数没有起作用。以前,我对 __weak 关键字的理解比较简单,认为编译器会自动用没有 __weak 关键字的同名函数替换带有 __weak 关键字的函数。而且 __weak 函数可以没有具体的定义,编译器也不会报错。但是今天遇到的情况让我感到困惑。为了更加深入地了解 __weak 关键字的使用,在这里我想以 GCC 作为对比,学习一下 ARM 中的 __weak 关键字的具体用法。

__weak 关键字在 GNU 编译器(gcc)中是一个扩展关键字。通过使用 __attribute__ 关键字,用户可以在声明时指定特定的属性。使用时,该关键字后跟双括号内的属性,例如:__attribute__((属性名字))。这些属性都是预先定义好的,其中之一就是 Weak 属性:__attribute__((weak))。在 Linux 源码中,我们经常见到这个关键字的使用。

image-20240106183342641
image-20240106183342641

GCC 不多介绍,重点关注 ARM。在 ARM 编译器(armcc)中,支持和 GCC 相同的关键字 __attribute__,使用方式也基本相同,如下:

__attribute__((attribute1, attribute2, ...))   // 例如:void * Function_Attributes_malloc_0(int b) __attribute__((malloc));
__attribute__((__attribute1__, __attribute2__, ...))    // 例如:static int b __attribute__((__unused__));
12

  1. 当函数属性发生冲突时,编译器将使用更安全或更强的一个

除此之外,ARM 编译器(armcc)还扩展了一个关键字 __weak,例如:__weak void f(void); 或者 __weak int i;。ARM 的汇编器(armasm)以另一种方式 [WEAK] 支持该特性。

「注意:」在许多源码中,经常通过宏定义的形式来定义关键字,例如 上面linux 中的 __weak 就是 宏定义的 __attribute__((weak))

强/弱符号

在 GCC 中,被 __attribute__((weak)) 修饰的符号,我们称之为 「弱符号(Weak Symbol)」。例如:弱函数、弱变量;没有 __attribute__((weak)) 修饰的符号被称为「强符号」。在 ARM 中,没有弱符号和强符号这种叫法,只有个「弱引用(Weak References)」「非弱引用(non-weak reference )」「弱定义(Weak definitions)」「非弱定义(non-weak definition)」 的介绍章节。

  1. 编译器和汇编器都可以输出弱符号。

非弱引用

非弱引用就是我们平常使用的对于非弱函数或者弱变量的引用。如果链接器无法在到目前为止已加载内容中解析对正常非弱符号的引用问题,则 「它会尝试通过在库中找到符号」 来解决此问题:

  • 如果找不到此类引用,则链接器将报告错误。
  • 如果解析了这样的引用,则从入口点可以通过至少一个非弱引用来访问的节区被标记为已使用。这样可以确保链接器不会将该节作为未使用的节删除。 每个非弱引用都必须通过一个定义来解决。 如果有多个定义,则链接器将报告错误。

弱引用

「引用弱声明的函数或者变量的引用即为弱引用。」 链接器不会从库中加载对象来解析弱引用。仅当由于其他原因在镜像中包含了定义时,它才能解析弱引用。「弱引用不会导致链接器将包含定义的节区标记为已使用,因此链接器可能会将其标记为未使用而删除。」

__weak

__weak 关键字可以应用于函数和变量的声明以及函数定义。

声明

__weak 可以用于函数声明或者变量的声明。对于声明,此存储类指定一个 extern 对象声明,即使该对象不存在,对于该声明的引用也不会导致链接器对未解析的引用(找不到定义的引用)当做错误来处理。 ??如果「在当前编译单元中」可以找到 __weak 声明定义,则会用找到的定义替换 __weak 引用;对于找不到定义 __weak 的声明(函数或变量,如上图的 FuncB),编译器做如下处理:

  • 引用被解析为分支连接指令 BL。等效于将被引用的分支为 NOP
  • 直接将引用替换为 NOP 指令

注意:必须是在当前编译单元,不再当前编译单元的没有意义(例如 ExtFuncA 在 main.c 中只有__weak 声明,但是没有定义)。具体看下图的测试代码:image-20240106183349013「注意:用 __weak 声明然后不使用 __weak 定义的函数的行为相当于非弱函数。」 这与 _attribute__((weak)) 关键字不同!

定义

__weak 定义的函数弱输出其符号。弱定义的函数的行为类似于正常定义的函数,除非将同名的非弱定义的函数链接到同一镜像中。 如果在同一镜像中同时存在非弱定义函数和弱定义函数,则对该函数的所有调用都会解析为调用非弱函数,否则直接使用弱定义的函数(与上面的若声明不同)。 ??如果可以使用多个弱定义,则除非使用链接器选项 --muldefweak,否则链接器会生成一条错误消息。在这种情况下,链接器随机选择一个供所有调用来使用。使用方式如下:

/* a.h !!!注意所在文件不同!!! */
void FuncA(void);
void FuncB(void);

/* a.c !!!注意所在文件不同!!! */
void FuncA(void)
{
    FuncB();        /* 这里将替换为 main.c 中的 FuncB */
}

__weak void FuncB(void)     /* 弱定义 */
{

}

/* main.c !!!注意所在文件不同!!! */
void FuncB(void)
{

}

int main (void)
{
 FuncB();
}
12345678910111213141516171819202122232425

注意,函数的声明一定不能添加 __weak 关键字。具体如下图:image-20240106183353257

「注意:用 __weak 声明然后不使用 __weak 定义的函数的行为相当于非弱函数。」 这与 _attribute__((weak)) 关键字不同!

限制

  1. 函数或变量不能在同一编译中同时弱和非弱地使用。
void f(void);

void g()
{
    f();    /* 非弱函数引用 */
}

__weak void f(void);

void h()
{
    f();    /* 弱函数引用 */
}
12345678910111213
  1. 不能在定义函数或变量的同一编译中使用弱函数或弱变量,如下将导致编译错误(正确的使用方式参考上面的使用示例):
/* a.c 如下同一文件中的定义及使用将报错 */
__weak void f(void);

void h()
{
    f();
}

void f()
{

}
123456789101112
  1. 弱函数不能是内联函数

「attribute」((weak))

__attribute__关键字使您可以指定变量或结构字段,函数和类型的特殊属性(与具体属性)。该关键字的作用与 __weak 的作用基本是一样的,在使用时有些不同,此外在某些情况下,编译的处理也有些区别。

声明

??这个参数是 GUN 编译器的一个扩展,ARM 编译器也支持该关键字。__attribute__((weak)) 可以声明弱变量,并且其声明方式与 __weak 相比更加灵活。除了 __weak 的声明方式,我们还可以用 extern int Variable_Attributes_weak_1 __attribute__((weak));??_attribute__((weak)) 可以声明弱函数,其声明方式与 __weak 相比更加灵活。除了 __weak 的声明方式,我们还可以用 extern int Function_Attributes_weak_0 (int b) __attribute__((weak));

??任何包含了 __attribute__((weak)); 声明的文件的中的同名函数定义,都将被当做弱函数。如下图:image-20240106183358527

开篇提出的问题就是因为上图所示的这种情况!

「注意:用 __attribute__((weak)) 声明然后不使用 __attribute__((weak)) 进行定义的函数的行为就像是弱函数。」 这与 __weak 关键字的用法不同。

在 GNU 模式中需要 extern 限定符。在非 gnu 模式下,编译器假设如果变量不是 extern,那么它将像对待其他非弱变量一样对待。

定义

__attribute__((weak)) 定义的函数弱输出其符号(与 __weak 相同)。其使用方式有以下两种:

__attribute__((weak)) void FuncA(void)
{
    printf("Weak FuncA!\r\n");
}
/* 或者 */
void __attribute__((weak)) FuncA(void)
{
    printf("Weak FuncA!\r\n");
}
123456789

「注意:用 __attribute__((weak)) 声明然后不使用 __attribute__((weak)) 进行定义的函数的行为就像是弱函数。」 这与 __weak 关键字的用法不同。除此之外,没有啥不同,这里不再多说!

区别

  1. 如上介绍,__weak__attribute__((weak)) 在声明和定义的时候,其所处的位置有不同。
  2. __weak 仅在函数定义中使用时才会生成弱函数。而在任何情况下(声明和定义) __attribute__((weak)) 都会生成弱函数,无论是用于函数定义还是用于函数声明中!

参考

  1. https://community.arm.com/developer/tools-software/tools/f/keil-forum/34584/run-error-when-use-__weak-to-define-function
  2. https://blog.csdn.net/rensheng__rumeng/article/details/78634804
  3. http://blog.sina.com.cn/s/blog_62d3426b0100g7n6.html
  4. http://www.keil.com/support/man/docs/armcc/armcc_chr1359124970859.htm
  5. http://infocenter.arm.com/help/topic/com.arm.doc.dui0472j/DUI0472J_armcc_user_guide.pdf
  6. https://github.com/ARM-software/CMSIS_5/issues/141

以上就是良许教程网为各位朋友分享的Linu系统相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多干货等着你 !

137e00002230ad9f26e78-265x300
本文由 良许Linux教程网 发布,可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。
良许

作者: 良许

良许,世界500强企业Linux开发工程师,公众号【良许Linux】的作者,全网拥有超30W粉丝。个人标签:创业者,CSDN学院讲师,副业达人,流量玩家,摄影爱好者。
上一篇
下一篇

发表评论

联系我们

联系我们

公众号:良许Linux

在线咨询: QQ交谈

邮箱: yychuyu@163.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

关注微博
返回顶部