Skip to content

LV035-attribute指令

一、attribute 机制简介

GNU C 的一大特色就是 __attribute__ 机制,它本质是一个编译器的指令,在声明的时候可以提供一些属性,在编译阶段起作用,来做多样化的错误检查和高级优化。用于在 C 、 C++ 、 Objective-C 中修饰变量、函数、参数、方法、类等。

常用 __attribute__ 来设置 函数属性( Function Attribute )、变量属性( Variable Attribute )和 类型属性( Type Attribute ,包括结构体和共用体 )。一般语法格式如下:

c
__attribute__ ((attribute-list))

【注意】

(1) __attribute__ 前后都有两个下划线,并切 后面会紧跟一对原括号,括号里面是相应的 __attribute__ 参数。

(2)在使用 __attribute__ 参数时,我们也可以在参数的前后都加上 __ (两个下划线),例如,使用 __aligned__ 而不是直接使用 aligned ,这样,我们就可以在相应的头文件里使用它而不用关心头文件里是否有重名的宏定义。

二、函数属性( Function Attribute )

__attribute__ 机制设置函数属性可以帮助我们将一些特性添加到函数声明中,从而使编译器在检查错误方面的功能更加强大。同时, __attribute__ 也可以很容易同非 GNU 应用程序想兼容,需要注意的是 GUN C 需要使用 -Wall 编译选项来激活该功能,这是控制警告信息的一个好办法。关于函数的参数可以看官方文档:Function Attributes (Using the GNU Compiler Collection (GCC))

1. format

1.1 语法格式

format 参数的语法格式如下:

c
format(archetype, string-index, first-to-check)

参数说明】 format 属性告诉编译器,按照 printf 、 scanf 、 strftime 或 strfmon 等样式的参数列表格式规则对指定的函数参数进行检查。

语法参数

  • archetype :指定按照哪种风格检查参数,可以是 printf 、 scanf 、 strftime 、 gnu_printf 、 gnu_scanf 、 gnu_strftime 或 strfmon 。
  • string-index :指定传入函数的第几个参数是格式化字符串(一般从 1 开始)。比如, "%d %s\n" 这些参数在函数的第几个位置。
  • first-to-check :指定从函数的第几个参数开始按照上边的规则进行检查,也就是指定格式化输入的字符串在函数参数中开始的位置。

具体格式

c
__attribute__((format(printf, m, n)))
__attribute__((format(scanf, m, n)))
  • m :第几个参数为格式化字符串。
  • n :参数集合中的第一个参数 ... 里的第一个参数在函数参数总数中排在第几个。

1.2 使用实例

c
#include <stdio.h>
/* m = 1, n = 2 */
void myPrintf1(const char * format,...) __attribute__((format(printf,1,2)));
/* m = 2, n = 3 */
void myPrintf2(int a, const char * format,...) __attribute__((format(printf, 2, 3)));
/* 自定义函数 */
void myPrintf1(const char * format,...)
{
	
}
void myPrintf2(int a, const char * format,...)
{
	printf("%d\n", a);
}
int main(int argc, char *argv[])
{
	myPrintf1("%d\n", 5);
	myPrintf2(1, "%d\n", 5);
	
	myPrintf1("%s %d\n", "aaa", "bbb");
	
	return 0;
}

在终端执行以下命令编译程序:

shell
gcc test.c -Wall # 编译链接程序

然后,终端会有以下信息显示:

shell
test.c: In function ‘main’:
test.c:20:17: warning: format ‘%d’ expects argument of type ‘int’, but argument 3 has type ‘char * [-Wformat=]
   20 |  myPrintf1("%s %d\n", "aaa", "bbb");
      |                ~^            ~~~~~
      |                 |            |
      |                 int          char *
      |                %s

当我们去掉 __attribute__ 的时候,便不会再有任何警告信息输出,即改为以下形式:

c
/* m = 1, n = 2 */
void myPrintf1(const char * format,...);
/* m = 2, n = 3 */
void myPrintf2(int a, const char * format,...);

2. noreturn

2.1 语法格式

noreturn 参数的具体语法格式如下:

c
__attribute__((noreturn))

参数说明】 该属性告知编译器指定的函数是没有返回值的,甚至默认不返回值也是不可以的。一些标准库函数,如 abort 和 exit ,是不返回的, GCC 会自动知道这一点,但是我们在有些程序中定义了自己的永不返回的函数,这些函数编译器是不知道的,有的时候编译器就会报警告,我们可以声明它们为 noreturn 属性,以此来告诉编译器这个事实。

2.2 使用实例

c
#include <stdio.h>

void myExit(void)  __attribute__((noreturn));

int main(int argc, char *argv[])
{
	myExit();
	return 0;
}

/* 自定义函数 */
void myExit(void)
{

}

在终端执行以下命令编译程序:

shell
gcc test.c -Wall # 编译链接程序

然后,终端会有以下信息显示:

shell
test.c: In function ‘myExit’:
test.c:15:1: warning: ‘noreturn’ function does return
   15 | }
      | ^

