`const T * restrict`是否保证指向的对象不被修改?
请考虑以下代码:
void doesnt_modify(const int *); int foo(int *n) { *n = 42; doesnt_modify(n); return *n; }
其中, doesnt_modify
的定义对编译器不可见。 因此,它必须假设, doesnt_modify
将对象n
指向更改并且必须在return
之前读取*n
(最后一行不能被return 42;
替换return 42;
)。
假设, doesnt_modify
不会修改*n
。 我考虑了以下内容以允许优化:
int foo_r(int *n) { *n = 42; { /* New scope is important, I think. */ const int *restrict n_restr = n; doesnt_modify(n_restr); return *n_restr; } }
这样做的缺点是, doesnt_modify
的调用者必须告诉编译器*n
没有被修改,而不是函数本身可以通过其原型告诉编译器。 简单地将参数restrict
为在声明中将参数设置为doesnt_modify
是不够的,参见 “是顶级的volatile
还是restrict
重要的[…]?” 。
当使用gcc -std=c99 -O3 -S
(或具有相同选项的Clang)进行编译时,所有函数都被编译为等效汇编,所有函数都从*n
重新读取42
。
-
是否允许编译器为
foo_r
执行此优化(将最后一行替换为return 42;
)? 如果没有,是否有(可移植的,如果可能的话)告诉编译器的方法,doesnt_modify
不会修改其参数指向的内容? 编译器有没有理解和利用的方式? -
是否有任何函数都有UB(假设
doesnt_modify
不修改其参数的指针)?
为什么我认为, restrict
在这里可以提供帮助(来自C11(n1570)6.7.3.1“ restrict
正式定义”,p4 [emph.mine]):
[在这种情况下, B
是foo_r
的内部块, P
是n_restr
, T
是const int
,而X
是用*n
表示的对象,我想。
在每次执行
B
期间,让L
为具有基于P
&L
的任何左值。 如果L
用于访问它指定的对象X
的值,并且X
也被修改(通过任何方式) ,则以下要求适用:T
不应是const限定的 。 […]
$ clang --version Ubuntu clang version 3.5.0-4ubuntu2 (tags/RELEASE_350/final) (based on LLVM 3.5.0) Target: x86_64-pc-linux-gnu
在x86 32位目标上,Gcc版本为4.9.2。
版本1似乎由restrict
的正式定义明确规定(C11 6.7.3.1)。 对于以下代码:
const int *restrict P = n; doesnt_modify(P); return *P;
6.7.3.1中使用的符号是:
- B – 那段代码
- P – 变量
P
- T –
*P
的类型,它是const int
- X –
P
指向的(非const)int - L – 左值
*P
是我们感兴趣的
6.7.3.1/4(部分):
在每次执行
B
期间,让L
为具有基于P
&L
的任何左值。 如果L
用于访问它指定的对象X
的值,并且X
也被修改(通过任何方式),则以下要求适用:T
不应是const限定的 […]如果这些要求不是遇到了,那么行为是未定义的。
请注意, T
是const限定的。 因此,如果在此块期间以任何方式修改X
(包括在调用该块中的函数期间),则行为未定义。
因此,编译器可以进行优化,就好像doesnt_modify
没有修改X
。
版本2对于编译器来说有点困难。 6.7.6.3/15表示顶级限定符不被认为是原型兼容性 – 尽管它们并未完全被忽略。
虽然原型说:
void doesnt_modify2(const int *restrict p);
它仍然可能是函数体被声明为void doesnt_modify2(const int *p)
,因此可能会修改*p
。
我的结论是,当且仅当编译器能够看到doesnt_modify2
的定义并确认p
在定义的参数列表中被声明为restrict
,它才能够执行优化。
通常, restrict
意味着指针没有别名(即只有它或从它派生的指针可用于访问指向对象)。
使用const
,这意味着无法通过格式良好的代码修改指向对象。
但是,没有什么可以阻止程序员使用显式类型转换来删除const
来破坏规则。 然后编译器(被程序员殴打成提交)将允许尝试修改指向的对象而没有任何抱怨。 严格来说,这会导致未定义的行为,因此允许任何可以想象的结果,包括 – 可能 – 修改指向的对象。
如果没有,是否有(可移植的,如果可能的话)告诉编译器的方法,dont_modify不会修改其参数指向的内容?
没有这种方式。
当涉及指针和引用函数参数时,编译器优化器难以优化。 因为该函数的实现可以抛弃T const*
编译器,假设T const*
和T*
一样糟糕。
因此,在您的示例中,在调用doesnt_modify(n)
它必须从内存重新加载*n
。
参见2013年主题演讲:Chandler Carruth:优化C ++的紧急结构 。 它也适用于C.
在此处添加restrict
关键字不会改变上述内容。
在指针类型参数和目标类型上的const
限定符上同时使用restrict
限定符将邀请编译器假定在指针对象的生命周期内通过其中包含的指针或任何派生指针访问的存储区域从它开始,将在指针的生命周期内通过任何方式进行修改。 它通常没有说明任何关于使用指针无法访问的存储区域。
const restrict
对整个对象有影响的唯一情况是使用带有static
边界的数组语法声明指针的情况。 在这种情况下,只能在可以读取整个数组对象的情况下定义行为(不调用UB)。 由于读取在函数执行期间发生更改的数组对象的任何部分将调用UB,因此可以允许代码假定数组的任何部分都不能以任何方式进行更改。
不幸的是,虽然知道函数的实际定义的编译器以:
void foo(int const thing[restrict static 1]);
有权假设在函数执行期间不会改变*thing
任何部分,即使该对象可能是函数可以通过不是从thing
派生的指针访问的对象,函数的原型包含这样的限定符的事实也不会强迫它的定义也是如此。