GCD源码分析之base.h

本文属于原创文章。转载请联系作者。1711809-d203d3a59d024556

GCD & Base

前言

GCD 是一个跨平台的库,它使用了大量的系统底层知识以及编译器优化内容。
本系列文章将主要分析该库的一些值得学习的地方。取其精华,去其糟粕。

base

base.h 主要声明了库中常用的宏。通过它,GCD 实现了对多平台的兼容。

attribute

__attribute__GCC 的一大特色1clang 实现大部分的描述符并添加了一些扩展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); 方法使用了该宏。

    因为,我们在正常的应用开发中不会使用到该方法。所以,下面通过其它的示例代码对其进行说明。

    void objc_terminate(void)objc 库的一个退出方法。

    在这里,通过 extern 将其重新声明并添加 DISPATCH_NORETURN 宏。

    int sun_terminater() 是我自定义的一个终止函数,它不接收参数,并返回 int 类型的结果。

    在 clang 中,当函数声明了返回值,却没有提供时,编译器报出Control reaches end of non-void function的❗️错误提示。如下图所示。

    111711809-2c7a073f8d455c31

     但是,这里因为有被 noreturn 描述的函数 objc_terminate(); 的存在,原来的错误提示会被编译器丢失,即开发者看不到原先的❗️错误提示。

    一种更复杂的情况是,当函数有分支处理时,需要所有的分支都包含有被noreturn 描述的任意函数。否则,系统仍然会提示错误。如下图所示。

    121711809-38f2816a9df1015d

nothrow

#define DISPATCH_NOTHROW __attribute__((__nothrow__)) 宏将 __attribute__((__nothrow__)) 定义为 DISPATCH_NOTHROW

该描述符的含义是,函数不会抛出异常。因为 iOS 中使用异常处理的情况比较少,所以这里就不进行展开讲解。

nonnull

__nonnull__用于指定函数或方法的入参类型不为空指针。

