LV01-17-C语言-attribute机制

本文主要是C语言基础——GNU C 中的 __attribute__机制相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows windows11
Ubuntu Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
SecureCRT Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日
开发板 正点原子 i.MX6ULL Linux阿尔法开发板
uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03)
linux内核 linux-4.15(NXP官方提供)
STM32开发板 正点原子战舰V3(STM32F103ZET6)
点击查看本文参考资料
参考方向 参考原文
GCC官方文档Attribute Syntax (Using the GNU Compiler Collection (GCC))
__attribute__ 机制详解attribute__ 机制详解_weaiken的博客-CSDN博客___attribute
__attribute__的使用C语言__attribute__的使用_【ql君】qlexcel的博客-CSDN博客___attribute
C语言的attribute机制C语言的attribute机制_IT残荷轩的博客-CSDN博客_c语言attribute

【说明】想要详细了解 __attribute__ 的话,就看 GCC 的官方文档,那里边写的是最详细的。

点击查看相关文件下载
--- ---

1. attribute 机制简介

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

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

1
__attribute__ ((attribute-list))

【注意】

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

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

2.函数属性( Function Attribute )

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

2.1 format

2.1.1语法格式

format 参数的语法格式如下:

1
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 :指定从函数的第几个参数开始按照上边的规则进行检查,也就是指定格式化输入的字符串在函数参数中开始的位置。

【具体格式】

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

2.1.2使用实例

点击查看实例
test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#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;
}

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

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

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

1
2
3
4
5
6
7
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 的时候,便不会再有任何警告信息输出,即改为以下形式:

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

2.2 noreturn

2.2.1语法格式

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

1
__attribute__((noreturn))

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

2.2.2使用实例

点击查看实例
test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

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

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

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

}

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

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

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

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

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

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

2.3 constructor

2.3.1语法格式

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

1
2
3
__attribute__((constructor)
/* 或者 */
__attribute__((constructor(priority))

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

【语法参数】

  • priority :在混合声明中,用于指定在 main 函数之前的多个函数执行的顺序(范围似乎是 100 以上, 0-100 是被保留的)。其实这点不是很明白,使用了这参数后,参数的值若在 0-100 ,则会出现警告如下:
1
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 参数的函数(其中先定义的先执行,后定义的后执行)。

2.3.2使用实例

点击查看实例
test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#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");
}

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

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

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

1
2
3
4
5
6
7
Before main function3!
Before main function2!
Before main function1!
Before main function4!
Before main function5!
Before main function6!
This is main function!

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

2.4 destructor

2.4.1语法格式

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

1
2
3
__attribute__((destructor)
/* 或者 */
__attribute__((destructor(priority))

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

【语法参数】

  • priority :在混合声明中,用于指定在 main 函数之后的多个函数执行的顺序(范围似乎是 100 以上, 0-100 是被保留的)。使用了这参数后,参数的值若在 0-100 ,则会出现警告如下:
1
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 参数的函数(数值大的先执行,数值小的后执行)。

2.4.2使用实例

点击查看实例
test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#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");
}

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

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

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

1
2
3
4
5
6
7
This is main function!
After main function6!
After main function5!
After main function4!
After main function1!
After main function2!
After main function3!

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

3.变量属性( Variable Attribute )

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

3.1 aligned

3.1.1语法格式

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

1
2
__attribute__ (aligned)
__attribute__ (aligned(alignment))

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

【语法参数】

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

【注意】

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

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

3.1.2使用实例

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

点击查看实例
test.c
1
2
3
4
5
6
int a __attribute__((aligned(16))) = 0;
char a[3] __attribute__((aligned));
struct TEST
{
int x[2] __attribute__((aligned(8)));
}

3.2 packed

3.2.1语法格式

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

1
__attribute__ (packed)

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

3.2.2使用实例

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

点击查看实例
test.c
1
2
3
4
5
struct foo
{
char a;
int x[2] __attribute__ ((packed));
};

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

4.类型属性( Type Attribute )

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

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

4.1 aligned

4.1.1语法格式

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

1
2
__attribute__ (aligned)
__attribute__ (aligned(alignment))

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

【语法参数】

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

4.1.2使用实例

点击查看实例
test.c
1
2
3
4
5
struct __attribute__ ((aligned (8))) S
{
short f[3];
};
typedef int more_aligned_int __attribute__ ((aligned (8)));

4.2 packed

4.2.1语法格式

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

1
__attribute__ (packed)

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

4.2.2使用实例

点击查看实例
test.c
1
2
3
4
5
6
7
8
9
10
11
12
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 约束。