__attribute__ 机制使用

这是属性经常看见,但是也没仔细的看看他具体如何使用,最近浏览博客,发现这个属性其实可以做好多事情的。因此,这里就打算把这个属性详细的学习一下。

介绍

__attribute 其实是个编译器指令,告诉编译器声明的特性,或者让编译器进行更多的错误检查和高级优化。

attribute 可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)。

__attribute 是GCC的特性,LLVM借鉴过来,又对其进行了扩展。


语法

attribute 语法格式为: attribute ((attribute-list))


分类

函数属性

aligned
alloc_size
noreturn
returns_twice
noinline, noclone
always_inline
flatten
pure
const
nothrow
sentinel
format
format_arg
no_instrument_function
no_split_stack, section
constructor, destructor
used
unused
deprecated
weak
malloc
alias
ifunc
warn_unused_result
nonnull
gnu_inline
externally_visible
hot
cold
artificial
error
warning

变量属性

aligned
cleanup
common
nocommon
deprecated
mode
packed
section
shared
tls_model
unused
used
vector_size
selectany
weak
dllimport
dllexport

类型属性

aligned
packed
transparent_union unused
deprecated
visibility
and may_alia

我就是大概罗列下,其实有些属性可以修饰不止一种属性类型。不做过多介绍。

Clang 扩展的属性

availability
overloadable


属性用法介绍

知识点不难,主要是会使用,下面就对上面的属性一一举例说明。

创建个命令工程


image.png

alias

将工程中的main函数改成如下

void __f () { NSLog(@"print"); };
void f () __attribute__ ((weak, alias ("__f")));

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
//        f();
    }
    return 0;
}

我们发现报错了,这是因为darwin 不支持alias。 不是所有的target machine 支持这个属性

aligned

这个属性可以用来修饰函数也可以用来修饰变量。

这个属性是用来字节对齐的。测试基本代码

struct stu{
    char sex;
    int length; ;
    char name[2];
    char value[15];
}__attribute__ ((aligned (1)));
struct stu my_stu;

int main(int argc, const char * argv[]) {
    NSLog(@"%lu",sizeof(my_stu));
    NSLog(@"%p %p,%p,%p",&my_stu,&my_stu.length,&my_stu.name,&my_stu.value);
  
    return 0;
}

测试结果


结果一

内存样式


一行四字节
结果二
一行四字节
结果三
一行八字节

这里我们发现 aligned后面必须跟2的次幂,其他的编译器报错。当 我们传入1 和2 的时候,编译器模式都是以4字节对齐的
这里还需要注意,我们的结构体上下两个字段是char类型的才会将其合并处理。见图结果二和结果三

alloc_size

alloc_size 的用法

alloc_size 属性 需要和__builtin_object_size 函数一起使用。
alloc_size后面可以跟一到二个参数
alloc_size 后面跟的参数是指定使用函数的第几个参数。
要是函数的参数的个数只有一个,那么alloc_size的参数只能是1。通过__builtin_object_size 获取的值 就是传入的参数值。如图,我们给函数my_malloc 传入的值是100 ,那么我们通过__builtin_object_size 获取的值就是100。
要是函数的参数的个数多余两个,那么alloc_size 的最多可以指定两个参数。传入两个参数,__builtin_object_size的值是这两个参数的乘积。传入一个参数,__builtin_object_size 的值就是这个参数的值。如图,my_callocd函数指定的参数是alloc_size(2,3),通过__builtin_object_size获取的值就是my_callocd传入的第二和三个参数的乘积(3*5=15)。

这相当于给指针绑定了空间大小了。

always_inline

一般来说,除非指定优化,否则函数不内联。对于声明为内联的函数,会强制优化。所有加了attribute((always_inline))的函数再被调用时不会被编译成函数调用而是直接扩展到调用函数体内。
测试代码

 void inlineFunction(){
     int a =10;
     a+=10;
}
void testInline(){
inlineFunction();
}

int main() {
    testInline();
    return 0;
}

转换成汇编

_inlineFunction:                        ## @inlineFunction
Lfunc_begin0:
    .file   1 "/Users/yangfangkuo1/Downloads/\345\272\237\345\274\203/__Attribute__/__Attribute__" "main.m"
    .loc    1 11 0                  ## /Users/yangfangkuo1/Downloads/废弃/__Attribute__/__Attribute__/main.m:11:0
    .cfi_startproc
