C23引入了属性[[reproducible]]
和[[unsequenced]]
.
- 他们背后的动机是什么?
- 它们是如何定义的,它们对函数有什么影响?
- 我应该将它们应用于什么类型的函数?
C23引入了属性[[reproducible]]
和[[unsequenced]]
.
激励性的问题是,当没有可用的函数定义时,编译器无法洞察函数.这会阻止几乎所有的编译器优化(除非使用LTO).
请考虑以下示例:
// note: this is redundant, because [[未排序]] implies [[可重现]]
int square(int x) [[可重现]] [[未排序]];
int arr[] = {
square(2),
square(2),
square(3),
square(3),
};
即使编译器没有square
的定义,它也可以执行两个优化:
[[可重现]]
,因此连续两次调用square(2)
会产生相同的结果,并且编译器可以决定只调用square(2)
一次[[未排序]]
,所以可以以任何顺序调用square
,编译器甚至可以在程序启动时决定只计算square(2)
一次.它还可以决定在square(2)
之前计算square(3)
,如果这样做更有效率的话.这样的优化也可以通过在头部中定义函数inline
,并让编译器自己推断这些属性来实现.然而,对于复杂的函数,由于额外的编译速度减慢,将所有函数都设置为inline
并不可行.
有关更严格的说明,请参阅C23 standard working draft N3096§6.7.12.7函数类型的标准属性.
[[可重现]]
该属性断言函数是reproducible function,这意味着
Effectless限制函数可以修改的状态.如果修改了任何非本地状态,则只能通过传递给它的指针进行修改.例如,如果一个void to_upper_case(char *str)
函数只修改局部变量和str
的内容,那么它就是effectless.(直觉上,该功能没有可观察到的副作用.)
Idempotent意味着多次调用该函数与调用一个函数具有相同的效果.例如,我们可以呼叫to_upper_case(s); to_upper_case(s);
,这与只呼叫一次具有相同的效果.
[[未排序]]
该属性断言函数是unsequenced function,这意味着
Stateless表示static
或thread_local
个局部变量不能为非const
,也不能为volatile
.
Independent意味着函数的所有调用都将看到相同的全局变量值,不会更改全局状态,也不会通过指针参数更改任何状态.to_upper_case
不是独立的,但像strlen
这样的函数可以是独立的.
直观地说,unsequenced函数可以被任意排序,甚至可以在其观察到的状态改变之间并行地排序:(另见标准中的脚注196)
char *str = /* ... */; // A
strlen(str);
global = 123;
strlen(str);
strcpy(str, /* ... */); // B
在本例中,在点A
和B
之间可以有一个、两个或无限多个对strlen
的呼叫.这些可以顺序进行,也可以并行进行.无论如何,对于unsequenced函数,结果必须是相同的.global
的Mutations 不允许改变strlen
的结果.
GCC属性pure
和const
是这些标准属性的灵感来源,它们的行为类似.有关比较,请参见N2956 5.8 Some differences with GCC const and pure.简而言之:
pure
比[[independent]]
轻松多了const
比[[未排序]]
更严格These attributes are meant for advanced users who want to take advantage of compiler optimizations.个
一般而言,您必须非常小心地应用它们.该程序格式错误,如果将它们应用于没有断言属性的函数,则不需要进行诊断.我们鼓励编译器检测这些属性的误用,但这不是必需的.
printf
既不是strlen
和memcmp
可以是[[未排序]]
memcpy
可以是[[可重现]]
memmove
也不可能,因为对于重叠的内存区域来说,它不是idempotentfabs
可以是[[未排序]]
sqrt
也不能,因为它修改了浮点环境并可能设置errno