C語言的attribute機制

1. __attribute__作用及使用方法

在Linux內核中經常會遇到這麼一類的定義:就是在函數、變量、或者數據類型裏面添加__attribute__,對於內核的初學者來說,往往不懂這些定義是什麼意思,遇到此類的定義往往需要查找半天,導致學習進度延誤。

下面就從__attribute__的作用,約束方法以及使用方法和使用實例上來給大家講解。

1.1 __attribute__的作用:

	設置函數、變量以及數據類型的屬性。

之前大家可能會遇到過:在C語言中對於函數或者變量重複定義的問題,attribute機制可以很好的解決這類的衝突問題;C語言中使用main函數作爲入口函數,通常大家理解的在main函數之前,或者main函數結束之後可能不會再運行什麼函數了,引入attribute機制就可以解決這類的問題;對於函數中可能直接終止的執行進行不恰當的調用會引起程序無故終止,attribute機制也可以解決這類的問題。

1.2 __attribute__使用方法:

	在函數、數據、數據類型的定義之後,定義的頓號之前。
	語法格式爲:
			__attribute__((attr_list))
			請注意在__attribute__後一定是兩組小括號,後面跟着我們要設置屬性的關鍵字。
			attribute英文字符的下劃線是兩個。

比如我們可以設置:
void func(void ) __attribute__((attr_list));
int num __attribute__((attr_list));
struct test { int number; } __attribute__((attr_list))

本文章將會根據__attribute__針對函數屬性、變量屬性數據類型屬性一一用實例講解。根據gnu的attribute介紹一章,鏈接如下:gnu Function attribute
gnu Variable attribute

2 函數屬性設置

gnu對於函數屬性主要設置的關鍵字如下:

		alias:      設置函數別名。
		aligned:    設置函數對齊方式。
		always_inline/gnu_inline: 
					函數是否是內聯函數。
		constructor/destructor:
					主函數執行之前、之後執行的函數。
		format:
					指定變參函數的格式輸入字符串所在函數位置以及對應格式輸出的位置。
		noreturn:
					指定這個函數沒有返回值。
					請注意,這裏的沒有返回值,並不是返回值是void。而是像_exit/exit/abord那樣
					執行完函數之後進程就結束的函數。
		weak:指定函數屬性爲弱屬性,而不是全局屬性,一旦全局函數名稱和指定的函數名稱
			 命名有衝突,使用全局函數名稱。

2.1 函數的constructor/destructor屬性

一般認爲,在C語言中,main函數是函數的執行起點和終點,在main函數之前和之後都不會執行什麼額外的步驟。但是gnu引入的constructor和destructor的attribute屬性關鍵字,可以讓C函數在執行之前,和執行之後分別執行特定的函數。

編譯以下實例,然後執行。

//main.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
static __attribute__((constructor))
before(void)
{
        printf("before main\n");
}

static __attribute__((destructor))
after(void)
{
        printf("after main\n");
}

int main(void)
{
        printf("in main\n");

        return EXIT_SUCCESS;
}

我們可以看到以下執行結果:

before main
in main
after main

在代碼中的main函數裏面,我們沒有使用before和after兩個函數,但是在實際執行中before函數在main函數之前執行,after函數在main函數之後執行。這和before和after函數分別使用了attribute屬性有關。

2.2 函數的noreturn屬性

函數的noreturn屬性可以告訴編譯器,這個函數是沒有返回值的,甚至默認不返回值也是不可以的。

include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


void noret(void)
__attribute__((noreturn));

void noret(void)
{

}

int main(void)
{
        noret();
        return 0;
}

如以上代碼,我們可以看到noret函數在定義的時候使用了noreturn屬性,函數裏面什麼也沒有做,相當於是個空函數,但是空函數使用了默認返回值void。如果我們編譯的話,會出現以下警告:

main.c: In function ‘noret’:
main.c:12:1: warning: ‘noreturn’ function does return [enabled by default]
 }
 ^

上述警告告訴我們,這個函數設置的noreturn屬性,但是的確返回有返回值。空函數默認返回的void空類型空數據。所以說有返回值。
如果把noret函數裏面增加noreturn類型的庫函數,或者把noreturn屬性去掉,就不會再出現錯誤了,修改如下:

void noret(void)
{
        _exit(0);
}

2.3 函數weak屬性

函數的weak屬性比較有意思,它的作用在有名稱衝突的函數中,如果我們在使用中有兩個函數名稱是一樣的,我們編譯器會報告給我們名稱衝突的錯誤。如果其中一個函數使用了weak屬性,我們的編譯器就會把不含有weak的函數名稱編譯進入我們的文件裏面。
實例如下:

//test.c
#include "test.h"  
#include <stdio.h>
int weak_func(void)
{
        printf("this is in test weak function\n");
        return 0;  
}
//test.h
#ifndef __test_h_  

#define __test_h_

int weak_func(void);
        
#endif
//main.c
#include <stdio.h>
#include "test.h"


//extern int weak_func(void) __attribute__((weak));
int weak_func(void)
{
        printf("this is in main weak function\n");
        return 0;
}

int main(void)
{
        weak_func();
        return 0;
}

上面的函數編譯是編譯不通過的,編譯結果如下:

gcc main.c test.h test.c
/tmp/cciED8ZA.o: In function `weak_func':
test.c:(.text+0x0): multiple definition of `weak_func'
/tmp/ccZwUE37.o:main.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status

顯示名稱衝突,如果把main.c裏面的extern int weak_func(void) __attribute__((weak));去掉註釋,則可以編譯過,使用的是test.c文件裏面的weak_func。

2.4 函數format屬性

format屬性是在使用變長參數指定格式化字符串的時候使用的比較多。可以讓編輯器檢查格式化的字符串使用的是否正確。
format使用格式如下:

format (archetype, string-index, first-to-check)
archetype:
			指定參照哪個函數的格式化字符串:printf、scanf、strftime....
string-index:
			指定格式化字符串在函數的第幾個參數。
			比如:“%d  %s\n”這些參數在函數的第幾個位置。
first-to-check:
			指定格式化輸入的字符串在函數參數中開始的位置。

如下代碼:

#include <stdio.h>


void myprintf(const char * format,...)
__attribute__((format(printf,1,2)));
void myprintf(const char * format,...)
{
        printf("%s", format);
}

int main(void)
{
        myprintf("abc%d\n", 1);
        myprintf("abc%s\n", "abc");
        myprintf("abc%s\n", 1);
        myprintf("%s %d\n", 2, 1);
        return 0;
}

myprintf定義時候指定參照printf的格式化字符串,1代表指定檢查格式化字符串的位置,2指定參照字符串開始檢查的位置。
我們

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章