1. 程式人生 > >__attribute__用法解析

__attribute__用法解析

attribute是GNU C特色之一,在linux開發中用的比較廣泛.系統中有許多地方使用到. attribute可以設定函式屬性(Function Attribute )、變數屬性(Variable Attribute )和型別屬性(Type Attribute)等.

函式屬性(Function Attribute)

  • noreturn
  • noinline
  • always_inline
  • pure
  • const
  • nothrow
  • sentinel
  • format
  • format_arg
  • no_instrument_function
  • section
  • constructor
  • destructor
  • used
  • unused
  • deprecated
  • weak
  • malloc
  • alias
  • warn_unused_result
  • nonnull

型別屬性(Type Attributes)

  • aligned
  • packed
  • transparent_union,
  • unused,
  • deprecated
  • may_alias

變數屬性(Variable Attribute)

  • aligned
  • packed

Clang特有的

  • availability
  • overloadable

書寫格式

書寫格式:attribute後面會緊跟一對原括弧,括弧裡面是相應的attribute引數

__attribute__(xxx)

常見的系統用法

format

官方例子:NSLog

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

format屬性可以給被宣告的函式加上類似printf或者scanf的特徵,它可以使編譯器檢查函式宣告和函式實際呼叫引數之間的格式化字串是否匹配。該功能十分有用,尤其是處理一些很難發現的bug。對於format引數的使用如下
format (archetype, string-index, first-to-check)
第一引數需要傳遞“archetype”指定是哪種風格,這裡是 NSString;“string-index”指定傳入函式的第幾個引數是格式化字串;“first-to-check”指定第一個可變引數所在的索引.

noreturn

官方例子: abort() 和 exit()

該屬性通知編譯器函式從不返回值。當遇到類似函式還未執行到return語句就需要退出來的情況,該屬性可以避免出現錯誤資訊。

availability

官方例子:

- (CGSize)sizeWithFont:(UIFont *)font NS_DEPRECATED_IOS(2_0, 7_0, "Use -sizeWithAttributes:") __TVOS_PROHIBITED;

//來看一下 後邊的巨集
 #define NS_DEPRECATED_IOS(_iosIntro, _iosDep, ...) CF_DEPRECATED_IOS(_iosIntro, _iosDep, __VA_ARGS__)

define CF_DEPRECATED_IOS(_iosIntro, _iosDep, ...) __attribute__((availability(ios,introduced=_iosIntro,deprecated=_iosDep,message="" __VA_ARGS__)))

//巨集展開以後如下
__attribute__((availability(ios,introduced=2_0,deprecated=7_0,message=""__VA_ARGS__)));
//ios即是iOS平臺
//introduced 從哪個版本開始使用
//deprecated 從哪個版本開始棄用
//message    警告的訊息

availability屬性是一個以逗號為分隔的引數列表,以平臺的名稱開始,包含一些放在附加資訊裡的一些里程碑式的宣告。

  • introduced:第一次出現的版本。

  • deprecated:宣告要廢棄的版本,意味著使用者要遷移為其他API

  • obsoleted: 宣告移除的版本,意味著完全移除,再也不能使用它

  • unavailable:在這些平臺不可用

  • message:一些關於廢棄和移除的額外資訊,clang發出警告的時候會提供這些資訊,對使用者使用替代的API非常有用。

  • 這個屬性支援的平臺:ios,macosx。

簡單例子:

//如果經常用,建議定義成類似系統的巨集
- (void)oldMethod:(NSString *)string __attribute__((availability(ios,introduced=2_0,deprecated=7_0,message="用 -newMethod: 這個方法替代 "))){
    NSLog(@"我是舊方法,不要調我");
}

- (void)newMethod:(NSString *)string{
    NSLog(@"我是新方法");
}

效果:

 

Paste_Image.png

//如果呼叫了,會有警告

 

Paste_Image.png

unavailable