## BB#0:
    pushq   %rbp
Lcfi0:
    .cfi_def_cfa_offset 16
Lcfi1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Lcfi2:
    .cfi_def_cfa_register %rbp
Ltmp0:
    .loc    1 12 10 prologue_end    ## /Users/yangfangkuo1/Downloads/废弃/__Attribute__/__Attribute__/main.m:12:10
    movl    $10, -4(%rbp)
    .loc    1 13 7                  ## /Users/yangfangkuo1/Downloads/废弃/__Attribute__/__Attribute__/main.m:13:7
    movl    -4(%rbp), %eax
    addl    $10, %eax
    movl    %eax, -4(%rbp)
    .loc    1 14 1                  ## /Users/yangfangkuo1/Downloads/废弃/__Attribute__/__Attribute__/main.m:14:1
    popq    %rbp
    retq
Ltmp1:
Lfunc_end0:
    .cfi_endproc

    .globl  _testInline
    .p2align    4, 0x90
_testInline:                            ## @testInline
Lfunc_begin1:
    .loc    1 15 0                  ## /Users/yangfangkuo1/Downloads/废弃/__Attribute__/__Attribute__/main.m:15:0
    .cfi_startproc
## BB#0:
    pushq   %rbp
Lcfi3:
    .cfi_def_cfa_offset 16
Lcfi4:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Lcfi5:
    .cfi_def_cfa_register %rbp
Ltmp2:
    .loc    1 16 1 prologue_end     ## /Users/yangfangkuo1/Downloads/废弃/__Attribute__/__Attribute__/main.m:16:1
    callq   _inlineFunction
    .loc    1 17 1                  ## /Users/yangfangkuo1/Downloads/废弃/__Attribute__/__Attribute__/main.m:17:1
    popq    %rbp
    retq
Ltmp3:
Lfunc_end1:
    .cfi_endproc

有个 callq _inlineFunction ,这里说明是调用了inlineFunction函数

加上**attribute((always_inline)) **

__attribute__((always_inline)) void inlineFunction(){
     int a =10;
     a+=10;
}
void testInline(){
inlineFunction();
}

转换成汇编

_inlineFunction:                        ## @inlineFunction
Lfunc_begin0:
    .file   1 "/Users/yangfangkuo1/Downloads/\345\272\237\345\274\203/__Attribute__/__Attribute__" "main.m"
    .loc    1 11 0                  ## /Users/yangfangkuo1/Downloads/废弃/__Attribute__/__Attribute__/main.m:11:0
    .cfi_startproc
## BB#0:
    pushq   %rbp
Lcfi0:
    .cfi_def_cfa_offset 16
Lcfi1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Lcfi2:
    .cfi_def_cfa_register %rbp
Ltmp0:
    .loc    1 12 10 prologue_end    ## /Users/yangfangkuo1/Downloads/废弃/__Attribute__/__Attribute__/main.m:12:10
    movl    $10, -4(%rbp)
    .loc    1 13 7                  ## /Users/yangfangkuo1/Downloads/废弃/__Attribute__/__Attribute__/main.m:13:7
    movl    -4(%rbp), %eax
    addl    $10, %eax
    movl    %eax, -4(%rbp)
    .loc    1 14 1                  ## /Users/yangfangkuo1/Downloads/废弃/__Attribute__/__Attribute__/main.m:14:1
    popq    %rbp
    retq
Ltmp1:
Lfunc_end0:
    .cfi_endproc

    .globl  _testInline
    .p2align    4, 0x90
_testInline:                            ## @testInline
Lfunc_begin1:
    .loc    1 15 0                  ## /Users/yangfangkuo1/Downloads/废弃/__Attribute__/__Attribute__/main.m:15:0
    .cfi_startproc
## BB#0:
    pushq   %rbp
Lcfi3:
    .cfi_def_cfa_offset 16
Lcfi4:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Lcfi5:
    .cfi_def_cfa_register %rbp
Ltmp2:
    .loc    1 12 10 prologue_end    ## /Users/yangfangkuo1/Downloads/废弃/__Attribute__/__Attribute__/main.m:12:10
    movl    $10, -4(%rbp)
    .loc    1 13 7                  ## /Users/yangfangkuo1/Downloads/废弃/__Attribute__/__Attribute__/main.m:13:7
    movl    -4(%rbp), %eax
    addl    $10, %eax
    movl    %eax, -4(%rbp)
