__attribute__ 的详解

attribute 的详解

目录

说明

__attribute__ 既熟悉又陌生的关键字,我们很多常用的函数都有用到这个关键字(例如:NSLog(),[NSString stringWithFormat:]),但是我们平时开发过程中又很少直接使用到。

__attribute__ 是GNU C 的一种体制,本质是一个编译器的指令,在声明的时候可以提供一些属性,在编译阶段起作用,来做多样化的错误检查和高级优化。可以分别设置函数的属性,变量属性和类型属性。语法为:attribute(xxx)。

用法与解说

一、 format[1]

  • 作用

    • format可以给声明的函数加上类似printf or scanf的特征,可以使编译器检查声明函数和函数实际调用参数之间的格式化字符串是否匹配
  • 语法__attribute__((format(archetype,string-index,first-to-check)))

    • archetype: 格式的类型
    • string-index: 指定传入函数的第几个参数是格式化内容
    • first-to-check: 指定第一个可变参数的位置
  • 例子

    • NSLog()
      • archetype = NSString 表示使用的是NSString风格
      • string-index = 1 表示第一个参数是格式化字符串
      • first-to-check = 2 第二个参数是第一个可变参数
    FOUNDATION_EXPORT void NSLog(NSString *format, ...) __attribute__((format(__NSString__, 1, 2)))
    

noreturn[2]

  • 作用

    • 通知编译器函数从不反回值,当遇到类似函数还未运行到return语句就需要退出来的情况,该属性可以避免出现错误信息。(简单来说又来这个定义 就算函数声明是有返回值的,但是加了这个noreturn后就算没有return都不会报错)
  • 例子

    • exit(int )
    #define __dead2         __attribute__((__noreturn__))
    void     exit(int) __dead2;
    

availbility[3]

  • 作用
    • 告诉编译器该函数的生命周期(用于什么平台,什么时候开始,什么时候废弃,什么时候完全不能使用,那些平台不可用,描述信息)
  • 语法 __attribute__((availability(platform,introduced=vesion,deprecated=vesion,obsoleted=version,message=""))) ro ((availability(platform,unavailable)))
    • platform:是指定那个平台,目前只支持iosmacos
    • introduced :是该方法最初出现时的版本号
    • deprecated: 声明移除的版本号,当超过或者等于该版本号时会出现⚠️
    • obsoleted: 不可用的版本号,当超过或者等于该版本号时会出现错误
    • message: 添加出现 警告 或者 错误是 的提示语,一般用于说明新的替换函数
  • 例子
    • - (NSString *)filePathForName:(NSString *)name
      • archetype 替换为 ios 说明这个限制只针对 ios平台
      • introduced = 10.1 说明这个函数是从ios 10.1开始存在的
      • deprecated = 11.1 说明这个函数在ios 11.1开始废弃
      • obsoleted = 13.0 说明这个函数在ios 13.0之后就不能调用了
      • message = "请使用 - (NSString *)newfilePathForName:(NSString *)name group:(NSString * 提示用户该函数的替换方案
    • - (NSString *)newfilePathForName:(NSString *)name
      • archetype 替换为 ios 说明这个限制只针对 ios平台
      • unavailable 为不可使用, 关联 archetype 属性,则表示该方法不能被 ios平台调用

- (NSString *)filePathForName:(NSString *)name __attribute__((availability(ios,introduced=10.1,deprecated=11.1,obsoleted=13.0,message="请使用 - (NSString *)newfilePathForName:(NSString *)name group:(NSString *)group")));

- (NSString *)newfilePathForName:(NSString *)name __attribute__((availability(ios,unavailable)));

unavailable[4]

  • 作用
    • 告诉编译器该函数不可用
  • 语法 __attribute__((unavailable)) or __attribute__((unavailable("说明")))
    • "说明"为说明内容
  • 例子
- (NSString *)newfilePathForName:(NSString *)name __attribute__((unavailable("该方法不可用")));

noinline&always_inline[5]

  • 作用
    • 声明函数是否新形成内联函数
    • ==但是能否形成内联函数,需要看编译器对该函数定义的具体处理==
  • 语法
    • __attribute__((always_inline)) 内联函数
    • __attribute__((noinline)) 不内联函数
  • 例子
    • void test_inlineFunc(int i) __attribute__((always_inline))
    • void test_noinlineFunc(int i) __attribute__((noinline))