告訴編譯器該方法不可用,如果強行呼叫編譯器會提示錯誤。比如某個類在構造的時候不想直接通過init來初始化,只能通過特定的初始化方法()比如單例,就可以將init方法標記為unavailable;

//系統的巨集,可以直接拿來用
 #define UNAVAILABLE_ATTRIBUTE __attribute__((unavailable))

 #define NS_UNAVAILABLE UNAVAILABLE_ATTRIBUTE

@interface Person : NSObject

@property(nonatomic,copy) NSString *name;

@property(nonatomic,assign) NSUInteger age;

- (instancetype)init NS_UNAVAILABLE;

- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age;

@end

Paste_Image.png

//實際上unavailable後面可以跟引數,顯示一些資訊,如:

//系統的
 #define NS_AUTOMATED_REFCOUNT_UNAVAILABLE __attribute__((unavailable("not available in automatic reference counting mode")))

objc_root_class

表示這個類是一個根類(基類),比如NSObject,NSProxy.

//摘自系統
//NSProxy
NS_ROOT_CLASS
@interface NSProxy <NSObject> {
    Class   isa;
}

//NSObject
__OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0)
OBJC_ROOT_CLASS
OBJC_EXPORT
@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

NSObject

@property (nonatomic,strong) __attribute__((NSObject)) CFDictionaryRef myDictionary;

CFDictionaryRef屬於CoreFoundation框架的,也就是非OC物件,加上attribute((NSObject))後,myDictionary的記憶體管理會被當做OC物件來對待.

objc_designated_initializer

用來修飾類的designated initializer初始化方法,如果修飾的方法裡沒有呼叫super類的 designated initializer,編譯器會發出警告。可以簡寫成NS_DESIGNATED_INITIALIZER

這篇文章講的很好,建議參考這個.
https://yq.aliyun.com/articles/5847

visibility

語法:

__attribute__((visibility("visibility_type")))

其中,visibility_type 是下列值之一:

  • default
    假定的符號可見性可通過其他選項進行更改。預設可見性將覆蓋此類更改。預設可見性與外部連結對應。

  • hidden
    該符號不存放在動態符號表中,因此,其他可執行檔案或共享庫都無法直接引用它。使用函式指標可進行間接引用。

  • internal
    除非由 特定於處理器的應用二進位制介面 (psABI) 指定,否則,內部可見性意味著不允許從另一模組呼叫該函式。

  • protected
    該符號存放在動態符號表中,但定義模組內的引用將與區域性符號繫結。也就是說,另一模組無法覆蓋該符號。

  • 除指定 default 可見性外,此屬性都可與在這些情況下具有外部連結的宣告結合使用。
    您可在 C 和 C++ 中使用此屬性。在 C++ 中,還可將它應用於型別、成員函式和名稱空間宣告。

系統用法:

//  UIKIT_EXTERN     extern
 #ifdef __cplusplus
 #define UIKIT_EXTERN        extern "C" __attribute__((visibility ("default")))
 #else
 #define UIKIT_EXTERN            extern __attribute__((visibility ("default")))
 #endif

nonnull

編譯器對函式引數進行NULL的檢查,引數型別必須是指標型別(包括物件)
//使用

- (int)addNum1:(int *)num1 num2:(int *)num2  __attribute__((nonnull (1,2))){//1,2表示第一個和第二個引數不能為空
    return  *num1 + *num2;
}

- (NSString *)getHost:(NSURL *)url __attribute__((nonnull (1))){//第一個引數不能為空
    return url.host;
}

常見用法

aligned

__attribute((aligned (n))),讓所作用的結構成員對齊在n位元組自然邊界上。如果結構中有成員的長度大於n,則按照最大成員的長度來對齊.例如:

不加修飾的情況

typedef struct
{
    char  member1;
    int   member2;
    short member3;
}Family;

//輸出位元組:
NSLog(@"Family size is %zd",sizeof(Family));
//輸出結果為:
2016-07-25 10:28:45.380 Study[917:436064] Family size is 12

//修改位元組對齊為1

