请考虑以下代码:
void doesnt_modify(const int *);
int foo(int *n) {
*n = 42;
doesnt_modify(n);
return *n;
}
其中doesnt_modify
的定义对编译器不可见。因此,必须假定doesnt_modify
将对象n
更改为*n
并且必须在return
之前阅读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
- 在声明中将参数限定为gcc -std=c99 -O3 -S
是不够的,参见“Is top-level volatile
or restrict
significant [...]?”
使用42
(或具有相同选项的Clang)进行编译时,所有函数都编译为等效汇编,所有函数都从*n
重新读取return 42;
。
是否允许编译器为foo_r
执行此优化(替换doesnt_modify
的最后一行)?如果没有,是否有(可移植的,如果可能的话)方式告诉编译器doesnt_modify
不修改其参数指向的内容?编译器是否有理解并使用?
是否有任何函数都有UB(提供restrict
不会修改其参数的指针)?
为什么我认为,restrict
可以在这里提供帮助(来自C11(n1570)6.7.3.1“B
”的正式定义,p4 [emph.mine]):
[在这种情况下,foo_r
是P
的内部块,n_restr
是T
,const int
是X
,{我认为{1}}是由*n
表示的对象。]
在每次执行
B
期间,让L
成为&L
基于P
的左值。如果L
用于访问其指定的对象X
的值,并且X
也被修改(无论如何),则以下要求apply: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。
答案 0 :(得分:9)
版本1似乎由restrict
的正式定义明确规定(C11 6.7.3.1)。对于以下代码:
const int *restrict P = n;
doesnt_modify(P);
return *P;
6.7.3.1中使用的符号是:
P
*P
的类型const int
P
*P
是我们感兴趣的6.7.3.1/4(部分):
在每次执行
B
期间,让L
成为&L
基于P
的左值。如果L
用于访问其指定的对象X
的值,并且X
也被修改(通过任何方式),则以下要求适用: {{ 1}}不得为const限定 [...] 如果不满足这些要求,则行为未定义。
请注意T
是const限定的。因此,如果在此块期间以任何方式修改T
(包括在调用该块中的函数期间),则行为未定义。
因此,编译器可以进行优化,就好像X
没有修改doesnt_modify
。
版本2对于编译器来说有点困难。 6.7.6.3/15表示顶级限定符不符合原型兼容性 - 尽管它们不会被完全忽略。
所以虽然原型说:
X
可能仍然是函数体被声明为void doesnt_modify2(const int *restrict p);
,因此可能会修改void doesnt_modify2(const int *p)
。
我的结论是,当且仅当编译器能够看到*p
的定义并确认doesnt_modify2
在定义的参数列表中声明为p
时,它才能执行优化。
答案 1 :(得分:1)
通常,restrict
表示指针没有别名(即只有它或从它派生的指针可用于访问指向的对象)。
使用const
,这意味着无法通过格式良好的代码修改指向对象。
然而,没有什么可以阻止程序员使用显式类型转换来删除const
的规则。然后编译器(被程序员殴打成提交)将允许尝试修改指向对象而没有任何抱怨。严格来说,这会导致未定义的行为,因此允许任何可以想象的结果,包括 - 可能 - 修改指向的对象。
答案 2 :(得分:1)
如果没有,是否有(可移植的,如果可能的话)方式告诉编译器,dont_modify不会修改其参数指向的内容?
没有这种方式。
编译器优化器在涉及指针和引用函数参数时难以优化。因为该函数的实现可以抛弃constness编译器,假设T const*
与T*
一样糟糕。
因此,在您的示例中,在调用doesnt_modify(n)
之后,它必须从内存重新加载*n
。
见2013 Keynote: Chandler Carruth: Optimizing the Emergent Structures of C++。它也适用于C.
此处添加restrict
关键字不会改变上述内容。
答案 3 :(得分:0)
在指针类型参数上同时使用restrict
限定符和在其目标类型上同时使用const
限定符将邀请编译器假定在该变量的生存期内没有访问的存储区域。通过包含在其中的指针或从其派生的任何指针的指针对象,将在该指针的生存期内通过任何方式进行修改。通常,对于使用相关指针无法访问的存储区域,什么也没说。
const restrict
对整个对象有影响的唯一情况是使用绑定了static
的数组语法声明指针的情况。在那种情况下,仅在可以读取整个数组对象(不调用UB)的情况下定义行为。由于读取数组对象的在函数执行过程中发生更改的任何部分都会调用UB,因此将允许代码假定数组的任何部分均不能以任何方式更改。
不幸的是,当编译器知道函数的实际定义时,其开头是:
void foo(int const thing[restrict static 1]);
将有权假定在函数执行过程中*thing
的任何部分都不会更改,即使该对象可能是一个对象,否则该函数可以通过不是从thing
派生的指针进行访问,事实函数的原型包含这样的限定符不会迫使其定义也这样做。