一、修饰函数

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

        用户可以在用户文件中重新定义一个同名函数,最终编译器编译的时候,会选择用户定义的函数,如果用户没有重新定义这个函数,那么编译器就会执行__weak声明的函数,并且编译器不会报错。所以我们可以在别的地方定义一个相同名字的函数,而不必也尽量不要修改之前的函数。

        表示弱声明,若外部文件没有声明EXTI0_IRQHandler函数,则在编译链接的阶段,链接本汇编起始startup_stm32f40_41xxx.s文件即启动代码中的EXTI0_IRQHandler函数。反之,链接外部文件中的EXTI0_IRQHandler函数。

__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(GPIO_Pin);

  /* NOTE: This function should not be modified, when the callback is needed,
            the HAL_GPIO_EXTI_Callback could be implemented in the user file
   */ 
}

从这个函数中的语句和注释来看,这个函数其实什么都没有做。
我们需要在用户文件中,自己再定义一个一模一样的函数,只是我们自己定义的函数,不需要指明是弱函数。

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  if (GPIO_Pin == GPIO_PIN_13)
  {
    /* Toggle LED2 */
    HAL_GPIO_TogglePin( GPIOA,  GPIO_PIN_5);
   
  }
}

        程序在编译的时候,如果发现有两个相同名称的函数,而且其中一个是弱函数,就会忽略弱函数,使用正常的函数进行编译;如果发现只有一个弱函数,那还是会使用弱函数参与编译。

/* test1.c */
#include <stdio.h>

void HelloFun(void)
{
    printf("my hello.");
}

/* test2.c */
#include <stdio.h>

#define __weak __attribute__((weak))

__weak void HelloFun(void)
{
    printf("default hello.");
}

void main(void)
{
    HelloFun();
}

        ​__weak void HelloFun(void)函数和void HelloFun(void)函数不能在同一个.c文件中(当然,如果在同一个文件中,就没有了什么意义。因为弱函数一般用于几个模块之间的交互接口,哪有把几个模块写一个文件中的)。上面的程序中,如果我们在test1.c中没有定义HelloFun()函数,则编译器会使用test2.c中的HelloFun()函数。因此程序会打印default hello.如果在test1.c中定义了HelloFun()函数。则打印my hello.

        c99并没有__weak关键字。此关键字是编译器外扩的。所以不同的编译器可能不一样。比如gcc编译链中并没有这个关键字。而是使用__attribute__((weak))代替。为了方便移植,我们可以宏定义,如下:

#ifndef __weak
#define __weak __attribute__((weak))
#endif

        原理:连接器发现同时存在弱符号和强符号,有限选择强符号,如果发现不存在强符号,只存在弱符号,则选择弱符号。如果都不存在:静态链接,编译时会报错,动态链接:系统无法启动。

        综上所述:如果我们没有在工程中其他地方重新定义 HAL_GPIO_EXTI_Callback()函数,那么 HAL_Init 初始化函数执行的时候, 会默认执行 stm32f4xx_hal.c 文件中定义的 HAL_GPIO_EXTI_Callback函数,而这个函数没有任何控制逻辑。
如果用户在工程中重新定义函数 HAL_GPIO_EXTI_Callback,那么调用 HAL_Init 之后,会执行用户自己定义的 HAL_GPIO_EXTI_Callback函数而不会执行 stm32f4xx_hal.c 默认定义的函数。也就是说,表面上我们看到函数 HAL_GPIO_EXTI_Callback被定义了两次,但是因为有一次定义是弱函数,使用了__weak修饰符,所以编译器不会报错。

weak属性只会在静态库(.o .a )中生效,动态库(.so)中不会生效。

从上面的外部中断函数看出,_weak弱函数声明经常会出现在回调函数当中。

1、回调函数(钩子函数)

概念:函数实现方,不方便直接调用该函数, 而是有函数接口提供方简介调用该函数,称为回调函数
示例: 系统中的信号处理函数,就是一个比较典型的回调函数
1

二、修饰变量(了解)

不持有对象,所以在超出其变量作用域时,对象即被释放
使用方式

__weak int i
__attribute__((weak)) 可以声明弱变量,并且其声明方式与 __weak 相比更加灵活。
extern int Variable_Attributes_weak_1 __attribute__((weak));

        修饰变量在代码编写用的比较少。
        循环引用容易发生内存泄漏。所谓的内存泄漏就是应当废弃的对象在其作用域之外继续存在。
此代码的本意是赋予变量 test0 的对象 A 和赋予变量 test1 的对象 B 在超出其变量作用域时被释放,即在对象不被任何变量持有的状态下予以废弃。但是,循环引用使得对象不能被再次废弃。
        以下情况,虽然只有一个对象,也会出现循环引用:

id test = [[Test alloc] init];

[test setObject:test];

        因为带 __weak 修饰符的变量(即弱引用)不持有对象,所以在超出其变量作用域时,对象即被释放。如果像以下内容将可能发生循环引用的类成员变量改成附有 __weak 修饰符的成员变量的话,该现象是可以避免的。
原文:_weak修饰符详解

@inerface Test : NSObject

{

id __weak obj_;

}

  • (void)setObject:(id __strong)obj;

@end

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