这是因为函数里面什么也没有做,相当于是个空函数,但是空函数使用了默认返回值 void 。上述警告告诉我们,这个函数设置的 noreturn 属性,但是的确返回有返回值。空函数默认返回的 void 空类型空数据,所以说有返回值。如果把 myExit 函数里面增加 noreturn 类型的库函数,或者把 noreturn 属性去掉,就不会再出现错误了:

c
void myExit(void);
/* 或者函数定改成这样 */
void myExit(void)
{
	exit(0); /* 加上 #include <stdlib.h> */
}

3. constructor

3.1 语法格式

constructor 参数的具体语法格式如下:

c
__attribute__((constructor)
/* 或者 */
__attribute__((constructor(priority))

参数说明】 该属性告知编译器指定的函数在 main 函数之前需要被执行。

语法参数

  • priority :在混合声明中,用于指定在 main 函数之前的多个函数执行的顺序(范围似乎是 100 以上, 0-100 是被保留的)。其实这点不是很明白,使用了这参数后,参数的值若在 0-100 ,则会出现警告如下:
shell
warning: constructor priorities from 0 to 100 are reserved for the implementation [-Wprio-ctor-dtor]

注意

(1)去掉优先级的参数的话或者将值设置为大于 100 的数,就不会有警告了。

(2)在不使用 priority 参数的时候,如果有多个函数被设置为 destructor 属性,那么就会按照 函数定义的先后顺序来执行先定义的会先执行,后定义的后执行。声明顺序倒是无所谓,至少我在测试的时候是这个样子,要是有问题的话,欢迎批评指正。

(3)使用 priority 参数的时候,函数会按照 priority 大小顺序执行, priority 值 越小越先执行,值越大越靠后执行

(4)若有未使用 priority 和使用 priority 的混合出现时,先执行使用 priority 参数的函数(数值小的先执行,数值大的后执行),后执行未使用 priority 参数的函数(其中先定义的先执行,后定义的后执行)。

3.2 使用实例

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

void beforeMain1(void) __attribute__((constructor(103)));
void beforeMain2(void) __attribute__((constructor(102)));
void beforeMain3(void) __attribute__((constructor(101)));
void beforeMain4(void) __attribute__((constructor));
void beforeMain5(void) __attribute__((constructor));
void beforeMain6(void) __attribute__((constructor));

int main(int argc, char *argv[])
{
	printf("This is main function!\n");
	return 0;
}

/* 自定义函数 */
void beforeMain1(void)
{
	printf("Before main function1!\n");
}
void beforeMain2(void)
{
	printf("Before main function2!\n");
}
void beforeMain3(void)
{
	printf("Before main function3!\n");
}
void beforeMain4(void)
{
	printf("Before main function4!\n");
}
void beforeMain5(void)
{
	printf("Before main function5!\n");
}
void beforeMain6(void)
{
	printf("Before main function6!\n");
}

在终端执行以下命令编译程序:

shell
gcc test.c -Wall # 编译链接程序
./a.out

然后便会看到有如下信息输出:

c
Before main function3!
Before main function2!
Before main function1!
Before main function4!
Before main function5!
Before main function6!
This is main function!

这就说明,我们自定义的函数在 main 函数执行前先执行了。

4. destructor

4.1 语法格式

destructor 参数的具体语法格式如下:

c
__attribute__((destructor)
/* 或者 */
__attribute__((destructor(priority))

参数说明】 该属性告知编译器指定的函数在 main 函数之后或者 exit 之后被执行。

语法参数

  • priority :在混合声明中,用于指定在 main 函数之后的多个函数执行的顺序(范围似乎是 100 以上, 0-100 是被保留的)。使用了这参数后,参数的值若在 0-100 ,则会出现警告如下:
shell
warning: constructor priorities from 0 to 100 are reserved for the implementation [-Wprio-ctor-dtor]

注意

(1)去掉优先级的参数的话或者将值设置为大于 100 的数,就不会有警告了。

(2)在不使用 priority 参数的时候,如果有多个函数被设置为 destructor 属性,那么就会按照 函数定义的先后顺序来执行后定义的会先执行,先定义的后执行。声明顺序倒是无所谓,至少我在测试的时候是这个样子,要是有问题的话,欢迎批评指正。

(3)使用 priority 参数的时候,函数会按照 priority 大小顺序执行, priority 值 越大越先执行,值越小越靠后执行

(4)若有未使用 priority 和使用 priority 的混合出现时,先执行未使用 priority 参数的函数(其中先定义的后执行,后定义的先执行),再执行使用 priority 参数的函数(数值大的先执行,数值小的后执行)。

4.2 使用实例

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

void afterMain1(void) __attribute__((destructor(103)));
void afterMain2(void) __attribute__((destructor(102)));
void afterMain3(void) __attribute__((destructor(101)));
void afterMain4(void) __attribute__((destructor));
void afterMain5(void) __attribute__((destructor));
void afterMain6(void) __attribute__((destructor));
int main(int argc, char *argv[])
{
	printf("This is main function!\n");
	return 0;
}

/* 自定义函数 */

void afterMain1(void)
{
	printf("After main function1!\n");
}
void afterMain2(void)
{
	printf("After main function2!\n");
}
void afterMain3(void)
{
	printf("After main function3!\n");
}
void afterMain4(void)
{
	printf("After main function4!\n");
}
void afterMain5(void)
{
	printf("After main function5!\n");
}
void afterMain6(void)
{
	printf("After main function6!\n");
}

在终端执行以下命令编译程序:

shell
gcc test.c -Wall # 编译链接程序
./a.out

然后便会看到有如下信息输出:

c
This is main function!
After main function6!
After main function5!
After main function4!
After main function1!
After main function2!
After main function3!

这就说明,我们自定义的函数在 main 函数执行后执行了。

三、变量属性( Variable Attribute )

__attribute__ 机制也可以对变量或者结构体成员进行属性设置。更为详细的参数可以看这里:Common Variable Attributes (Using the GNU Compiler Collection (GCC))

1. aligned

1.1 语法格式

aligned 参数的具体语法格式如下:

c
__attribute__ (aligned)
__attribute__ (aligned(alignment))

参数说明】 aligned 属性告诉编译器指定变量或者结构体成员的最小对齐方式对齐,以字节为单位。指定时,对齐方式必须是 2 的整数常数次方。不指定对齐参数的话意味着指定变量或者结构体成员以最大对齐方式对齐,通常为 8 或 16 字节,但并非总是如此。

语法参数

  • alignment :指定对齐的字节数,一般会设置为 2 、 4 、 8 或者 16 ,另外 GCC 还提供了一个特定于目标的宏 __BIGGEST_ALIGNMENT__ ,这表示我们可以在正在编译的目标机器上对任何数据类型使用的最大对齐方式。

注意

(1)选择针对目标机器的最大对齐方式,可以提高复制操作的效率。

(2)需要注意的是,若目标机器的链接器最大只支持 16 字节对齐的话,即便我们设置为 32 位对齐,那也无济于事。

1.2 使用实例

这里只写一下使用实例,并未做验证,后边会有一篇笔记专门来写结构体占用空间的,那里会对结构体成员使用该属性进行验证。

c
int a __attribute__((aligned(16))) = 0;
char a[3] __attribute__((aligned));
struct TEST {
    int x[2] __attribute__((aligned(8)));
}

2. packed

2.1 语法格式

packed 参数的具体语法格式如下:

c
__attribute__ (packed)

参数说明】 packed 属性告诉编译器指定变量或者结构体成员使用最小的对齐方式,即对变量是 1 字节对齐,对域 field 是位对齐。

2.2 使用实例

这里只写一下使用实例,并未做验证,后边会有一篇笔记专门来写结构体占用空间的,那里会对结构体成员使用该属性进行验证。

c
struct foo
{
  char a;
  int x[2] __attribute__ ((packed));
};

上边的结构体中成员数组 x 的值将会紧跟着 a 成员放置。

四、类型属性( Type Attribute )

__attribute__ 机制也可以对结构体( struct )或共用体( union )进行属性设置。更为详细的参数可以看这里:

Common Type Attributes (Using the GNU Compiler Collection (GCC))

1. aligned

1.1 语法格式

aligned 参数的具体语法格式如下:

c
__attribute__ (aligned)
__attribute__ (aligned(alignment))

参数说明】 aligned 属性告诉编译器为指定类型设置指定对齐方式,以字节为单位。指定时,对齐方式必须是 2 的整数常数次方。不指定对齐参数的话意味着指定变量或者结构体成员以最大对齐方式对齐,通常为 8 或 16 字节,但并非总是如此。

语法参数

  • alignment :指定对齐的字节数,一般会设置为 2 、 4 、 8 或者 16 。

1.2 使用实例

c
struct __attribute__ ((aligned (8))) S
{
	short f[3];
};
typedef int more_aligned_int __attribute__ ((aligned (8)));

2. packed

2.1 语法格式

packed 参数的具体语法格式如下:

c
__attribute__ (packed)

参数说明】 packed 属性对 struct 、 union 类型定义,指定放置其每个成员(除零宽度的位字段外)以最小所需内存。当用于 enum 类型时, packed 属性指定了应使用的最小完整的类型。

2.2 使用实例

c
struct my_unpacked_struct
{
	char c;
	int i;
};

struct __attribute__ ((__packed__)) my_packed_struct
{
	char c;
	int  i;
	struct my_unpacked_struct s;
};

上边的实例中, my_packed_struct 类型的变量数组中的值会紧紧的靠在一起,但是内部成员 s 受到 packed 的约束,如果希望内部成员变量也被 packed 约束的话, my_unpacked_struct 类型也需要进行 packed 约束。

参考资料:

Attribute Syntax (Using the GNU Compiler Collection (GCC))

__attribute__ 机制详解_attribute 语法-CSDN 博客

C 语言 __attribute__ 的使用_c attribute-CSDN 博客

C 语言的 attribute 机制_c 语言 attribute-CSDN 博客