C语言再学习 -- __attribute__详解

Posted 聚优致成

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言再学习 -- __attribute__详解相关的知识,希望对你有一定的参考价值。

一、attribute 介绍

__attribute__是一个编译属性,用于向编译器描述特殊的标识、错误检查或高级优化。它是GNU C特色之一,系统中有许多地方使用到。__attribute__可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)等。

二、attribute 格式

attribute 前后都有两个下划线,并切后面会紧跟一对原括弧,括弧里面是相应的__attribute__ 属性规范。

格式如下:

__attribute__ ((attribute-list)) 

三、指定函数的属性

参看:GNU 声明函数的属性

在 GNU C 中,您声明有关程序中调用的函数的某些内容,这有助于编译器优化函数调用并更仔细地检查您的代码。

以下属性目前在所有目标函数的定义: aligned, alloc_size, noreturn, returns_twice, noinline, noclone, always_inline, flatten, pure, const, nothrow, sentinel, format, format_arg, no_instrument_function, no_split_stack, section, constructor, destructor, used, unused, deprecated, weak, malloc, alias, ifunc, warn_unused_result, nonnull, gnu_inline, externally_visible, hot, cold, artificial, error and warning.

四、指定类型的属性

参看:GNU 指定类型的属性

关键字允许您在定义此类类型时__attribute__指定struct和类型的特殊属性。union此关键字后跟双括号内的属性规范。目前为类型定义了七个属性:alignedpackedtransparent_unionunuseddeprecatedvisibilitymay_alias

五、指定变量的属性

参看:GNU 指定变量的属性

关键字__attribute__允许您指定变量或结构字段的特殊属性。

目前为变量定义的属性:aligned,cleanupcommondeprecatedmodepackedsectionsharedtls_modelunusedusedvector_sizeselectanyweakdllimportdllexport

六、常见属性

1. aligned (alignment)

指定函数的属性:

此属性指定函数的最小对齐方式,以字节为单位。不能使用此属性减少函数的对齐,只能使用此属性增加函数的对齐。请注意,对齐属性的有效性可能受到链接器固有限制的限制。在许多系统上,连接器只能安排函数对齐到某个最大对齐。

指定类型的属性:

此属性指定指定类型变量的最小对齐方式(以字节为单位)。例如,

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

强制编译器确保(尽可能)每个类型为struct Smore_alignd_int的变量至少在8字节的边界上被分配和对齐。在SPARC上,将struct S的所有变量对齐到8字节边界,允许编译器在将struct S的一个变量复制到另一个变量时使用lddstd(双字加载和存储)指令,从而提高运行时效率。

请注意,ISO C标准要求任何给定structunion类型的对齐至少是相关structunion所有成员对齐的最低公倍数的完全倍数。这意味着您可以通过将aligned属性附加到此类类型的任何一个成员来有效地调整structunion类型的对齐方式,但上面示例中所示的符号是一种更明显、直观和可读的方式,用于请求编译器调整整个结构或联合类型的对齐方式。

与前面的示例一样,您可以显式地指定希望编译器用于给定structunion类型的对齐方式(以字节为单位)。或者,您可以省略对齐因子,只要求编译器将类型对齐为您正在编译的目标机器的最大有用对齐。例如,你可以这样写:

struct S  short f[3];  __attribute__ ((aligned));

每当在aligned属性规范中遗漏对齐因子时,编译器会自动将该类型的对齐方式设置为正在编译的目标机器上任何数据类型所使用的最大对齐方式。这样做通常可以使复制操作更有效,因为编译器可以使用任何指令复制最大的内存块,当执行复制到或从具有这样对齐的类型的变量时。

在上面的例子中,如果每个short的大小是2字节,那么整个struct S类型的大小是6字节。大于或等于2的最小幂是8,因此编译器将整个struct S类型的对齐设置为8字节。

请注意,尽管您可以要求编译器为给定类型选择省时的对齐方式,然后只声明该类型的独立对象,但编译器选择省时对齐方式的能力仅在计划创建具有相关(高效对齐)类型的变量数组时才有用。如果声明或使用有效对齐类型的变量数组,那么程序很可能也会对指向相关类型的指针进行指针算术(或下标,这相当于同样的事情),而且编译器为这些指针算术操作生成的代码对于有效对齐类型通常比其他类型更有效。

请注意,aligned属性的有效性可能受到链接器固有限制的限制。在许多系统上,链接器只能安排变量对齐到某个最大对齐。(对于某些连接器,支持的最大对齐可能非常非常小。)如果你的链接器只能对变量进行最多8个字节的对齐,那么在__attribute__中指定aligned(16)仍然只能为你提供8个字节的对齐。

指定变量的属性

此属性指定变量或结构字段的最小对齐方式,以字节为单位。例如,

int x __attribute__ ((aligned (16))) = 0;

导致编译器在16字节边界上分配全局变量x。在68040上,这可以与asm表达式一起使用来访问move16指令,该指令需要16字节对齐的操作数。

还可以指定结构字段的对齐方式。例如,要创建一个双字对齐的int对,你可以这样写:

 struct foo  int x[2] __attribute__ ((aligned (8))); ;

这是创建具有double memberunion的另一种选择,它迫使union以双字对齐。