pure&const[6]

  • 作用
    • pure:属性表明这个函数除了返回值以外没有任何副作用,也就是说它们的返回值只依赖于传入的参数和/或全局变量。这种函数可以通过常见的子表达式消除和循环优化技术进行优化,就像算术操作符一样。
    • const: 属性表明这个函数除了参数之外不会对值进行检查,除了返回值之外也没有其他副作用。注意,一个有指针类型的参数同时检查指针指向的数据的函数,一定不要声明为 const。同样的,一个调用在内部非 const 函数的函数通常也不能是 constconst 函数返回 void 类型是没有意义的。
    • 网上比较好的解说 来源于作者:saitjr
      • 用于函数的返回值仅与入参有关,并且函数状态单一。pure 除了与入参有关外,还与全局变量有关。这两个函数均属于函数式编程,且 const 要比 pure 更为严格一些。对于这两个属性,twitter blog 给出的建议是:
        • 虽然对于 runtime 来说,加不加 const 和 pure 关系不大,但是这对提高接口可读性帮助非常大。
        • 建议给不需要传入任何参数的方法加上该属性。正因为不需要入参,所以无论何时返回值都是相同的,那么完全可以对返回值进行缓存,之后调用时,直接返回缓存的结果即可。比如,单例的初始方法(#1)。
        • 如果 Objective-C 某方法用 pure 或 const 修饰了,并且调用非常频繁,那么应该考虑将其设计为 C 的接口,可优化函数开销(#2)。
        • 即便 pure 与 const 这么好用,但是一旦用错,会造成 bug 不易发现。
  • 语法
    • void test(int i) __attribute__((pure))
    • void test(int i) __attribute__((const))
  • 例子
    • ==因为本人也还没理解,所以先预留解释==

nothrow[7]

  • 作用
    • 告诉编译器该函数不能抛出错误
  • 语法
    • __attribute__((nothrow))
  • 例子 - void test(int i) __attribute__((nothrow))

sentinel[8]

  • 作用
    • 告诉编译器函数需要nil作为边界
  • 语法
    • __attribute__((sentinel(index,num)))
      • index: 表示第几个参数需要 nil or NULL
      • num: 表示需要几个nil or NULL
  • 例子
    • NSArray 的类方法 :+ (instancetype)arrayWithObjects:(ObjectType)firstObj, ...

      • index = 0 代表第一个参数需要nil or NULL作为边界
      • num = 1 代表需要1个nil or NULL
          
          #define NS_REQUIRES_NIL_TERMINATION __attribute__((sentinel(0,1)))
          + (instancetype)arrayWithObjects:(ObjectType)firstObj, ... NS_REQUIRES_NIL_TERMINATION;
      
      

format_arg[9]

  • 作用

    • 告诉编译器 函数的返回值是字符串类型,并且第几个参数是格式化字符串。(简单的说就是,定义这个函数的返回值是字符串,第几个参数也是字符串)
  • 语法

    • __attribute__((format_arg(string-index)))
      • string-index 为参数的下标 从1开始,不可以超过参数的数量
  • 例子

    • NSBundle- (NSString *)localizedStringForKey:(NSString *)key value:(nullable NSString *)value table:(nullable NSString *)tableName
      • string-index = 1 表示第一参数必须是字符串
    #define NS_FORMAT_ARGUMENT(string-index) __attribute__ ((format_arg(string-index)))
    
    - (NSString *)localizedStringForKey:(NSString *)key value:(nullable NSString *)value table:(nullable NSString *)tableName NS_FORMAT_ARGUMENT(1);
    

no_instrument_function[10]

  • 作用
    • If -finstrument-functions is given, profiling function calls will be generated at entry and exit of most user-compiled functions. Functions with this attribute will not be so instrumented.
  • 语法
    • __attribute__((no_instrument_function))
  • 例子

constructor&destructor[11]

  • 作用

    • constructor 定义该属性的函数会在 + Load 函数之后, main函数之前被调用
    • destructor 定义该属性的函数会在 Exit 之前调用
  • 格式

    • __attribute__((constructor)) 放在函数前面
    • __attribute__((destructor)) 放在函数后面
  • 例子

    
    __attribute__((constructor)) void beforeMain(void){
        NSLog(@"Main之前调用");
    }
    
    __attribute__((destructor)) void beforeExit(void){
        NSLog(@"exit之前调用");
    }
    

used[12]

  • 作用
    • used的作用是告诉编译器,我声明的这个符号是需要保留的。被used修饰以后,意味着即使函数没有被引用,在Release下也不会被优化。如果不加这个修饰,那么Release环境链接器会去掉没有被引用的段。具体的描述可以看gun的官方文档
  • 格式
    • __attribute__((used))
  • 例子
    • void test(int i) __attribute__((used))
    • section 配合好使用放在为被引用的 函数 or 变量被空间优化

section[13]

  • 作用
    • 通常情况下,编译器会将对象放置于DATA段的data或者bss节中。但是,有时我们需要将数据放置于特殊的节中,此时section可以达到目的。例如,BeeHive 中就把 module 注册数据存在 __DATA 数据段里面的 “BeehiveMods” section中。
    • section通常用于修饰全局变量。
  • 格式
    • __attribute__((section(name))) 默认使用 DATA段
      • name 为节点的命名 需要注意命名不能超多 ==16 bytes==
    • __attribute__((section("segname,sectname")))
      • segname 段的名称 ==长度限制 16 bytes==
      • sectname 节点名称 ==长度限制 16 bytes==
  • 例子
    • 定义 段为 _DATA(默认段) ,节点为 _newSection
    void test_func_attr_section (void) __attribute__ ((used,section ("_newSection")));
    void test_func_attr_section (void)
    {
        static int aStatic =0;
        aStatic++;
    }
    
    • 定义 段为 _TEST ,节点为 _newSection
    void test_func_attr_section (void) __attribute__ ((used,section ("_TEST,_newSection")));
    void test_func_attr_section (void)
    {
        static int aStatic =0;
        aStatic++;
    }
    
  • 参考

unused [14]

  • 作用
    • 告诉编译器,这个函数/属性可能不被调用,GCC也不会对函数产生警告,使用__unused 也可以达到同样的效果,用于声明函数中没有被使用到的参数
  • 格式
    • __attribute__((unused))
  • 例子
    - (void) test __attribute__((unused));
    //or
    int test __attribute__((unused));
    // or
    -(void) test:(NSString *)__unused name;
    
    

deprecated[15]

  • 作用
    • 告诉编译器该函数/变量已经过期,并警告
  • 格式
    • __attribute__((deprecated))
    • __attribute__((deprecated(msg)))
      • msg : 警告提示的内容
  • 例子
        -(void) oldFunc __attribute__((deprecated));
        //or
        -(void) oldFunc2 __attribute__((deprecated("请使用: - (void) newFunc2");
    

weak[16]

  • 作用
    • 将变量或者函数定义为弱引用符
      • 弱引用符的作用和说明:
        • 若两个或两个以上全局符号(函数或变量名)名字一样,而其中之一声明为weak symbol(弱符号),则这些全局符号不会引发重定义错误。链接器会忽略弱符号,去使用普通的全局符号来解析所有对这些符号的引用,但当普通的全局符号不可用时,链接器会使用弱符号。当有函数或变量名可能被用户覆盖时,该函数或变量名可以声明为一个弱符号。弱符号也称为weak alias(弱别名)。
  • 格式
    • __attribute__((weak))
  • 例子
    • 情况是这样的,碰到一个棘手的问题:我们不确定外部模块是否提供一个函数func,但是我们不得不用这个函数,即自己模块的代码必须用到func函数:
      extern int func(void);
      ...................
      int a = func();
      if( a > .....)
      {
          ..........
      }
      ............
      
    • 我们不知道func函数是否被定义了;
      • 这会导致2个结果:
        • 1:外部存在这个函数func,并且EXPORT_SYMBOL(func),那么在我自己的模块使用这个函数func,正确。
        • 2:外部其实不存在这个函数,那么我们使用func,程序直接崩溃。
    • 所以这个时候,attribute((weak)) 派上了用场。
    • 在自己的模块中定义:
          int  __attribute__((weak))  func(......)
          {
              return 0;
          }
      
    • 将本模块的func转成弱符号类型,如果遇到强符号类型(即外部模块定义了func),那么我们在本模块执行的func将会是外部模块定义的func。
    • 如果外部模块没有定义,那么,将会调用这个弱符号,也就是在本地定义的func,直接返回了一个1(返回值视具体情况而定)
    • 相当于增加了一个默认函数。
    • 原理:连接器发现同时存在弱符号和强符号,有限选择强符号,如果发现不存在强符号,只存在弱符号,则选择弱符号。如果都不存在:静态链接,恭喜,编译时报错,动态链接:对不起,系统无法启动。
    • weak属性只会在静态库(.o .a )中生效,动态库(.so)中不会生效。
  • 产考

malloc [17]

  • 作用
    • 使用该属性标记的函数返回的块必须不包含任何指向其他对象的指针。这样做的目的是帮助编译器估计哪些指针可能会指向同一个对象:这个属性告诉GCC,它不需要担心你的函数返回的对象可能包含指向它正在跟踪的其他对象的指针。
  • 格式
    • __attribute__((malloc))
  • 例子
  • 产考

alias[18]

  • 作用
    • 标记该属性的函数,作为指定函数的别名
  • 格式
    • __attribute__((alias(source_name))
      • source_name 原函数或者属性名称,(需要主要别名的参数,返回值定义,必须和原来的函数相匹配)
  • 例子
    • 函数别名定义例子
    void oldFunc(void)
    {
        printf("%s\n", __FUNCTION__);
    }
    void newFunc(void) __attribute__((alias("oldFunc")));
     
    int main(int argc, char *argv[])
    {
        newFunc(); // calls foo
    }
    /** 程序输出结果
     oldFunc
    */
    
    • 变量别名定义
    #include <stdio.h>
    int oldVar = 1;
    extern int newVar __attribute__((alias("oldVar"))); 
    int main(int argc, char *argv[])
    {
        printf("oldVar = %d\n", oldVar); // prints 1
    }
    /** 程序输出结果
    newname = 1
    */
    
  • 产考

warn_unused_result[19]

  • 作用
    • 告诉编译器,该属性标记的函数的返回值必须被使用,否则会发出警告
  • 格式
    • __attribute__((warn_unused_result))
  • 例子
        - (NSString *) getResuntFunc  __attribute__((warn_unused_result))
    

nonnull[20]

  • 作用
    • 告诉编译器,那些参数不能为NULL ro nil 所以参数类型必须是指针类型
  • 格式
    • __attribute__((nonnull(index..)))
    • index.. 为参数下标 从1 开始
  • 例子
    - (void) saveWithKey:(NSString *)key value:(id) value defaultValue:(id)defaultValue __attribute__((nonnull(1,2)));
    
    • nonnull(1,2) 标识第一个和第二个参数不能为空

aligned[21]

  • 作用
    • 让所作用的结构成员对齐在指定长度自然边界上。如果结构中有成员的长度大于指定长度,则按照最大成员的长度来对齐
  • 格式
    • __attribute__((aligned(length)))
      • length 为指定的长度
  • 例子
    • 不加修饰的情况
    typedef struct
    {
        char  member1;
        int   member2;
        short member3;
    }Family;
    
    //输出字节:
    NSLog(@"Family size is %zd",sizeof(Family));
    
    
    typedef struct
    {
        char  member1;
        int   member2;
        short member3;
    }__attribute__ ((aligned (1))) Family2;
    
    //输出字节:
    NSLog(@"Family2 size is %zd",sizeof(Family2));
    
    typedef struct
    {
        char  member1;
        int   member2;
        short member3;
    }__attribute__ ((aligned (8))) Family3;
    
    //输出字节:
    NSLog(@"Family3 size is %zd",sizeof(Family3));
    
    
    
    • 输出结果为:
    2020-08-15 14:41:56.994879+0800 TestOCApp[58956:8462998] Family size is 12
    
    2020-08-15 14:41:56.994879+0800 TestOCApp[58956:8462998] Family2 size is 12
    
    2020-08-15 14:41:56.994879+0800 TestOCApp[58956:8462998] Family3 size is 16
    
    • 第1个为什么是 12?
      • 因为 char 为1 字节 ,int 为 4 字节,short为两个字节 ,所以sizeof(Family) = member3偏移值(member2偏移值(member1偏移值 + sizeof(member1)) + sizeof(member2)) + sizeof(member3);
        ==因为偏移值必须是成员大小的整倍数== 所以: member1偏移值 = 0 ,member2偏移值 = 4(因为int是4个字节,1不是4的整倍数,所以填充3位变成4) ,member3偏移值 = 9
      • 所以最终就是 sizeof(Family) = 9(4(0+1)+4)+1 => 10 ,==但是结构体大小必须是所有成员大小的整数倍,也即所有成员大小的公倍数。== 所以 sizeof(Family) 必须是 4的倍数, 所以最终大小是 12.
    • 第2个为什么是 12?
      • 因为我们设置__attribute__ ((aligned (1))) 但是我们成员最大的是4为,所以最终结果还是按照 4来算,所以最终结果还是 12
    • 第3个为什么是 16?
        • 因为我们设置__attribute__ ((aligned (8))) 8 > 4 ,所以最终结果是按照 8来算,所以最终结果还是 81 < 10 < 82 说以最终的结果是 16。
  • 参考

packed[22]

  • 作用
  • 格式
    • __attribute__ ((packed))
  • 例子
    typedef struct
    {
        char  member1; //
        int   member2; //
        char member3;//
    }__attribute__((packed)) Family;
    //输出字节:
    NSLog(@"Family size is %zd",sizeof(Family));
    
    • 输出结果为:
    2020-08-15 14:41:56.994879+0800 TestOCApp[58956:8462998] Family size is 6
    

transparent_union[23]

  • 等待填充

may_alias[24]

  • 待定

overloadable[25]

  • 作用
    • 用于c语言函数,可以定义若干个函数名相同,但参数不同的方法,调用时编译器会自动根据参数选择函数原型
  • 格式
    • __attribute__((overloadable)) 放置在函数前面
  • 例子
    __attribute__((overloadable)) void print(NSString *string){
        NSLog(@"%@",string);
    }
    __attribute__((overloadable)) void print(int num){
        NSLog(@"%d",num);
    }
    //调用
    print(10);
    print(@"哈哈");
    

objc_root_class[26]

  • 作用
    • 放置在类定义前,告诉编译器,当前类是基类
  • 格式
    • __attribute__((objc_root_class))放置在类定义前
  • 例子
    • 来自系统的NSObject定义
    #define OBJC_ROOT_CLASS __attribute__((objc_root_class))
    
    OBJC_ROOT_CLASS
    @interface NSObject <NSObject> {
        ....
    }
    

NSObject[27]

  • 作用
    • 非OC对象被修饰后,会当作OC对象来进行内存处理
  • 格式
    • __attribute__((NSObject))
  • 例子
    @property (nonatomic,strong) __attribute__((NSObject)) CFDictionaryRef myDictionary;
    
    • myDictionary被修饰后会当作OC对象来进行内存处理

objc_designated_initializer[28]

  • 作用
    • 用来修饰类的designated initializer初始化方法,如果修饰的方法里没有调用super类的 designated initializer,编译器会发出警告。
  • 格式
    • __attribute__((objc_designated_initializer))
  • 例子
    • 来自于系统UIViewController 的代码片段
    #define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
    
    - (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
    
    • 当重写 UIViewController 的- (nullable instancetype)initWithCoder:(NSCoder *)coder 方法是,如果没有调用[super initWithCoder:coder]的话 会触发警告

visibility[29]

  • 作用
    • 用于C语言和C++,主要作用是定义函数/属性的访问范围
  • 格式
    • __attribut__((visbility("visbility_type")))
      • visbility_type

更多参考[30]

最后的唠叨[31]

  • 这帖子是我学习过程中结合自己的理解写出的,有理解错误的地方希望指出谢谢。

  1. format

  2. noreturn

  3. availbility

  4. unavailable

  5. noinline&always_inline

  6. pure&const

  7. nothrow

  8. sentinel

  9. format_arg

  10. no_instrument_function

  11. constructor&destructor

  12. used

  13. section

  14. unused

  15. deprecated

  16. weak

  17. malloc

  18. alias

  19. warn_unused_result

  20. nonnull

  21. aligned

  22. packed

  23. transparent_union

  24. may_alias

  25. overloadable

  26. objc_root_class

  27. NSObject

  28. objc_designated_initializer

  29. visibility

  30. 参考

  31. 唠叨

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335