typedef struct
{
    char  member1;
    int   member2;
    short member3;
}__attribute__ ((aligned (1))) Family;

//輸出位元組:
NSLog(@"Family size is %zd",sizeof(Family));
//輸出結果為:
2016-07-25 10:28:05.315 Study[914:435764] Family size is 12

和上面的結果一致,因為 設定的位元組對齊為1.而結構體中成員的最大位元組數是int 4個位元組,1 < 4,按照4位元組對齊,和系統預設一致.

修改位元組對齊為8

typedef struct
{
    char  member1;
    int   member2;
    short member3;
}__attribute__ ((aligned (8))) Family;

//輸出位元組:
NSLog(@"Family size is %zd",sizeof(Family));
//輸出結果為:
2016-07-25 10:28:05.315 Study[914:435764] Family size is 16

這裡 8 > 4,按照8位元組對齊,結果為16,不知道位元組對齊的可以看我的這篇文章http://www.jianshu.com/p/f69652c7df99

可是想了半天,也不知道這玩意有什麼用,設定值小於系統預設的,和沒設定一樣,設定大了,又浪費空間,效率也沒提高,感覺學習學習就好.

packed

讓指定的結構結構體按照一位元組對齊,測試:

//不加packed修飾
typedef struct {
    char    version;
    int16_t sid;
    int32_t len;
    int64_t time;
} Header;

//計算長度
NSLog(@"size is %zd",sizeof(Header));
輸出結果為:
2016-07-22 11:53:47.728 Study[14378:5523450] size is 16

可以看出,預設系統是按照4位元組對齊

//加packed修飾
typedef struct {
    char    version;
    int16_t sid;
    int32_t len;
    int64_t time;
}__attribute__ ((packed)) Header;

//計算長度
NSLog(@"size is %zd",sizeof(Header));
輸出結果為:
2016-07-22 11:57:46.970 Study[14382:5524502] size is 15

用packed修飾後,變為1位元組對齊,這個常用於與協議有關的網路傳輸中.

noinline & always_inline

行內函數:行內函數從原始碼層看,有函式的結構,而在編譯後,卻不具備函式的性質。行內函數不是在呼叫時發生控制轉移,而是在編譯時將函式體嵌入在每一個呼叫處。編譯時,類似巨集替換,使用函式體替換呼叫處的函式名。一般在程式碼中用inline修飾,但是能否形成行內函數,需要看編譯器對該函式定義的具體處理

  • noinline 不內聯
  • always_inline 總是內聯
  • 這兩個都是用在函式上

內聯的本質是用程式碼塊直接替換掉函式呼叫處,好處是:快程式碼的執行,減少系統開銷.適用場景:

  • 這個函式更小
  • 這個函式不被經常呼叫

使用例子:

//函式宣告
void test(int a) __attribute__((always_inline));

warn_unused_result

當函式或者方法的返回值很重要時,要求呼叫者必須檢查或者使用返回值,否則編譯器會發出警告提示

- (BOOL)availiable __attribute__((warn_unused_result))
{
   return 10;
}

警告如下:

Paste_Image.png

objc_subclassing_restricted

因為某些原因,我們不希望這個類被繼承,也就是 "最終"的類,用法如下:

__attribute__((objc_subclassing_restricted))
@interface ViewController : UIViewController


@end

如果繼承了這個類,編譯器會報錯

Paste_Image.png

objc_requires_super

這個屬性要求子類在重寫父類的方法時,必須要過載父類方法,也就是呼叫super方法,否則警告.示例如下:

@interface ViewController : UIViewController

- (void)jump __attribute__((objc_requires_super));

@end

- (void)jump{
    NSLog(@"父類必須先執行");
}


@interface SGViewController : ViewController

@end

@implementation SGViewController
- (void)jump{
    NSLog(@"子類才能再執行");
}
@end

警告如下:

Paste_Image.png

objc_boxable