Ltmp3:
    .loc    1 17 1                  ## /Users/yangfangkuo1/Downloads/废弃/__Attribute__/__Attribute__/main.m:17:1
    popq    %rbp
    retq
Ltmp4:
Lfunc_end1:
    .cfi_endproc

我们发现callq _inlineFunction 没了。相应的位置变成了

 movl   $10, -4(%rbp)
    .loc    1 13 7                  ## /Users/yangfangkuo1/Downloads/废弃/__Attribute__/__Attribute__/main.m:13:7
    movl    -4(%rbp), %eax
    addl    $10, %eax
    movl    %eax, -4(%rbp)

这不就是inlineFunction 函数的实现。

其实就是减少调用函数。要是某些小函数频繁调用,那么我们应该声明将其声明成内敛函数

慎用内联

   内联能提高函数的执行效率,为什么不把所有的函数都定义成内联函数?如果所有的函数都是内联函数,还用得着“内联”这个关键字吗?

   内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收

获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。

以下情况不宜使用内联:

(1)如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。

(2)如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。

一个好的编译器将会根据函数的定义体,自动地取消不值得的内联

objc_subclassing_restricted

这个属性要是加载@interface 的类前面,那么这个类就不能增加子类了。

没添加属性objc_subclassing_restricted

这时候编译器不会出错的,


objc_subclassing_restricted

这时候编码阶段直接报错了。

objc_requires_super

这个属性是当子类覆盖父类的时候,子类需要调用下父类的方法,不调用会产生警告,不会报错

image.png

如果method加上attribute((objc_requires_super)) 属性。
image.png

会产生警告
在foundation 已经定义好了这个宏定义

#ifndef NS_REQUIRES_SUPER
#if __has_attribute(objc_requires_super)
#define NS_REQUIRES_SUPER __attribute__((objc_requires_super))
#else
#define NS_REQUIRES_SUPER
#endif
#endif

objc_boxable

这个属性是可以将struct 或者unions 转换成NSValue

struct __attribute__((objc_boxable)) _some_struct {
    int i;
};
union __attribute__((objc_boxable)) some_union {
    int i;
    float f;
};
typedef struct __attribute__((objc_boxable)) _some_struct some_struct;


int main() {
    some_struct ss;
    NSValue *boxed = @(ss);
}

声明了attribute((objc_boxable)) 可以直接用语法糖,不会报错

image.png

去掉
image.png

constructor / destructor

顾名思义,构造器和析构器,加上这两个属性的函数会在分别在可执行文件(或 shared library)load 和 unload 时被调用,可以理解为在 main() 函数调用前和 return 后执行:

__attribute__((constructor))
static void beforeMain(void) {
NSLog(@"beforeMain");
}
__attribute__((destructor))
static void afterMain(void) {
NSLog(@"afterMain");
}
int main(int argc, const char * argv[]) {
NSLog(@"main");
return 0;
}

执行截图


image.png

constructor 和 +load 都是在 main 函数执行前调用,但 +load 比 constructor 更加早一丢丢,因为 dyld(动态链接器,程序的最初起点)在加载 image(可以理解成 Mach-O 文件)时会先通知 objc runtime 去加载其中所有的类,每加载一个类时,它的 +load 随之调用,全部加载完成后,dyld 才会调用这个 image 中所有的 constructor 方法。

所以 constructor 是一个干坏事的绝佳时机:

  • 所有 Class 都已经加载完成
  • main 函数还未执行
  • 无需像 +load 还得挂载在一个 Class 中

若有多个 constructor 且想控制优先级的话,可以写成 attribute((constructor(101))),里面的数字越小优先级越高,1 ~ 100 为系统保留。

enable_if

这个属性只能用在 C 函数上,可以用来实现参数的静态检查:

static void printValidAge(int age)
__attribute__((enable_if(age > 0 && age < 120, "你丫火星人?"))) {
    printf("%d", age);
}

void foo(char c) {
    printValidAge(99);
    printValidAge(199);
}
int main(int argc, const char * argv[]) {
    return 0;
}
image.png

官网给的结构 供学习做下参考