它有两种写法,__attribute__((__nonnull__)) 或者 __attribute__((__nonnull__(1,2))

第一种要求所有的入参类型不为空指针,第二种要求指定位置的参数不为空指针。

比如下面的方法声明中,要求第二个参数 block 不能为空指针。当 block 为空时,根本没有必要调用该方法,所以这里使用 DISPATCH_NONNULL2 对其进行限制。

sentinel

#define DISPATCH_SENTINEL __attribute__((__sentinel__)) 宏将 __attribute__((__sentinel__)) 定义为 DISPATCH_SENTINEL

__sentinel__ 的中文意思是哨兵。

当接收可变参数时,可以通过它指定NULL所在的位置。

默认情况下,NULL所在的位置为从后往前数第0个为NULL。
常见于数组或者字典等集合类型接受多个参数时指定最后一个为NULL。
通过它,函数内部可以知道何时终止参数的处理。

示例如下代码如下:

131711809-c33a8382da3168f9

修饰符指定了从后往前数第三个为NULL,如果不满足这个规则,编译器会产生警告⚠️。

pure

#define DISPATCH_PURE __attribute__((__pure__)) 宏将 __attribute__((__pure__)) 定义为 DISPATCH_PURE

__pure__ 的中文意思是纯净的。它只依赖于入参和全局变量进行处理。并且不会对外部造成影响。

pure 修饰的函数不具有可重入性,但是可以通过关中断、信号量(即P、V操作)等手段对全局变量加以保护以实现。

当被 pure 修饰的函数的返回结果没有被使用时,编译器会直接丢弃调用被 pure 修饰的函数的相关代码。

下面以 Implications of pure and constant functions中的例子进行说明。

代码分析:

我们很容易发现,testfunction 函数内的部分代码是可以被忽略的。

比如 int res2 = somepurefunction(b, c);,只有返回值 res2 是由该函数生成的,在它的内部不会影响全局变量,所以该行代码可以全部忽略。

代码行:int res2 = somepurefunction(b, c); 在执行过程中可能影响全局变量,所以无法忽略,但是其返回值没有用处,所以,返回值可以忽略返回值。

代码行: int res3 = a + b - c; 很明显,因为返回结果 res3 没有使用,该行代码可以忽略掉。

所以,编译优化后代码为:

const

DISPATCH_CONST __attribute__((__const__)) 宏将 __attribute__((__const__)) 定义为 DISPATCH_CONST

__const____pure__ 类似,但是 __const 不能依赖于全局变量。也就意味着,const 修饰的函数是幂等的,具有可重入性。

下面仍然通过改造 Implications of pure and constant functions 中的例子进行说明。

输出日志:

编译器发现,lookup(val)被调用了三次,且在该过程中,val的值没有发生变化。所以编译器只调用了一次该函数,并缓存了其结果。

特殊情况1

请注意,虽然 const 修饰的函数是幂等的,但是当被 const 修饰的函数有入参时,我没有发现能够合理优化如下代码的编译器。

在下面的代码中, square 函数的入参并没有发生变化,所以,合理的优化应该是只调用一次 square 函数,并缓存结果以提供给后续的代码使用。但实际上,square 函数被调用了1000次。

特殊情况2

当被 const 修饰的函数有没有入参时,编译结果却发生了非常大的变化。编译器会尽可能的在编译期间执行里面的代码,并将结果直接赋值给调用方。
示例代码:

优化结果:

warn_unused_result

#define DISPATCH_WARN_RESULT __attribute__((__warn_unused_result__)) 宏将 __attribute__((__warn_unused_result__)) 定义为 DISPATCH_WARN_RESULT

当函数的调用方没有使用函数的返回结果时,会产生一个警告。

比如下面的代码会在第5行产生一个⚠️警告。Ignoring return value of function declared with warn_unused_result attribute

malloc

#define DISPATCH_MALLOC __attribute__((__malloc__)) 宏将 __attribute__((__malloc__)) 定义为 DISPATCH_MALLOC

当一个函数返回的指针是类似于调用 malloc 函数时,可以指定该修饰符。

它表示通过这个函数返回的非空指针肯定与当前程序中其它的有效指针不同,即其值肯定不同与当前任何有效指针。这样编译器就能够识别出该指针不存在别名,可以进行更好的优化。

always_inline

#define DISPATCH_ALWAYS_INLINE __attribute__((__always_inline__)) 宏将 __attribute__((__always_inline__)) 定义为 DISPATCH_ALWAYS_INLINE

内联(inline) 函数,会导致代码增大(同一份代码存在多个地方),但是运行速度会加快(不需要跨函数调用)。

一般情况下,只有开启优化选项后,部分函数才能够被编译器当做内联函数编译。但是 __always_inline__ 可以在没有开启优化选项时,依然强制某个函数当做内联函数使用。

unavailable

#define DISPATCH_UNAVAILABLE __attribute__((__unavailable__)) 宏将 __attribute__((__unavailable__)) 定义为 DISPATCH_UNAVAILABLE

__unavailable__用于声明某个函数不可用。

DISPATCH_EXPORT

DISPATCH_EXPORT 可以在文件中声明一个全局变量或函数。

DISPATCH_INLINE

DISPATCH_INLINE 可以将函数声明为静态内联函数。

__builtin_expect((x), (v))

#define DISPATCH_EXPECT(x, v) __builtin_expect((x), (v)) 宏将 __builtin_expect((x), (v)) 定义为 DISPATCH_EXPECT(x, v)

__builtin_expect 可以指定某个值的预期值。这样可以让编译器对相关的分支代码进行优化。

下面部分需要有部分的处理器相关的知识才能理解该部分。

在现代的处理器设计中,因为 cpu 和内存的速度不匹配。所以现在的 cpu 会读取多条指令并执行(多流水线)。比如,遇到分支时,它会同时计算分支的条件,并选择第一个分支执行。

比如下面的代码,__builtin_expect(x, 5) 代码告诉编译器,x 的值具有很大的概率是5。

如果没有任何优化,处理器会经常进入default分支执行,导致资源浪费。在这种情况下,编译后的代码会调整分支的顺序以达到提高资源效率的最优化。

ns_returns_retained

__ns_returns_retained__ 告诉编译器,该函数的返回值需要进行retain 操作。

因为,在 iOS 系统中,dispatch_block_t 是被当做对象管理的,而对象的生命周期是通过引用计数管理的。

所以,当一个对象初始化后,需要将其引用计数设置为1,防止其销毁。

但是在其它平台。它可以被当做结构体处理,所以,不需要引用计数设置为1。为了兼容这两种模式,通过 DISPATCH_RETURNS_RETAINED_BLOCK 可以检测平台并合理设置引用计数。

DISPATCH_ENUM

__has_feature__has_extension 是 clang 的语言特性。为了增强安全性,dispatch 希望能够通过 typedef enum { ... } tagname; 语法进行声明并定义枚举。但是为了兼容一些不支持该语法的平台版本,dispatch 通过宏实现对其的兼容处理。通过 clang 进行编译时,

这里以 block.h 文件中的 DISPATCH_ENUM(dispatch_block_flags, unsigned long,...) 为例进行说明。当通过 clang 预处理时,其会被转换为 typedef enum : unsigned long { __VA_ARGS__ } dispatch_block_flags_t;当通过其它平台进行处理时,会被转换为标准写法 enum { __VA_ARGS__ }; typedef unsigned long dispatch_block_flags_t

dispatch_function_t

该行代码是 base.h 文件中唯一的函数指针。其定义了一个名为 dispatch_function_t 的函数指针,它接收一个空指针,并且该函数返回为空(即无返回值)。

1 收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部