實現類似於NSNumber 的快速打包能力@(...),一般對於struct,union我們只能通過NSValue將其打包. objc_boxable 可以幫助我們實現快速打包,示例如下:

//自定義結構體
typedef struct __attribute__((objc_boxable)){
    CGFloat x,y,width,height;
}SGRect;

 SGRect rect = {0,0,100,200};
 //這裡直接打包成NSValue
 NSValue *value = @(rect);
 
 //這裡我直接用系統的方法列印
 NSLog(@"%@",NSStringFromCGRect(value.CGRectValue));
 
 輸出:
 2016-07-21 21:28:43.538 Study[14118:5408921] {{0, 0}, {100, 200}}

這樣SGRect就具備快速打包功能了.

constructor / destructor

意思是: 構造器和析構器;constructor修飾的函式會在main函式之前執行,destructor修飾的函式會在程式exit前呼叫.
示例如下:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSLog(@"main");
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

__attribute__((constructor))
void  before(){
    NSLog(@"before main");
}

__attribute__((destructor))
void  after(){
    NSLog(@"after main");
}

//在viewController中呼叫exit
- (void)viewDidLoad {
    [super viewDidLoad];
    
    exit(0);
}
輸出如下:

2016-07-21 21:49:17.446 Study[14162:5415982] before main
2016-07-21 21:49:17.447 Study[14162:5415982] main
2016-07-21 21:49:17.534 Study[14162:5415982] after main

注意點:

  • 程式退出的時候才會呼叫after函式,經測試,手動退出程式會執行
  • 上面兩個函式不管寫在哪個類裡,哪個檔案中效果都一樣
  • 如果存在多個修飾的函式,那麼都會執行,順序不定

實際上如果存在多個修飾過的函式,可以它們的調整優先順序
程式碼如下:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSLog(@"main");
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

__attribute__((constructor(101)))
void  before1(){
    NSLog(@"before main - 1");
}
__attribute__((constructor(102)))
void  before2(){
    NSLog(@"before main - 2");
}

__attribute__((destructor(201)))
void  after1(){
    NSLog(@"after main - 1");
}
__attribute__((destructor(202)))
void  after2(){
    NSLog(@"after main - 2");
}

輸出結果如下:
2016-07-21 21:59:35.622 Study[14171:5418393] before main - 1
2016-07-21 21:59:35.624 Study[14171:5418393] before main - 2
2016-07-21 21:59:35.624 Study[14171:5418393] main
2016-07-21 21:59:35.704 Study[14171:5418393] after main - 2
2016-07-21 21:59:35.704 Study[14171:5418393] after main - 1

注意點:

  • 括號內的值表示優先順序,[0,100]這個返回時系統保留的,自己千萬別調用.
  • 根據輸出結果可以看出,main函式之前的,數值越小,越先呼叫;main函式之後的數值越大,越先呼叫.

當函式宣告和函式實現分開寫時,格式如下:

static void before() __attribute__((constructor));

static void before() {
    printf("before\n");
}

討論:+load,constructor,main的執行順序,程式碼如下:


+ (void)load{
    NSLog(@"load");
}
__attribute__((constructor))
void  before(){
    NSLog(@"before main");
}

輸出結果如下:
2016-07-21 22:13:58.591 Study[14185:5421811] load
2016-07-21 22:13:58.592 Study[14185:5421811] before main
2016-07-21 22:13:58.592 Study[14185:5421811] main

可以看出執行順序為:
load->constructor->main
為什麼呢?
因為 dyld(動態連結器,程式的最初起點)在載入 image(可以理解成 Mach-O 檔案)時會先通知 objc runtime 去載入其中所有的類,每載入一個類時,它的 +load 隨之呼叫,全部載入完成後,dyld 才會呼叫這個 image 中所有的 constructor 方法,然後才呼叫main函式.

enable_if

用來檢查引數是否合法,只能用來修飾函式:

void printAge(int age)
__attribute__((enable_if(age > 0  && age < 120, "你丫太監?")))
{
    NSLog(@"%d",age);
}

