架构技术评论

聊聊__attribute__关键字

大家好,我是脑洞 TF ,目前就职于阿里巴巴。从事 iOS 开发多年。这是我刚开的公众号。未来我会持续不定期更新,欢迎大家关注我。一起学习,一起探讨,一起成长。

最近一段时间比较忙,在研究内存相关的东西,后续也会给大家分享下。距离上次更新已经一个月了,不知道写什么,在我上一篇文章中的一个细节,让我想起了 一个 知识点。下面就展开说说。有理解错误的地方欢迎私信指正 🙏 。
下面进入正题~~~

一、引言 在上一篇文章 常量字符串,你可能不知道的那些事儿~ 中,常量字符串变量是这样的定义的:
static  __NSConstantStringImpl  __NSConstantStringImpl__var_folders_l7_r5syspkx43724t__mz8h8rr00000gp_T_ViewController_754f19_mi_0  __attribute__ ((section ("__DATA, __cfstring"))) =  { __CFConstantStringClassReference,   0x000007c8,   "abcd",   4};
这里有一个 __attribute__ (( section ( "__DATA , __cfstring ”))) 。 其中关键词“ __attribute__ ”作为一个 iOS 开发者的我们,在系统源码中经常看到。那这个关键词是干什么的呢?

二、 __attribute__ 简介
__attribute__ 是一个用于在声明时指定一些特性的编译器指令,可以做一些错误检查和高级优化工作。
常见的几个__attribute__的参数:alias ("target")aligned (alignment)alloc_size (position-1, position-2)deprecated (msg)unavailable (msg)packedtls_model ("tls_model")visibility ("visibility_type")...这里不再展开(毕竟有些我也不清楚😂)

有兴趣的可以看: https://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html#Variable-Attributes
三、简单介绍几个 __attribute__ 参数 1 、 __attribute__ ( noreturn ):
这个属性告诉编译器函数不会返回,这可以用来抑制关于未达到代码路径的错 误。
(比如有些方法是不会return的,那这个方法下面的逻辑也不会触达。 如果在noreturn标记过的方法下面添加逻辑,编译器会有提示。 )

2 、 __attribute__ (( visibility ( "hidden ”))): 这个属性 可以隐藏一些外部不需要的符号。这个对包大小都有一定程度的优化。
举个例子:
void cfunc(void) __attribute__((visibility("hidden"))) ;void cfunc2(){}

通过下图可以发现 cfunc 的符号确实没有了。 所有的 c 符号都会存在 mach-o 的 string table 中,如果符号字符串少了,理论上包大小自然也会有所减少。



3 、 __attribute__ (( naked )) : 此属性告诉编译器该函数是嵌入式程序集函数。可以使用 __asm 语句在汇编代码中编写函数体。
下面关于 naked 的使用举个例子:
void func2() {}void func() {    func2();}int main(int argc, char * argv[]) {    func();    return 0;}

在 func2 打断点。查看堆栈如下:

将 func 函数修改为:
void func() {    __asm__ volatile ("STP    x29, x30, [sp, #0x10]!;\n"                      "B _func2;\n");}

在 func2 打断点。 查看堆栈如下:


可以看到 func 的栈帧被移除了!

4 、 __attribute__ (( constructor )) 、 __attribute__ (( destructor )) 这两个属性还支持设置优先级: constructor ( priority ) destructor ( priority )
举个例子:
__attribute__((constructor)) static void beforeMain(){    printf("beforeMain\n");}__attribute__((destructor)) static void afterMain(){    printf("afterMain\n");}int main(int argc, char * argv[]) {    return 0;}

打印结果如下:



5 、 _attribute__ (( section ( "__DATA , __cfstring ”))) 开篇就有提到 _attribute__ (( section ( "__DATA , __cfstring ”))),在上一篇 常量字符串,你可能不知道的那些事儿~ 中,也有变量是用它来声明的。
下面就展开介绍下 section属性 :
举个例子 :
typedef struct SectionItem {    int s1;    int s2;}SectionItem_t;//用以下2种方式试试看。__attribute__((used, section("__MySeg,__MySec"))) static const SectionItem_t item1 = {123,456};static SectionItem_t item2 __attribute__ ((section ("__MySeg, __MySec"))) = {22,33};
查看 mach-o 文件:
发现确实创建了一个自定义的 segement 和 section ,但是数据貌似只添加了一条。

这是为什么呢?
注释掉第一行,仅保留第二行再试试看:
//用以下2种方式试试看。//__attribute__((used, section("__MySeg,__MySec"))) static const SectionItem_t item1 = {123,456};static SectionItem_t item2 __attribute__ ((section ("__MySeg, __MySec"))) = {22,33};

发现 mach-o 文件中并没有自定义的 section 。 那一定是编译器搞了什么事情。

还是声明上面的item1和item2两 个变量 ,再 通过执行这个命令 再来 验证一下:

clang -S -fobjc-arc -emit-llvm main.m -o main.ll

查看 .ll 文件

只有一条记录,看来果真是编译器优化了。

那没道理啊,毕竟最开始有提到过,系统的常量字符串变量就是用的 __attribute__ (( section ( "__DATA , __cfstring ”)))声明的啊~


那一定是和“ used ”这个属性有关了。
没错! used 就是告诉编译器,把变量标记为使用,而不去做优化。既然如此,那就使用一下 item2 这个变量吧。
添加代码:
int tmp = item2.s1;


重复上面的步骤,果真 .ll 文件中有 2 个记录了。


小总结下: 用 __attribute__ (( section (“ xx , xx ”)))在自定义的 section 中添加数据,然后再在运行时通过 getsectiondata ()这个方法,获取对应 section 的内容。这个小技巧还是可以用在很多场景的。


四、写在最后
之前对attribute就有所了解,但是没有相对系统的汇总过。 最近不知道写啥,把我知道的 __attribute__ 相关的这点东西梳理一下,如果有错误的话,可以私信我,一起探讨、学习、成长。




记得关注、转发、分享我, 下一期决定写一下最近在调研的内存相关的知识 !比心!~~