简介
本篇文章翻译自http://nshipster.com/__attribute__/
翻译的不对的地方还请多多包涵指正,谢谢~
正文
这个书刊的一个永恒主题是与编译器良好关系的重要性。像任何工艺一样,一个手工艺人的效率可能取决于他们对待工具的态度。良好的爱护使用他们,他们将回报于你。
__attribute__
是一个编译指令,可以指定在声明时的错误检查及高级优化的特性。
这个关键词的语法是__attribute__
后面跟了两个小括号(这两个小括号让其很容易区别于宏(make it easy to "macro out"),特别是多个属性的时候)。在小括号里面是属性是用逗号分隔的。__attribute__
指令放置在函数,变量或类型的声明后。
// 返回一个数的平方
int square(int n) __attribute__((const));
// 声明一个接口的平台可用性
void f(void)
__attribute__((availability(macosx,introduced=10.4,deprecated=10.6)));
// 向 stderr 和 exit 中发送诸如打印等的消息
extern void die(const char *format, ...)
__attribute__((noreturn, format(printf, 1, 2)));
是不是让你想起了标准C中的#pragma
?我也是~
事实上,当__attribute__
第一次被引入到GCC中时,就遇到一些人的阻力,他们认为使用#pragma
可以达到同样的目的。
但是,有两个特别重要的原因解释为何要使用__attribute__
:
- 在宏定义(在C99
_Pragma
操作符之前)里使用#pragma
指令是不可能的。(因为#符号在宏定义当中有特殊用于,用于获取宏中的参数名)- 我们并不知道
#pragma
在其他编译器里意味着什么。
引用 GCC Documentation for Function Attributes 里的一句话:
这两种情况几乎发生在任何提议用
#pragma
的应用上。使用#pragma
做任何事情本质上就是个错误。
事实上,如果你去看看现代Objective-C Apple框架的头文件或者一些精心设计的开源项目会发现,__attribute__
被用于很多目的。(相反的,#pragma
现在主要用于#pragma mark
的声明)
所以我们不再啰嗦,让我们看看这些最重要的编译属性吧:
GCC
format
这个属性指定一个函数比如printf,scanf,strftime,strfmon 作为参数,并且通过一个格式化字符串来做类型检查。
extern int
my_printf (void *my_object, const char *my_format, ...)
__attribute__((format(printf, 2, 3)));
Objective-C 程序员还可以通过使用__NSString__
格式达到同样的效果,就像在NSString +stringWithFormat:
和NSLog()
里使用字符串格式一样。
nonnull
这个属性指定函数的的某些参数不能是空指针。
extern void *
my_memcpy (void *dest, const void *src, size_t len)
__attribute__((nonnull (1, 2)));
使用nonnull
能够将可能为空的值暴露出来,这样能够找出任何潜伏在调用代码中的空指针bug。记住:编译错误远早于运行时错误。
noreturn
几个标注库函数,例如
abort
exit
,没有返回值。GCC能够自动识别这种情况。noreturn
属性指定像这样的任何不需要返回值的函数。
例如,AFNetworking库为它的网络请求显示入口函数使用了该属性。这个在生成一个专用的线程时使用,保证分离的线程能在应用的整个生命周期继续执行。
pure / const
pure
属性指定一个除了返回值没有其他影响的函数,因此函数的返回值只取决于他的入参和全局参数。这样的函数可以作为公共子表达式删除或者循环优化,就像算术符一样。
const
属性指定函数不能审查除了她参数的的值,而且不能影响除了返回值的其他值。注意此类函数不能有指针作为参数且指向的数据不能声明为const
。同样地,调用非const
函数的函数肯定也不是const
类型。一个返回void
的const
函数是没有意义的(因为此类型函数不能任何其他值产生影响,且参数只能值数值型,如果没有返回值,那执行就没有任何意义)。
int square(int n) __attribute__((const));
pure
和const
都是会触发具有重大性能优化的函数编程范式的属性。const
可以认为是比pure
更严格的形式,因为她不依赖全局变量,也不能将指针作为参数,或者更改指针的值。
例如,因为const
函数返回值只跟入参有关,这种函数的返回值可以被缓存起来。之后调用该函数可以直接使用返回值。(比如:我们都知道一个数的平方是固定的,所以只需要执行一次算法就行)
unused
这个属性,附加到函数上,意味着这个函数很可能是未被使用的。GCC将不会对这个函数产生警告。
使用__unused
属性可以达到同样效果。可以将其声明在在函数实现中没有使用过的参数上。这样能够让编译器做相应的优化。你很可能使用该属性在代理的的实现函数上,因为协议经常提供很多不必要的函数,为了满足许多潜在的情况。
LLVM
就像GCC的许多特性一样,Clang支持__attribute__
,而且添加了一些自己的小扩展。为了检查一个特殊属性的可用性,你可以使用__has_attribute
指令。
availability
Clang引入了可用性属性,这个属性可以在声明中描述跟系统版本有关的生命周期。例如:
void f(void) __attribute__((availability(macosx,introduced=10.4,deprecated=10.6,obsoleted=10.7)));
availability
属性声明f函数是在系统 OS X Tiger 引入的,在系统OS X Snow Leopard不建议使用,在系统OS X Lion被废弃不用的。Clang编译器用这个信息决定在什么时候使用函数f。例如:Clang需要在OS X Leopard系统下编译,那么调用f函数是OK的。如果Clang在OS X Leopard下编译代码,也能编译成功但是会提示该函数不建议使用。最后,Clang在OS X Lion系统下编译的话,调用就会失败因为函数f已经不再可用了。
availability
属性写法是逗号分割的,以平台名称作为第一个参数,之后是一些无序的指定重要的声明信息的句子。
-
introduced
: 声明被引入的第一个版本信息。 -
deprecated
: 第一次不建议使用的版本,意味着使用者应该移除这个方法的使用。 -
obsoleted
: 第一次被废弃的版本,意味着已经被移除,不能够使用了 -
unavailable
: 意味着这个平台不支持使用。 -
message
: 当Clang发出一些关于废弃或不建议使用的警告时的文本。用于引导使用者不要使用改接口了。
各种类型的
availability
属性放在声明里,这些属性可能在不同的平台是一致的。只有当属性声明的平台和目标的平台一致时,属性才会生效。
支持的平台有:
- ios: 苹果的iOS操作系统。最小部署目标平台版本是通过
-mios-version-min=*version*
或-miphoneos-version-min=*version*
命令行指定的。 - macosx: 苹果的OS X操作系统。最小部署目标平台版本是通过
-mmacosx-version-min=*version*
命令行指定的。
overloadable
Clang在C中提供对C++标准函数重载的支持。函数重载在C中是通过
overloadable
属性引入的。例如:你可以重载tgsin函数,写出sin函数在入参不同时的不同版本。
#include <math.h>
float __attribute__((overloadable)) tgsin(float x) { return sinf(x); }
double __attribute__((overloadable)) tgsin(double x) { return sin(x); }
long double __attribute__((overloadable)) tgsin(long double x) { return sinl(x); }
注意到重载只对函数有效。你可以通过改变返回值类型(例如:id *,void *
)来重载函数。
当需要对编译优化时,代码是主要的(Context is king when it comes to compiler optimizations)。通过对代码的限制,你可以有机会让你生成的代码越来越高效。让编译器为你工作,你将会获得奖励。
而且__attribute__
不仅仅只是服务于编译器。其他人看你代码时也会看到这些额外的编译属性。所以在编译属性上多做些优化吧,将会有益于你的同伴,接替你代码的人,或者是两年之后忘了关于这个代码一切的你自己。
因为最后,你付出的等于你获得的。