表示只能輸入的引數只能是 0 ~ 120左右,否則編譯報錯
報錯如下:

Paste_Image.png

cleanup

宣告到一個變數上,當這個變數作用域結束時,呼叫指定的一個函式.如果不知道什麼是作用域,請先學習一下.例子:

//這裡傳遞的引數是變數的地址
void intCleanup(int *num){
    NSLog(@"cleanup------%d",*num);
}

- (void)test{
  int a __attribute__((cleanup(intCleanup))) = 10;
}

輸出結果為:
2016-07-22 09:59:09.139 Study[14293:5495713] cleanup------10

注意點:

  • 指定的函式傳遞的引數是變數的地址
  • 作用域的結束包括:大括號結束、return、goto、break、exception等情況
  • 當作用域內有多個cleanup的變數時,遵守 先入後出 的棧式結構.

示例程式碼:

void intCleanup(int *num){
    NSLog(@"cleanup------%d",*num);
}

void stringCleanup(NSString **str){
    NSLog(@"cleanup------%@",*str);
}

void rectCleanup(CGRect *rect){
    CGRect temp = *rect;
    NSString *str = NSStringFromCGRect(temp);
    NSLog(@"cleanup------%@",str);
}


 int a __attribute__((cleanup(intCleanup))) = 10;
    {
        NSString *string __attribute__((cleanup(stringCleanup))) = @"string";
        CGRect rect __attribute__((cleanup(rectCleanup))) = {0,0,1,1};
    }
    
    
    輸出結果為:
    2016-07-22 10:09:36.621 Study[14308:5498861] cleanup------{{0, 0}, {1, 1}}
2016-07-22 10:09:36.622 Study[14308:5498861] cleanup------string
2016-07-22 10:09:36.622 Study[14308:5498861] cleanup------10
    

討論:如果修飾了某個物件,那麼cleanup和dealloc,誰先執行?
測試程式碼如下:

void objectCleanup(NSObject **obj){
    NSLog(@"cleanup------%@",*obj);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    ViewController *vc __attribute__((cleanup(objectCleanup))) = [[ViewController alloc] init];
}

- (void)dealloc{
    NSLog(@"dealloc");
}

輸出結果如下:
2016-07-22 10:23:08.839 Study[14319:5502769] cleanup------<ViewController: 0x13fe881e0>
2016-07-22 10:23:08.840 Study[14319:5502769] dealloc

可以明顯看出,cleanup先於物件的dealloc執行.

  • 在block中的用法:在block中使用,先看例子:
//指向block的指標,覺得不好理解可以用typeof
void blockCleanUp(void(^*block)()){
    (*block)();
}

 void (^block)(void) __attribute__((cleanup(blockCleanUp))) = ^{
        NSLog(@"finish block");
    };

這個好處就是,不用等到block最後才寫某些程式碼,我們可以把它放在block的任意位置,防止忘記.

overloadable

用於c語言函式,可以定義若干個函式名相同,但引數不同的方法,呼叫時編譯器會自動根據引數選擇函式原型:

__attribute__((overloadable)) void print(NSString *string){
    NSLog(@"%@",string);
}

__attribute__((overloadable)) void print(int num){
    NSLog(@"%d",num);
}

//呼叫
print(10);
print(@"哈哈");

objc_runtime_name

看到runtime是不是就感覺高大上,沒錯這個也跟執行時有關.作用是將將類或協議的名字在編譯時指定成另一個.示例如下:

__attribute__((objc_runtime_name("NSObject")))
@interface SGObject :NSObject

@end

 //呼叫
 NSLog(@"%@",[SGObject class]);
 //輸出
 2016-07-22 11:18:00.934 Study[14355:5516261] NSObject

可以用來做程式碼混淆.

更多請看官網:
https://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/Function-Attributes.html


轉載來自:
作者:iOSSinger
連結:https://www.jianshu.com/p/29eb7b5c8b2d