c进阶篇(十)——weak用法详解

前言:
  weak用于修饰弱函数或变量,以实现类似c++语言中函数重载的功能,本篇文章详细探讨weak的运用。

1 什么是weak函数

   weak 不是c语言的关键字,而是一些编译器的扩展属性,MDK-ARM中在函数定义和声明前加 __attribute__((weak)) 表示将该函数定义为弱函数,而在变量定义或声明前加 __attribute__((weak)) 表示将该变量定义为弱变量。弱函数或变量允许多次定义,编译器不会报重复定义的错误,但是调用时只能有一个有效。编译器会根据强弱程度决定调用哪个定义,简单来说就是支持重定义。普通定义的优先级大于弱定义,具体的调用规则如下。

  • 一次或多次弱定义,一次普通定义。编译器忽略所有弱定义,调用普通定义;
  • 一次或多次弱定义,无普通定义。编译器会发出警告,会调用其中一个弱定义,具体调用哪一个不确定;
  • 普通定义不能有多次,否则报错重复定义;

  注意,weak不能与static搭配,因此不存在静态局部弱函数或变量,weak只能修饰全局函数或变量。

2 weak修饰函数

2.1 weak修饰函数定义

  weak对函数定义进行修饰时,该函数定义即为弱定义。以下代码演示在函数定义处进行weak修饰。

  • hdr.h
1
extern byte Fun(void);
  • main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
#include "hdr.h"

__attribute__((weak)) byte Fun(void)
{
return 1u;
}

int32_t main(void)
{
byte byTmp = Fun();

while(1u);
}
  • com.c
1
2
3
4
5
6
#include "hdr.h"

byte Fun(void)
{
return 2u;
}

  这段代码在hdr.h中声明了全局函数 Fun ,在main.c中对该函数进行了弱定义并且返回值为 1u ,在com.c中重复定义了该函数并且返回值为 2u ,调试运行后观察到 Fun 函数的返回值为 2 。虽然有两次定义但其中一次为弱定义,一次为普通定义,这两种定义同时存在时,编译器会调用普通定义,弱定义将会失效,也即普通定义优先级高于弱定义。
  不论是弱定义还是普通定义,同一个函数在一个.c文件中只能定义一次。

  • hdr.h
1
extern byte Fun(void);
  • main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "hdr.h"

__attribute__((weak)) byte Fun(void)
{
return 1u;
}

__attribute__((weak)) byte Fun(void)
{
return 2u;
}

int32_t main(void)
{
byte byTmp = Fun();

while(1u);
}

  以上代码存在错误,在同一个.c文件中对同一个函数进行了多次定义(弱定义)。

  • hdr.h
1
extern byte Fun(void);
  • main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "hdr.h"

__attribute__((weak)) byte Fun(void)
{
return 1u;
}

byte Fun(void)
{
return 2u;
}

int32_t main(void)
{
byte byTmp = Fun();

while(1u);
}

  以上代码存在错误,在同一个.c文件中对同一个函数进行了多次定义(一次弱定义和一次普通定义)。

2.2 weak修饰函数声明

  weak也可以用来修饰函数声明。

  • hdr.h
1
extern __attribute__((weak)) byte Fun(void);
  • main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
#include "hdr.h"

byte Fun(void)
{
return 1u;
}

int32_t main(void)
{
byte byTmp = Fun();

while(1u);
}

  以上代码在.h文件中对函数声明使用 __attribute__((weak)) 修饰,并且在.c中对该函数普通定义,这样代码是合法的。若在多个文件.h中进行带weak修饰的函数声明,则编译器会给出警告,见以下代码。

  • hdr.h
1
2
3
4
#include "com.h"
#include "other.h"

extern __attribute__((weak)) byte Fun(void);
  • com.h
1
extern __attribute__((weak)) byte Fun(void);
  • other.h
1
extern byte Fun(void);
  • main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
#include "hdr.h"

byte Fun(void)
{
return 1u;
}

int32_t main(void)
{
byte byTmp = Fun();

while(1u);
}

  以上代码是正确的,可以看出在至少有一个weak修饰函数声明的情况下,增加普通的函数声明是合法的。

2.3 weak同时修饰函数定义和声明

  weak同时修饰函数定义和声明时,还有分几种情况讨论。这种方式不推荐。

  • hdr.h
1
2
3
#include "com.h"

extern __attribute__((weak)) byte Fun(void);
  • com.h
1
2
3
#include "hdr.h"

extern __attribute__((weak)) byte Fun(void);
  • main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
#define "hdr.h"

__attribute__((weak)) byte Fun(void)
{
return 1u;
}

int32_t main(void)
{
byte byTmp = Fun();

while(1u);
}

  当只有一次函数弱定义,一个及以上函数弱声明可都或部分被 __attribute__((weak)) 修饰,这时候编译器是没有报错和警告的。

  • hdr.h
1
extern __attribute__((weak)) byte Fun(void);
  • com.h
1
2
3
4
#include "hdr.h"

extern __attribute__((weak)) byte Fun(void);
extern byte Fun(void);
  • main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
#define "hdr.h"

__attribute__((weak)) byte Fun(void)
{
return 1u;
}

int32_t main(void)
{
byte byTmp = Fun();

while(1u);
}
  • com.c
1
2
3
4
5
6
#include "hdr.h"

__attribute__((weak)) byte Fun(void)
{
return 2u;
}

  当有多次弱定义和声明(可含有普通声明)而无普通定义时,编译器会发出警告,会调用其中一个弱定义,具体调用哪一个不确定。

  • hdr.h
1
__attribute__((weak)) extern byte Fun(void);
  • com.c
1
2
3
4
5
6
#include "hdr.h"

