C attribute

目录

 

C++ attribute

定义

解释

标准属性 

gnu attribute

对变量进行属性设置

aligned(alignment)

packed

对函数进行属性设置

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

noreturn

weak

weakref,weakref ("target")

hot

unused

used

always_inline


C++ attribute

定义

对函数,变量或者类型进行属性设置,其语法结构如下:[[attribute-list]](c++11)和[[using attribute-namespace:attribute-list]](C++17)

其中attribute-list是一个逗号分隔的零个或多个attribute序列(可能以省略号结尾…表示包扩展)

Identifier 简单的属性,例如[[noretrun]]
attribute-namespace::Identifier 具有命名空间的属性,例如[[gnu::unused]]
Identifier(argument-list) 带有参数的属性,例如[[deprecated("because")]]
attribute-namespace::Identifier(argument-list) 同时带有命名空间和参数列表的属性

如果using:namespace出现在属性列表的开头,而且属性列表中没有其他属性指定了命名空间:using中指定的命名空间将应用于所有属性:

[[using CC: opt(1), debug]] // same as [[CC::opt(1), CC::debug]]
[[using CC: CC::opt(1)]] // error: cannot combine using and scoped attribute

解释

attribute为实现定义的语言扩展提供统一的标准语法,如gnu和ibm语言扩展__attribute__((...))、microsoft扩展__declspec()等。

属性几乎可以在C++程序中到处使用,并且可以应用于几乎所有的事物:类型、变量、函数、名称、代码块、以及整个翻译单元,尽管每个特定属性只有在实现允许的情况下才有效:[[expect_true]]可以是一个只能与if一起使用而不能与类声明一起使用的属性。[[omp::parallel()]]可以是应用于代码块或for循环的属性,但不能应用于int类型等(注意,这两个属性是虚构的示例,请参见下面的标准属性和一些非标准属性)              

在声明中,属性可以出现在整个声明之前,也可以直接出现在被声明的实体的名称之后,在这种情况下,它们被组合在一起。在大多数其他情况下,属性应用于直接位于前面的实体。              

alignas specifier是属性说明符序列的一部分,尽管它有不同的语法。它可能出现在[[…]]属性出现的地方,并可能与它们混合(前提是在允许alignas的情况下使用)              

