前言
GCD 是一个跨平台的库,它使用了大量的系统底层知识以及编译器优化内容。
本系列文章将主要分析该库的一些值得学习的地方。取其精华,去其糟粕。
base
base.h
主要声明了库中常用的宏。通过它,GCD 实现了对多平台的兼容。
attribute
__attribute__
是 GCC
的一大特色1,clang
实现大部分的描述符并添加了一些扩展2。
通过它,开发者可以告诉编译器更多的信息以达到优化程序的目的。本文的主要内容即为分析 base.h
文件内的 __attribute__
。
noreturn
#define DISPATCH_NORETURN __attribute__((__noreturn__))
宏将 __attribute__((__noreturn__))
定义为 DISPATCH_NORETURN
。
noreturn
用于告诉编译器,该函数没有返回(既不需要 return ...
语句)。
noreturn
有如下实际作用:
- 优化代码函数的调用者在调用函数返回后可能需要做一些清理工作,比如调整桟指针。
当一个函数被标记为__noreturn__
时,那么编译器就可以知道这些清理工作能够被省略掉,这样可以达到少生成一些无用的代码的目的。 - 抑制编译器的某些警告在公共头文件中,只有
DISPATCH_EXPORT DISPATCH_NOTHROW DISPATCH_NORETURN void dispatch_main(void);
方法使用了该宏。因为,我们在正常的应用开发中不会使用到该方法。所以,下面通过其它的示例代码对其进行说明。
1234DISPATCH_NORETURN extern void objc_terminate(void);int sun_terminate() {objc_terminate();}void objc_terminate(void)
是objc
库的一个退出方法。在这里,通过
extern
将其重新声明并添加DISPATCH_NORETURN
宏。int sun_terminater()
是我自定义的一个终止函数,它不接收参数,并返回int
类型的结果。在 clang 中,当函数声明了返回值,却没有提供时,编译器报出
Control reaches end of non-void function
的❗️错误提示。如下图所示。 但是,这里因为有被
noreturn
描述的函数objc_terminate();
的存在,原来的错误提示会被编译器丢失,即开发者看不到原先的❗️错误提示。一种更复杂的情况是,当函数有分支处理时,需要所有的分支都包含有被
noreturn
描述的任意函数。否则,系统仍然会提示错误。如下图所示。
nothrow
#define DISPATCH_NOTHROW __attribute__((__nothrow__))
宏将 __attribute__((__nothrow__))
定义为 DISPATCH_NOTHROW
。
该描述符的含义是,函数不会抛出异常。因为 iOS 中使用异常处理的情况比较少,所以这里就不进行展开讲解。
nonnull
1 2 3 4 5 6 7 8 |
#define DISPATCH_NONNULL1 __attribute__((__nonnull__(1))) #define DISPATCH_NONNULL2 __attribute__((__nonnull__(2))) #define DISPATCH_NONNULL3 __attribute__((__nonnull__(3))) #define DISPATCH_NONNULL4 __attribute__((__nonnull__(4))) #define DISPATCH_NONNULL5 __attribute__((__nonnull__(5))) #define DISPATCH_NONNULL6 __attribute__((__nonnull__(6))) #define DISPATCH_NONNULL7 __attribute__((__nonnull__(7))) #if __clang__ && __clang_major__ |
__nonnull__
用于指定函数或方法的入参类型不为空指针。
它有两种写法,__attribute__((__nonnull__))
或者 __attribute__((__nonnull__(1,2))
。
第一种要求所有的入参类型不为空指针,第二种要求指定位置的参数不为空指针。
比如下面的方法声明中,要求第二个参数 block
不能为空指针。当 block
为空时,根本没有必要调用该方法,所以这里使用 DISPATCH_NONNULL2
对其进行限制。
1 2 3 4 |
DISPATCH_EXPORT DISPATCH_NONNULL2 DISPATCH_RETURNS_RETAINED_BLOCK DISPATCH_WARN_RESULT DISPATCH_NOTHROW dispatch_block_t dispatch_block_create(dispatch_block_flags_t flags, dispatch_block_t block); |
sentinel
#define DISPATCH_SENTINEL __attribute__((__sentinel__))
宏将 __attribute__((__sentinel__))
定义为 DISPATCH_SENTINEL
。
__sentinel__
的中文意思是哨兵。
当接收可变参数时,可以通过它指定NULL所在的位置。
默认情况下,NULL所在的位置为从后往前数第0个为NULL。
常见于数组或者字典等集合类型接受多个参数时指定最后一个为NULL。
通过它,函数内部可以知道何时终止参数的处理。
示例如下代码如下:
修饰符指定了从后往前数第三个为NULL,如果不满足这个规则,编译器会产生警告⚠️。
pure
#define DISPATCH_PURE __attribute__((__pure__))
宏将 __attribute__((__pure__))
定义为 DISPATCH_PURE
。
__pure__
的中文意思是纯净的。它只依赖于入参和全局变量进行处理。并且不会对外部造成影响。
pure
修饰的函数不具有可重入性,但是可以通过关中断、信号量(即P、V操作)等手段对全局变量加以保护以实现。
当被 pure
修饰的函数的返回结果没有被使用时,编译器会直接丢弃调用被 pure
修饰的函数的相关代码。
下面以 Implications of pure and constant functions中的例子进行说明。
1 2 3 4 5 6 7 8 9 10 11 |
int someimpurefunction(int a, int b); int somepurefunction(int a, int b) __attribute__((pure)); int testfunction(int a, int b, int c) { int res1 = someimpurefunction(c, b); int res2 = somepurefunction(b, c); int res3 = a + b - c; return a; } |
代码分析:
我们很容易发现,testfunction
函数内的部分代码是可以被忽略的。
比如 int res2 = somepurefunction(b, c);
,只有返回值 res2
是由该函数生成的,在它的内部不会影响全局变量,所以该行代码可以全部忽略。
代码行:int res2 = somepurefunction(b, c);
在执行过程中可能影响全局变量,所以无法忽略,但是其返回值没有用处,所以,返回值可以忽略返回值。
代码行: int res3 = a + b - c;
很明显,因为返回结果 res3 没有使用,该行代码可以忽略掉。
所以,编译优化后代码为:
1 2 3 4 5 6 7 |
int someimpurefunction(int a, int b); int testfunction(int a, int b, int c) { someimpurefunction(c, b); return a; } |
const
DISPATCH_CONST __attribute__((__const__))
宏将 __attribute__((__const__))
定义为 DISPATCH_CONST
。
__const__
与 __pure__
类似,但是 __const
不能依赖于全局变量。也就意味着,const
修饰的函数是幂等的,具有可重入性。
下面仍然通过改造 Implications of pure and constant functions 中的例子进行说明。