GNU C 扩展(二)

七、attribute机制
在GNU C中, 允许声明函数、变量和类型的特殊属性,以便指示编译器进行特定方面的优化和更仔细的代码检查。使用方式为在声明后面加上: __attribute__(( ATTRIBUTE ))。关键字__attribute__用来在声明的时候指定一个特定的属性。该关键字后面紧跟两对圆括号,括号里指定具体的属性,多个说明之间以逗号分隔。

GCC支持很多属性,常用的有以下这些:

1、noreturn
该属性用于函数,表示该函数从不返回。它能够让编译器生成较为优化的代码,消除不必要的警告。例如:

int for_testing(int);
void myexit(void) __attribute__ ((noreturn));
int main(void)
{
    int i = 1;
    for_testing(i);
    return 0;
}
int for_testing(int n)
{
    if(n > 0)
        myexit( );
    else
        return 100;
}
void myexit(void)
{
    exit(0);
}

在这个例子中,如果函数myexit不用 __attribute__ ((noreturn))修饰的话,编译的时候会有警告信息,因为函数for_testing是又返回类型的,但是如果进到了if分支语句的话,就有可能执行exit函数而再也不返回了。
标准C库函数中有很多函数用到了这个属性,比如最熟悉的有exit() 和 abort(),这两个函数从来不返回。
extern void exit(int)   __attribute__((noreturn));
extern void abort(void) __attribute__((noreturn));


2、format
其格式是:format (archetype, string-index, first-to-check)。
属性format用于函数,表示该函数使用printf、scanf、strftime或者strfmon风格的参数,并可以让编译器检查函数声明和函数实际调用参数之间的格式化字符串是否匹配。
archetype指定哪种风格(printf,scanf,strftime,gnu_printf,gnu_scanf,gnu_strftime或者strfmon。你也可以用 __printf__,__scanf__,__strftime__ 或者__strfmon__),string-index指定传入函数的第几个参数是格式化字符串,first-to-check指定从函数的第几个参数开始是变参。
比如:
    static inline int printk(const char *s, ...) __attribute__ ((format (printf, 1, 2)));
这是include/kernel.h中对printk()函数的声明,其中printk的第1个参数是格式化字符串,从第2个参数开始根据格式化字符串检查变参。


3、unused
属性unused用于函数和变量,表示该函数或者变量可能并不使用,这个属性能够避免编译器产生警告信息。比如:
int maybe __attribute__((unused));

void __attribute__((unused)) maybe(void)
{
    printf("whatever");
}

4、section("section-name")
通常,编译器会把生成的代码放在 .text 代码段中,但是有时候你可能需要一些额外的段,或者你需要把你的函数放在特定的段中。属性section允许你的这些要求。例如:
    extern void foobar (void)__attribute__((section("bar")));
编译器将会把函数foobar放在bar这个段里面。

再比如:
++++ include/linux/init.h
#define  __init  __attribute__((section(".init.text")))
#define  __initdata  __attribute__((section(".init.data")))
#define  __exitdata  __attribute__((section(".exit.data")))
#define  __exit_call  __attribute_used__ __attribute__((section(".exitcall.exit")))
通常编译器将函数放在.text段中,静态变量放在.data或者.bss段里面,而使用section属性,可以让编译器将函数或变量放在指定的段中。因此上面对 __init 的定义便表示将 __init 修饰的代码放在 .init.text 段里面。(放在init段里面的代码只会在初始化的时候被执行一遍,因此该段的代码可以在执行之后释放)。


5、aligned(ALIGNMENT)
属性aligned用于变量、结构或者联合,设定一个指定大小的对齐格式,以字节为单位。但是要注意,这个属性的设定只能用来增加对齐地址而不能减少,例如:
char c __attribute__((aligned(16))); //让变量 c 以16字节对齐。
int i __attribute__((aligned(2))); //该aligned属性想要设定变量 i 以2字节对齐,但由于 i 需要4字节对齐。因此该声明无效。
再如:
struct ohci_hcca
{
#define NUM_INTS 32
    __hc32 ini_table [NUM_INTS];

