假设我有一个函数,它接受一些指针参数 - 一些非常量,可以通过它们写入,一些常量,只能通过它们读取。示例:
void f(int * a, int const *b);
还假设该函数不会以其他方式写入内存(即不使用全局变量,固定地址,将 const 指针重铸为非常量以及诸如此类的技巧)。
现在,是否足以(按照 C 语言标准)实现restrict
内所有读取的好处f()
,而仅restrict
输出参数?即在示例中,限制a
但不限制b
?
一个简单的测试(GodBolt)表明这种限制应该足够了。此来源:
int f(int * restrict a, int const * b) {
a[0] += b[0];
return a[0] + b[0];
}
int all_restricted(int * restrict a, int const * restrict b) {
a[0] += b[0];
return a[0] + b[0];
}
int unrestricted(int * a, int const * b) {
a[0] += b[0];
return a[0] + b[0];
}
为 x86_64 生成相同的目标代码:
f:
mov eax, DWORD PTR [rsi]
mov edx, DWORD PTR [rdi]
add edx, eax
mov DWORD PTR [rdi], edx
add eax, edx
ret
all_restricted:
mov eax, DWORD PTR [rsi]
mov edx, DWORD PTR [rdi]
add edx, eax
mov DWORD PTR [rdi], edx
add eax, edx
ret
unrestricted:
mov eax, DWORD PTR [rsi]
add eax, DWORD PTR [rdi]
mov DWORD PTR [rdi], eax
add eax, DWORD PTR [rsi]
ret
但这并非普遍的保证。
不,这还不够。
这个假设是不够的。编译器还需要看到函数没有以其他方式写入指针指向的内存(包括可以通过指针访问的任何内存,例如
b[18]
)。例如,如果调用bar(b);
,并且编译器看不到bar
,那么它就无法知道指向的内存b
在执行期间是否被修改f
,即使没有被修改。给出这个额外的前提,即编译器可以看到没有对通过指向的任何内存进行修改,那么对于优化来说是否用和/或声明
b
并不重要:编译器了解有关内存的所有信息,告诉它更多信息都不会添加信息。b
const
restrict
然而,代码通常不满足这个前提。(即使满足,对程序员来说,确定这一点也可能很麻烦。)因此,让我们考虑一种没有附加前提的情况:
当
bar
被调用时,编译器不知道 是否*b
被修改。即使此函数没有传递b
给bar
,bar
也可能访问某个 外部对象,*b
或者具有指向 的指针*b
,因此bar
可以更改 对象*b
。因此,编译器必须*b
从内存中重新加载第二个printf
。相反,如果我们声明函数
void f(int * restrict a, int const * restrict b)
,则restrict
断言如果*b
在 的执行过程中被修改f
(包括间接地,在 内bar
),那么对它的每次访问都将通过b
(直接,如*b
,或间接地,如通过 中可见地复制或计算的指针b
)进行。由于编译器可以看到bar
不接收b
,它知道bar
不包含任何*b
基于 的访问b
,因此它可以假设bar
不改变*b
。因此,即使所有其他参数也已声明,
restrict
向参数添加指向限定类型的指针仍可以实现一些优化。const
restrict
如果您尝试修改指针指向的值,则显示的关键字
const
将导致警告或错误,但它不一定阻止您修改该值,并且肯定不会阻止其他人修改该值。 因此,编译器可能必须假设可能发生此类修改。例如,如果调用在不同源文件中定义的某个函数,编译器将不知道该函数的作用,因此它必须假定该函数可能以某种方式访问该指针指向的值,并可能对其进行修改。
此外,即使您修改了非常量指针指向的值,该非常量指针也可能指向与常量指针相同的值。(如
f( &x, &x );
)如果没有restrict
常量指针,编译器必须假设这也是一种可能性。因此,即使
const
参数也可以从关键字中受益restrict
,因为它承诺该指针指向的内存不会被任何人修改。本质上,您将承诺永远不会做类似的事情f( &x, &x );
。restrict
是片面的。它允许编译器“基于”restrict
-qualified 指针对通过左值进行的对象访问做出假设,而不依赖于任何其他指针是否也是restrict
-qualified。具体来说,如果你
restrict
使用参数,a
那么无论你是否也restrict
使用参数b
,你都允许编译器假设在任何给定的执行过程中f()
,L
是任何左值,其地址“基于”a
,并且L
用于访问其指定的对象(读取或写入),并且a
,但不一定通过L
特定方式进行。b
但不基于的左值来访问a
。)C17 第 6.7.3.1 节对此进行了更详细的说明,尽管该文本既复杂又有点令人担忧。
因此,在您描述的情况下,
restrict
ing 仅a
使编译器有权假设通过派生自的指针进行的读取b
永远不会观察到通过派生自的指针进行的写入a
。 当然,它是否真的会生成不同的代码是一个完全不同的问题。