与前面的示例一样,您可以显式地指定希望编译器为给定变量或结构字段使用的对齐方式(以字节为单位)。或者,您可以省略对齐因子,只要求编译器将变量或字段对齐为您正在编译的目标体系结构的默认对齐方式。默认对齐方式对于所有标量类型已经足够,但对于支持向量操作的目标上的所有向量类型可能还不够。对于特定的目标ABI,默认对齐方式是固定的。

aligned属性只能增加对齐;但您也可以通过指定packed来减少它。

请注意,对齐属性的有效性可能受到链接器固有限制的限制。在许多系统上,链接器只能安排变量对齐到某个最大对齐。(对于某些连接器,支持的最大对齐可能非常非常小。)如果你的链接器只能对变量进行最多8个字节的对齐,那么在__attribute__中指定aligned(16)仍然只能为你提供8个字节的对齐。

扩展:

结构体内存对齐与补齐

一个存储区的地址一定是它自身大小的整数倍(双精度浮点类型的地址只需要4的整数倍就行了),这个规则也叫数据对齐,结构体内部的每个存储区通常也需要遵守这个规则。数据对齐可能造成结构体内部存储区之间有浪费的字节。结构体的大小一定是内部最大基本类型存储区大小的整数倍,这个规则叫数据补齐。

#include <stdio.h>  
typedef struct  
  
    char ch;  
    int num;  
    char ch1;  
str;  

int main (void)  
  
    printf ("sizeof (str) is %d\\n", sizeof (str));  
    return 0;  
  
输出结果:  
sizeof (str) is 12  

变量属性与类型属性举例

下面的例子中使用__attribute__属性定义了一些结构体及其变量,并给出了输出结果和对结果的分析。

struct p

  int a;
  char b;
  char c;
__attribute__((aligned(4))) pp;

struct q

  int a;
  char b;
  struct n qn;
  char c;
__attribute__((aligned(8))) qq;

int main()

  printf("sizeof(int)=%d,sizeof(short)=%d.sizeof(char)=%d\\n",
	sizeof(int),sizeof(short),sizeof(char));
  printf("pp=%d,qq=%d \\n", sizeof(pp),sizeof(qq));
  return 0;


输出结果:
sizeof(int)=4,sizeof(short)=2.sizeof(char)=1
pp=8,qq=24

分析:
sizeof(pp):
sizeof(a)+ sizeof(b)+ sizeof©=4+1+1=6<23=8= sizeof(pp)
sizeof(qq):
sizeof(a)+ sizeof(b)=4+1=5
sizeof(qn)=8;即qn是采用8字节对齐的,所以要在a,b后面添3个空余字节,然后才能存储qn,
4+1+(3)+8+1=17
因为qq采用的对齐是8字节对齐,所以qq的大小必定是8的整数倍,即qq的大小是一个比17大又是8的倍数的一个最小值,由此得到17<24+8=24= sizeof(qq)

2. section-name

指定变量的属性

通常,编译器将它生成的对象放在databss这样的部分中。但是,有时需要额外的部分,或者需要某些特定的变量出现在特定的部分中,例如映射到特殊的硬件。section属性指定变量(或函数)位于特定的部分中。例如,这个小程序使用了几个特定的节名:

struct duart a __attribute__ ((section ("DUART_A"))) =  0 ;
struct duart b __attribute__ ((section ("DUART_B"))) =  0 ;
char stack[10000] __attribute__ ((section ("STACK"))) =  0 ;
int init_data __attribute__ ((section ("INITDATA")));

main()

/* Initialize stack pointer */
init_sp (stack + sizeof (stack));

/* Initialize initialized data */
memcpy (&init_data, &data, &edata - &data);

/* Turn on the serial ports */
init_duart (&a);
init_duart (&b);

将section属性用于全局变量,而不是局部变量,如示例所示。

你可以将section属性与初始化或未初始化的全局变量一起使用,但是链接器要求每个对象都定义一次,除非未初始化的变量暂时放在common(或bss)部分中,并且可以多次“defined”。使用section属性将改变变量进入的部分,如果未初始化的变量有多个定义,则可能导致链接器发出错误。可以使用-fno-common标志或nocommon属性强制初始化变量。

扩展

一个可执行目标文件,它主要由代码段数据段BSS 段构成。代码段主要存放编译生成的可执行指令代码,数据段BSS 段用来存放全局变量、未初始化的全局变量。代码段数据段BSS 段构成了一个可执行文件的主要部分。

参看:UNIX再学习 – 内存管理

参看:GCC 中的强符号、弱符号(-fno-common)

链接器中的全局符号可分为两种:强符号(Strong symbols),弱符号(Weak symbols)。GCC语法中使用__attribute__((weak))来声明这个符号是弱符号的。

而对于全局变量来说,如果初始化了不为0的值,那么该全局变量则被保存在data段,如果初始化的值为0,那么将其保存在bss段,如果没有初始化,则将其保存在common段,等到链接时再将其放入到bss段。关于第三点不同编译器行为会不同,有的编译器会把没有初始化的全局变量直接放到bss段。

以上是关于C语言再学习 -- __attribute__详解的主要内容,如果未能解决你的问题,请参考以下文章

C语言中的__attribute__宏定义之section属性

C语言__attribute__使用

C语言的定义问题,关于__attribute__的用法,望大家帮忙解答

C 语言编程 — GCC Attribute 语法扩展

C 语言编程 — GCC Attribute 语法扩展

C语言 attribute的问题