`const T * restrict`是否保证指向的对象不被修改?

时间:2014-10-13 18:14:47

标签: c const restrict-qualifier

请考虑以下代码:

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;

  1. 是否允许编译器为foo_r执行此优化(替换doesnt_modify的最后一行)?如果没有,是否有(可移植的,如果可能的话)方式告诉编译器doesnt_modify不修改其参数指向的内容?编译器是否有理解并使用?

  2. 是否有任何函数都有UB(提供restrict不会修改其参数的指针)?

  3. 为什么我认为,restrict可以在这里提供帮助(来自C11(n1570)6.7.3.1“B”的正式定义,p4 [emph.mine]):

    [在这种情况下,foo_rP的内部块,n_restrTconst intX,{我认为{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。

4 个答案:

答案 0 :(得分:9)

版本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成为&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派生的指针进行访问,事实函数的原型包含这样的限定符不会迫使其定义也这样做。

相关问题