只有在引入属性说明符或在属性参数内时,才可能出现两个连续的左方括号标记([[)。

void f() {
  int y[3];
  y[[] { return 0; }()] = 1;    // error
  int i [[cats::meow([[]])]]; // OK
}

除了下面列出的标准属性之外,实现还可以支持具有实现定义的行为的任意非标准属性。忽略实现未知的所有属性,而不会导致错误。(C++ 17)

标准属性 

只有以下属性是由C++标准定义的。

[[noreturn]] 指示函数不返回
[[carries_dependency]] 指示释放使用std::memory_order中的依赖链在函数内和函数外传播

[[deprecated]]

[[deprecated]]

指示允许使用用此属性声明的名称或实体,但由于某些原因不鼓励使用
[[fallthrough]] 指示前一个case标签中的fall-through是有意的,编译器不应在fall-through时发出警告
[[nodiscard]] 鼓励编译器在放弃返回值时发出警告 
[[maybe_unused]] 禁止编译器对未使用的实体(如果有)发出警告

[[likely]]

[[unlikely]]

指示编译器应针对通过语句执行路径的可能性大于或小于任何其他执行路径的情况进行优化
[no_unique_address]] 指示非静态数据成员不需要具有与其类的所有其他非静态数据成员不同的地址  
[[optimize_for_synchronized]] 指示应优化函数定义以从同步语句调用

gnu attribute

由于gnu中__attribute__((...))使用的更加广泛,所以我们在下面将重点介绍__attribute__((...))

对变量进行属性设置

aligned(alignment)

aligned属性指定变量或结构字段的最小对齐方式(以字节为单位)。指定时,对齐必须是2的整数恒幂。指定不对齐参数意味着目标的最大对齐,这通常是,但并非总是,8或16字节。例如,

#include <stdio.h>

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

int main()
{
  x=1;
  y=2;
}

我们动态调试可以发现变量之间以16位对齐

   0x5555555545fb <main+1>:	mov    rbp,rsp
   0x5555555545fe <main+4>:	mov    DWORD PTR [rip+0x200a18],0x1        # 0x555555755020 <x>
=> 0x555555554608 <main+14>:	mov    DWORD PTR [rip+0x200a1e],0x2        # 0x555555755030 <y>

packed

packed属性指定结构成员应具有尽可能小的对齐方式,位字段为一位,否则为一个字节,除非使用aligned属性指定了更大的值。该属性不适用于非成员对象。

例如,在下面的结构中,成员数组x被打包,以便它紧跟在a之后,而不带中间填充,在下面这个例子中,我们可以看到明显的区别

#include <stdio.h>

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

struct fooo
{
char a;
int x[2];} fo;

int main()
{
  printf("%ld\n",sizeof(f));
  printf("%ld\n",sizeof(fo));
}

对函数进行属性设置

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

format属性指定函数接受printf、scanf、strftime或strfmon样式的参数,这些参数应根据格式字符串进行类型检查。例如,声明:

void Printf(const char *Fmt, ...) __attribute__ ((format (printf, 1, 2)));

使编译器检查调用Printf时的参数是否与printf样式的格式字符串参数Fmt一致。下面我们举一个例子:

#include <stdio.h>
#include <stdarg.h>

void Printf(const char *Fmt, ...) __attribute__ ((format (printf, 1, 2)));

static FILE *OutputFile = stdout;

void Printf(const char *Fmt, ...) {
  va_list ap;
  va_start(ap, Fmt);
  vfprintf(OutputFile, Fmt, ap);
  va_end(ap);
  fflush(OutputFile);
}


int main ()
{
   Printf("i=%d\n",6);
   Printf("i=%s\n",6);
   Printf("i=%s\n","abc");
   Printf("%s,%d,%d\n",1,2);
   return 0;
}

noreturn

一些标准库函数,如异常和退出,不能返回。gcc自动知道这一点。有些程序定义了自己的函数,这些函数永远不会返回。你可以把它们声明为noreturn来告诉编译器这个事实。例如,

void fatal () __attribute__ ((noreturn));
void
fatal (/* . . . */)
{
/* . . . */ /* Print error message. */ /* . . . */
exit (1);
}

noreturn关键字告诉编译器假定fatal不能返回。然后,它可以优化,而不考虑如果致命武器真的回来会发生什么。这使得代码稍微好一点。更重要的是,它有助于避免未初始化变量的虚假警告。

noreturn关键字在应用时不会影响异常路径:带有noreturn标记的函数仍然可以通过引发异常或调用longjmp返回调用方。

noreturn函数的返回类型不是void是没有意义的,下面我们举一个例子

没有使用noreturn:

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


void myexit(int i) 
{
	exit(i);
}

int main()
{
	myexit(2);
	printf("yes\n");
	return 0;
}

用ida打开

使用noreturn

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

void myexit(int i) __attribute__((noreturn));

void myexit(int i) 
{
	exit(i);
}

int main()
{
	myexit(2);
	printf("yes\n");
	return 0;
}

用ida打开

constructor,destructor,constructor (priority),destructor (priority)
constructor函数属性在执行进入main()之前自动调用。类似地,destructor函数属性在调用main()完成或exit()之后会自动调用函数。具有这些属性的函数对于初始化在程序执行期间隐式使用的数据非常有用。

 在某些目标上,属性还接受一个整数参数来指定优先级,以控制构造函数和析构函数的运行顺序。具有较小优先级数的constructor函数在具有较大优先级数的constructor函数之前运行;destructor函数的相反关系成立。因此,如果有分配资源的构造函数和释放相同资源的析构函数,则这两个函数通常具有相同的优先级。constructor函数和destructor函数的优先级与命名空间范围C++对象所指定的优先级相同。但是,当前调用具有静态存储持续时间和用属性构造函数装饰的C++对象的构造函数的顺序是未指定的。在混合声明中,属性init_priority可用于强制指定顺序。

 在不支持该功能的目标上使用constructor函数和destructor函数属性的参数形式将被拒绝,并出现错误。下面我们举一个例子

#include <stdio.h>
#include <stdlib.h>
 
__attribute__((constructor(101))) void before_1()
{
    printf("\n%d before_1\n",__LINE__);
}
 
__attribute__((destructor(101))) void after_1()
{
    printf("\n%d after_1\n",__LINE__);
}

__attribute__((constructor(102))) void before_2()
{ 
    printf("\n%d before_2\n",__LINE__);
}
 
__attribute__((destructor(102))) void after_2()
{
    printf("\n%d after_2\n",__LINE__);
}
 
int main(int args,char ** argv)
{
    printf("\n%d main\n",__LINE__);
    return EXIT_SUCCESS;
}

注意当constructor (priority),destructor (priority)中priority在[0.100]时,会有以下警告:

weak

弱属性使声明作为弱符号而不是全局符号发出。这主要用于定义库函数,这些函数可以在用户代码中重写,但也可以用于非函数声明。当使用GNU汇编器和链接器时,ELF目标和A.OUT目标都支持弱符号。详细使用方法可以进入https://blog.csdn.net/zhang14916/article/details/98486959查看

weakref,weakref ("target")

weakref属性将声明标记为弱引用。如果没有参数,则它应该附带一个别名属性,命名目标符号。或者,可以将目标作为weakref本身的参数。在这两种情况下,weakref都隐式地将声明标记为弱声明。如果没有目标作为weakref或alias的参数,weakref就相当于弱符号。  

static int x() __attribute__ ((weakref ("y")));
/* is equivalent to... */
static int x() __attribute__ ((weak, weakref, alias ("y")));
/* and to... */
static int x() __attribute__ ((weakref));
static int x() __attribute__ ((alias ("y")));

 弱引用是一个别名,它本身不需要为目标符号提供定义。如果目标符号仅通过弱引用引用,则它将成为弱未定义符号。但是,如果直接引用,则以此类强引用为准,并且符号需要定义,不一定在同一翻译单元中。下面我们举几个例子

//example11.c
#include <stdio.h>
#include <stdlib.h>
static int x() __attribute__ ((weakref ("y")));
static int xx() __attribute__ ((weakref, alias ("yy")));
static int xxx() __attribute__ ((weakref));
static int xxxx() __attribute__ ((alias ("yyyy")));
int yy()
{
	printf("%s\n",__FUNCTION__);
}

int y()
{
	printf("%s\n",__FUNCTION__);
}

int yyyy()
{
	printf("%s\n",__FUNCTION__);
	return 0;
}
int main()
{
    if (x){
	x();
        y();
    }
    else{
        printf("x=%p\n", x);
    }
    if (xx){
	xx();
        yy();
    }
    else{
        printf("y=%p\n", xx);
    }
    if (xxx){
        xxx();
    }
    else{
        printf("xxx=%p\n", xxx);
    }
    if (yyyy){
	xxxx();
        yyyy();
    }
    else{
        printf("yyyy=%p\n", yyyy);
    }
    return 0;
}
//example12.c
#include <stdio.h>
#include <stdlib.h>
int xxx()
{
	printf("%s\n",__FUNCTION__);
}

hot

标签上的hot属性用于通知编译器,标签后面的路径比没有注释的路径更可能。此属性用于无法使用内置预期的情况,例如与计算的goto或asm goto一起使用。

unused

此功能用于程序生成的代码,这些代码可能包含未使用的标签,但使用'-wall'编译。通常不适合在其中使用人工编写的代码,不过在跳转到标签的代码包含在ifdef条件中的情况下,它可能会很有用。下面我们举个例子

//example.c
#include <stdio.h>
int main(void)
{
    printf("main\n");
}

static void a(void)
{
    printf("a\n");
}

当我们使用gcc example.c -Wall -o example编译的时候会出现 warning: ‘a’ defined but not used,当我们对static void a(void)添加属性__attribute__((unused)),将会消除这个警告。

used

附加到函数的此属性意味着必须为该函数生成代码,即使该函数似乎未被引用。例如,当函数仅在内联程序集中引用时,这很有用。

当应用到C++类模板的成员函数时,该属性还意味着,如果实例化类本身,则实例化该函数。下面我们举个小例子演示

//example11.c
#include "example11.h"

int main()
{
	a();
	printf("%d %s()\n",__LINE__,__FUNCTION__);
	return 0;
}
#include <stdio.h>
static void a();
static void b();
static void c() __attribute__((used));
void a()
{
	printf("%d %s()\n",__LINE__,__FUNCTION__);
}
void b()
{
	printf("%d %s()\n",__LINE__,__FUNCTION__);
}
void c()
{
	printf("%d %s()\n",__LINE__,__FUNCTION__);
}

 我们使用-O0优化编译,使用ida打开,我们发现

always_inline

通常,除非指定了优化-O2,否则函数不会内联。对于内联声明的函数,此属性内联函数,而不受内联的任何限制。未能内联这样的函数被诊断为错误。请注意,如果间接调用此类函数,编译器可能会或可能不会内联它,这取决于优化级别,并且内联间接调用失败可能会或可能不会被诊断。下面我们举一个例子:

//example.c
#include <stdio.h>
inline void a(){
	printf("%d %s()\n",__LINE__,__FUNCTION__);
}

int main()
{
	a();
	printf("%d %s()\n",__LINE__,__FUNCTION__);
	return 0;
}

//example11.c
#include <stdio.h>
inline __attribute__((always_inline)) void a(){
	printf("%d %s()\n",__LINE__,__FUNCTION__);
}

int main()
{
	a();
	printf("%d %s()\n",__LINE__,__FUNCTION__);
	return 0;
}

 

 cleanup(cleanup_function)

当变量超出范围时,cleanup属性运行函数。此属性只能应用于自动函数作用域变量;不能应用于具有静态存储持续时间的参数或变量。函数必须采用一个参数,即指向与变量兼容的类型的指针。函数的返回值(如果有)将被忽略。如果启用了'-fexceptions',则在处理异常期间发生的堆栈展开期间运行清除函数。注意,cleanup属性不允许捕捉异常,只允许执行操作。如果cleanup函数不能正常返回,会发生什么情况还没有定义。

(未完待续)

发布了43 篇原创文章 · 获赞 23 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/zhang14916/article/details/102624748