__attribute__((always_inline))
static inline size_t strkkknlen(const char *s, size_t maxlen)
__attribute__((overloadable))
__attribute__((enable_if(__builtin_object_size(s, 0) != -1, "你丫火星人?")))
{
   return strnlen_chk(s, maxlen, __builtin_object_size(s, 0));
}

void f() __attribute__((enable_if(true, "")));  // #1
void f() __attribute__((enable_if(true, ""))) __attribute__((enable_if(true, "")));  // #2

void g(int i, int j) __attribute__((enable_if(i, "")));  // #1
void g(int i, int j) __attribute__((enable_if(j, ""))) __attribute__((enable_if(true)));  // #2

cleanup

声明到一个变量上,当这个变量作用域结束时,调用指定的一个函数

static void stringCleanUp(__strong NSString **string) {
    NSLog(@"%@", *string);
}

int main(int argc, const char * argv[]) {  
    __strong NSString *string __attribute__((cleanup(stringCleanUp))) = @"sunnyxx";
    return 0;
}
image.png

当string 释放了,调用了与string绑定的方法,将其传入进去了。
这里好多人都提到了Reactive Cocoa中神奇的@onExit方法 的方法

我们知道使用attribute((cleanup(XXX)),当被标记的对象在释放的时候会调用其绑定的方法,那么要是我们给block绑定方法会发生什么呢?羡慕就是Reactive Cocoa 的黑魔法

static void blockCleanUp(__strong void(^*block)(void)) {
    (*block)();
}

#define onExit \
__strong void(^block)(void) __attribute__((cleanup(blockCleanUp), unused)) = ^

用法


image.png

overloadable

用于 C 函数,可以定义若干个函数名相同,但参数不同的方法,调用时编译器会自动根据参数选择函数原型:

__attribute__((overloadable)) void logAnything(id obj) {
    NSLog(@"%@", obj);
}

__attribute__((overloadable)) void logAnything(int number) {
    NSLog(@"%@", @(number));
}

__attribute__((overloadable)) void logAnything(CGRect rect) {
    NSLog(@"%@",@(rect));
}

int main(int argc, const char * argv[]) {
    logAnything(@[@"1", @"2"]);
    logAnything(233);
    logAnything(CGRectMake(1, 2, 3, 4));
    return 0;
}

结果


image.png

objc_runtime_name

用于 @interface 或 @protocol,将类或协议的名字在编译时指定成另一个:

__attribute__((objc_runtime_name("MyLocalName")))
@interface Message:NSObject
@end

@implementation Message
@end
int main(int argc, const char * argv[]) {
  
    
    NSLog(@"%@",Message.class);
    return 0;
}
image.png

将Message 替换成了MyLocalName. 所以想通过Message进行反射的操作全部失效了

_Noreturn

_Noreturn 关键词出现于函数声明中,指定函数不会由于执行到 return 语句或抵达函数体结尾而返回,(可通过执行 longjmp 返回)

调用函数没有返回值,所以程序就停在函数里面了

_Noreturn void stop_now(int i) // 或 _Noreturn void stop_now(int i)
{
    if (i > 0) exit(i);
}

int main(void)
{
    puts("Preparing to stop...");
    stop_now(2);
    puts("This code is never executed.");
}

image.png

就算是exit都没用
这个属性要要配合使用

jmp_buf jump_buffer;

_Noreturn void a(int count)
{
    printf("a(%d) called\n", count);
    longjmp(jump_buffer, count+1); // 将在 setjmp 外返回 count+1
}

int main(void)
{
    volatile int count = 0; // 必须为 setjmp 声明 volatile 对象
    if (setjmp(jump_buffer) != 9)
        a(count++);
}
image.png

availability

该availability属性可以放在声明上,以描述相对于操作系统版本的声明的生命周期。考虑假设函数的函数声明f

void f(void) __attribute__((availability(macos,introduced=10.4,deprecated=10.6,obsoleted=10.7)));

可用性属性表示f在macOS 10.4中引入,在macOS 10.6中已弃用,在macOS 10.7中已废弃。

availability属性是以逗号分隔的列表,以平台名称开头,包括改函数的生命周期和其他的描述信息。

字段有一下

introduced=version
首次定义版本

deprecated=version
弃用版本(不推荐使用)

obsoleted=version
废弃版本(调用就崩溃了)

unavailable
在平台无效

message=string-literal
在废弃或者弃用版本的提示

replacement=string-literal
该api的替代api

该属性支持的平台是

ios (最小版本用-mios-version-min 或者-miphoneos-version-min 标记)
Apple’s iOS operating system. The minimum deployment target is specified by the -mios-version-min=version or -miphoneos-version-min=version command-line arguments.

macos
Apple’s macOS operating system. The minimum deployment target is specified by the -mmacosx-version-min=version command-line argument. macosx is supported for backward-compatibility reasons, but it is deprecated.

tvos
Apple’s tvOS operating system. The minimum deployment target is specified by the -mtvos-version-min=version command-line argument.

watchos
Apple’s watchOS operating system. The minimum deployment target is specified by the -mwatchos-version-min=version command-line argument.

其实这个系统提供了方便的api API_AVAILABLE ,宏定义在<os/availability.h>

deprecated (gnu::deprecated)

废弃的api。

void f(void) __attribute__((deprecated("message", "replacement")));

diagnose_if

如果对属性函数的调用满足某些用户定义的条件,则可以将该属性放在函数声明上,以便在编译时发出警告或错误


int  tabs(int a)
__attribute__((diagnose_if(a >= 0, "Redundant abs call", "warning")));
int must_abs(int a)
__attribute__((diagnose_if(a >= 0, "Redundant abs call", "error")));


// diagnose_if attributes, this executes without
// issue.



int main(void)
{
    int val =tabs(1); // warning: Redundant abs call
    int val2 = must_abs(1); // error: Redundant abs call
    int val3 = tabs(val);
    int val4 = must_abs(val); // Because run-time checks are not emitted for
}
image.png

diagnose_if 和enable_if比较:

diagnose_if 满足条件发出错误或者警告, enable_if正好相反
如果diagnose_if无法评估提供的条件,则不会发出诊断信息。而enable_if 正好相反

可以标记相同的函数多次


image.png

format (gnu::format)

clang支持format属性。该函数接受printf或scanf格式字符串和相应的参数 或这包含参数的va_list 参数列表
这个我们经常用的NSLog,就是该属性来的。

FOUNDATION_EXPORT void NSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2) NS_NO_TAIL_CALL;
FOUNDATION_EXPORT void NSLogv(NSString *format, va_list args) NS_FORMAT_FUNCTION(1,0) NS_NO_TAIL_CALL;

    #define NS_FORMAT_FUNCTION(F,A) __attribute__((format(__NSString__, F, A)))