__attribute__((weak)) byte Fun(void)
{
return 1u;
}
  • main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
#define "hdr.h"

__attribute__((weak)) byte Fun(void)
{
return 2u;
}

int32_t main(void)
{
byte byTmp = Fun();

while(1u);
}

  当同时对函数定义和声明用 __attribute__((weak)) 进行修饰时,如果此时还有一个普通定义,则会产生警告。

3 weak修饰变量

3.1 weak修饰变量定义

  weak对变量定义进行修饰时,该变量定义即为弱定义。以下代码演示在变量定义处进行weak修饰。

  • hdr.h
1
extern byte g_byTmp;
  • main.c
1
2
3
4
5
6
7
8
9
10
#include "hdr.h"

__attribute__((weak)) byte g_byTmp = 1u;

int32_t main(void)
{
byte byTmp = g_byTmp;

while(1u);
}
  • com.c
1
2
3
#include "hdr.h"

byte g_byTmp = 2u;

  这段代码在hdr.h中声明了全局变量 g_byTmp ,在main.c中对该变量进行了弱定义并且初始化值为 1u ,在com.c中重复定义了该变量并且初始化值为 2u ,调试运行后观察到 g_byTmp 变量的值为 2 。虽然有两次定义但其中一次为弱定义,一次为普通定义,这两种定义同时存在时,编译器会调用普通定义,弱定义将会失效,也即普通定义优先级高于弱定义。
  不论是弱定义还是普通定义,同一个变量在一个.c文件中只能定义一次。

  • hdr.h
1
extern byte g_byTmp;
  • main.c
1
2
3
4
5
6
7
8
9
10
11
#include "hdr.h"

__attribute__((weak)) byte g_byTmp = 1u;
__attribute__((weak)) byte g_byTmp = 2u;

int32_t main(void)
{
byte byTmp = g_byTmp;

while(1u);
}

  以上代码存在错误,在同一个.c文件中对同一个变量进行了多次定义(弱定义)。

  • hdr.h
1
extern byte g_byTmp;
  • main.c
1
2
3
4
5
6
7
8
9
10
11
#include "hdr.h"

__attribute__((weak)) byte g_byTmp = 1u;
g_byTmp = 2u;

int32_t main(void)
{
byte byTmp = g_byTmp;

while(1u);
}

  以上代码存在错误,在同一个.c文件中对同一个变量进行了多次定义(一次弱定义和一次普通定义)。

3.2 weak修饰变量声明

  weak也可以用来修饰变量声明。

  • hdr.h
1
extern __attribute__((weak)) byte g_byTmp;
  • main.c
1
2
3
4
5
6
7
8
9
10
#include "hdr.h"

byte g_byTmp = 1u;

int32_t main(void)
{
byte byTmp = g_byTmp;

while(1u);
}

  以上代码在.h文件中对变量声明使用 __attribute__((weak)) 修饰,并且在.c中对该变量普通定义,这样代码是合法的。若在多个文件.h中进行带weak修饰的变量声明,则编译器会给出警告,见以下代码。

  • hdr.h
1
2
3
4
#include "com.h"
#include "other.h"

extern __attribute__((weak)) byte g_byTmp;
  • com.h
1
extern __attribute__((weak)) byte g_byTmp;
  • other.h
1
extern byte g_byTmp;
  • main.c
1
2
3
4
5
6
7
8
9
10
#include "hdr.h"

byte g_byTmp = 1u;

int32_t main(void)
{
byte byTmp = g_byTmp;

while(1u);
}

  以上代码是正确的,可以看出在至少有一个weak修饰变量声明的情况下,增加普通的变量声明是合法的。

3.3 weak同时修饰变量定义和声明

  weak同时修饰变量定义和声明时,还有分几种情况讨论。这种方式不推荐。

  • hdr.h
1
2
3
#include "com.h"

extern __attribute__((weak)) byte g_byTmp;
  • com.h
1
2
3
#include "hdr.h"

extern __attribute__((weak)) byte g_byTmp;
  • main.c
1
2
3
4
5
6
7
8
9
10
#define "hdr.h"

__attribute__((weak)) byte g_byTmp = 1u;

int32_t main(void)
{
byte byTmp = g_byTmp;

while(1u);
}

  当只有一次变量弱定义,一个及以上变量弱声明可都或部分被 __attribute__((weak)) 修饰,这时候编译器是没有报错和警告的。

  • hdr.h
1
extern __attribute__((weak)) byte g_byTmp;
  • com.h
1
2
3
4
#include "hdr.h"

extern __attribute__((weak)) byte g_byTmp;
extern byte g_byTmp = 1u;
  • main.c
1
2
3
4
5
6
7
8
9
10
#define "hdr.h"

__attribute__((weak)) byte g_byTmp = 1u;

int32_t main(void)
{
byte byTmp = g_byTmp;

while(1u);
}
  • com.c
1
2
3
#include "hdr.h"

__attribute__((weak)) byte g_byTmp = 2u;

  当有多次弱定义和声明(可含有普通声明)而无普通定义时,编译器会发出警告,会调用其中一个弱定义,具体调用哪一个不确定。

  • hdr.h
1
__attribute__((weak)) byte g_byTmp = 1u;
  • com.c
1
2
3
#include "hdr.h"

byte g_byTmp = 4u;
  • main.c
1
2
3
4
5
6
7
8
9
10
#define "hdr.h"

__attribute__((weak)) byte g_byTmp = 3u;

int32_t main(void)
{
byte byTmp = g_byTmp;

while(1u);
}

  当同时对变量定义和声明用 __attribute__((weak)) 进行修饰时,如果此时还有一个普通定义,则会产生警告。