    __hc32 frame_no;
    __hc32 done_head;
u8 reserved_for_hc [116];
u8 what[4];
} __attribute__((aligned(256)));
这表示结构体 ohci_hcca 以256字节对齐。

对于结构体的大小而言,网上书上都有一些比较含糊其辞的解释,今天我再啰嗦两句,彻底解决这个问题。一个结构体的大小取决于里面的成员的排列顺序,以及需要最大对齐方式的那个成员。怎么理解呢? 请看下面这几个例子:
struct A
{
    short s1, s2;
    char c;
};


sizeof(struct A) = 6。因为A成员中最大的对齐成员是short类型,2字节对齐,因此成员c后面需要补1个零。因此总共占用6个字节。

struct B
{
    int i;
    char c;
};

 

sizeof(struct B) = 8。因为B成员中最大的对齐成员是int类型,4字节对齐,因此成员c后面需要补3个零。因此总共占用8个字节。

struct C
{
    double lf;
    char c;
};


sizeof(struct C) = 12。因为C成员中最大的对齐成员是double类型,4字节对齐,因此成员c后面需要补3个零。因此总共占用8个字节。这里一定要注意,一个变量的最小地址对齐格式跟它所占的字节大小不要混淆,一个变量所占的字节的大小指的是变量的尺寸大小,而一个变量的最小地址对齐格式,指的是一个变量的起始地址的值至少是某个数的整数倍,比如int型的最小对齐格式是4个字节,因此int型变量的起始地址至少是4的倍数(可以是8的倍数,16的倍数等)。

再澄清一下,一个结构体里面有很多成员,每个成员都有自己的“最小对齐格式”,那么对于整个结构体来说呢?它的“最小对齐格式”又是什么呢?那就取决于成员中最大的那个“最小对齐格式”,这样说明白了吗? 再来看一个复杂点的例子:

struct D{
    int i;
    char c __attribute__((aligned(256)));
    short s;
};


这时,sizeof (struct D) = 512。由于结构体D中包含的成员c被aligned属性限定为最小256字节对齐,因此整个结构体也要256字节对齐,因此 i 的起始地址a是一个256的倍数,另外,成员c的起始地址也必须是256的倍数,因此 i 与 c 之间空了很多零,另外s两字节对齐,之后统统补零,因为要256对齐,整个结构体占512字节。
如果我们变换一下成员的相对位置,则就不一样了:
struct E
{
    char c __attribute__((aligned(256)));
    int i;
    short s;};

同样的,地址a和地址b都是256的地址,因为虽然成员 c 只占一个字节,但是它的最小对其格式是要256字节对齐,这也决定了整个结构体的最小对齐格式,因此s之后一直补零,一直补满256个字节。


6、packed属性packed用于变量和类型,当用于修饰变量(包括结构体成员)时表示使用最小可能的地址对齐格式(如果此时该变量被aligned属性所修饰,那么它的最小地址对齐格式就是aligned所指定的那个值),当用于修饰枚举、结构体或者联合类型时表示使该类型内的所有成员都具有可能的最小的地址对齐格式(就相当于一次性修饰每一个成员)。
例如:
struct A
{
    char c;
    int i __attribute__((packed));
    short s;
};


被packed修饰的变量 i 会以1个字节对齐,因此整个结构体占8个字节。(否则占12字节)

struct B
{
    char c;
    int i;
    short s;
}__attribute__((packed));
被packed修饰的结构体B会让它里面的所有的成员都具有尽可能小的地址对齐格式,因此整个结构体将会占用 1+4+2 = 7 个字节。

struct C
{
    int i __attribute__((aligned(256)));
    char c;
    short s;
}__attribute__((packed));
被packed修饰的结构体C同样会让它里面的所有的成员都具有尽可能小的地址对齐格式(记住!这代表 i 的起始地址至少是256 的倍数,但是别忘了它只占4个字节,c成员被放置在 i 之后的第4个字节处),但如果结构体C没有packed修饰,成员s的地址将会是偶数,但是有packed修饰的这个结构体中,s的地址将会是紧邻c之后的奇数.

你可能感兴趣的:(GNU C 扩展(二))