Clang implements this mostly the same way as GCC, but there is a difference for functions that accept a va_list argument (for example, vprintf). GCC does not emit -Wformat-nonliteral warning for calls to such functions. Clang does not warn if the format string comes from a function parameter, where the function is annotated with a compatible attribute, otherwise it warns
clang 实现方式和GCC基本相同,但是获取va_list 参数不太一样。
gcc 不会对这样的函数发出 -Wformat-nonliteral 警告。要是是参数的话,clang 也不发出警告,不是参数会发出警告。

格式

attribute((format (A, numA,numB)))
A: 是输入格式样式
NumA:输入格式位置
numB: 参数位置


void foo(const char* s, char *buf, ...) {
  va_list ap;
  va_start(ap, buf);

  vprintf(s, ap); // warning
}

__has_attribute

用来检测是否有属性

#if __has_feature(attribute_ns_returns_retained)
#define NS_RETURNS_RETAINED __attribute__((ns_returns_retained))
#else
#define NS_RETURNS_RETAINED
#endif

借鉴博客
简单介绍
attribute官方介绍
clang attribute

推荐阅读更多精彩内容

  • pdf下载地址:Java面试宝典 第一章内容介绍 20 第二章JavaSE基础 21 一、Java面向对象 21 ...
    王震阳阅读 83,733评论 27 525
  • GNU C的一大特色就是__attribute__机制。__attribute__可以设置函数属性(Functio...
    闭家锁阅读 2,942评论 0 4
  • ES6 http://es6.ruanyifeng.com 目录 1 let 和 const 命令 2 变量的解构...
    雪夜不下雪阅读 6,581评论 0 41
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    萌萌的小伟哥阅读 547评论 0 1
  • 2018年8月11日 星期六 课程完美的进行到第二天,每一次的收获,都是不一样的。 当你要学习的课程涨价的速度,比...
    罗文均阅读 49评论 1 1