C restrict限定符是否可传递?

时间:2016-12-12 16:49:28

标签: c compiler-optimization restrict-qualifier

虽然有许多示例[1] [2] [3]解决了restrict关键字的工作原理,但我并不完全确定限制关系是否可以传递给指针它可以指向。例如,以下代码声明了一个包含整数和指向整数的指针的结构。

typedef struct container_s {
  int x;
  int *i;
} container_s;


int bar(container_s *c, int *i) {
  int* tmp = c->i;
  *tmp = 5;      
  *i = 4;
  return *tmp;
}

int main(){
  return 0;
}

编译器是否需要额外的load指令才能上次访问tmp(返回值),因为它无法推断出*i*tmp没有别名?

如果是这样,这个新定义会修复那个加载吗?

int bar(container_s *c, int* restrict i) { ... }

修改

这种情况int bar(container_s *c, int * restrict i) { ... }在我生成LLVM IR(clang -S -O3 -emit-llvm)时删除了提取负载。但是,我不明白为什么接下来的两个修改在以下情况下不会删除最终加载:

  1. 我更新了函数的定义(restrictc->i的传递?):

    int bar(container_s * restrict c, int *i) { ... }
    
  2. 我更新结构如下(为什么编译器不能推断不需要额外的负载?):

    typedef struct container_s {
      int x;
      int * restrict i;
    } container_s;
    
    int bar(container_s *c, int *i) { ... }
    

2 个答案:

答案 0 :(得分:2)

“这个新标题会修复加载吗?”, - >不。restrict引用i,并访问其字段:

  

...要求对该对象的所有访问直接或间接使用该特定指针的值...C11§6.7.38

但是当这些字段依次用于访问其他数据时,不会扩展以限定这些字段。

#include<stdio.h>

typedef struct container_s {
  int x;
  int *i;
} container_s;


int bar(container_s * c, int* restrict  i) {
  int* tmp = c->i;
  *tmp = 5;
  *i = 4;
  return *tmp;
}

int main(void) {
  int i = 42;
  container_s s = { 1, &i };

  printf("%d\n", bar(&s, &i));
  printf("%d\n", i);
  printf("%d\n", *(s.i));
}

输出

4
4  
4

答案 1 :(得分:2)

我很难看到传递性如何适用于此,但我可以说你的例子。

  

编译器是否需要额外的load指令才能上次访问tmp(返回值),因为它无法推断出*i*tmp没有别名?

编译器确实无法安全地推断出*i*tmp在原始代码中没有别名,正如您已恰当地证明的那样。这并不意味着编译器特别需要发出load运算符的抽象机器语义隐含的*指令,但它确实需要注意处理别名问题以某种方式< / em>的

  

如果是这样,[限制合格参数i]会修复该加载吗?

在函数定义中向参数restrict添加i - 限定条件,对程序的行为(从C2011,6.7.3.1 / 4的文本中派生)提出以下附加要求: bar()的执行,因为i(基本上)基于 i,而*i用于访问它指定的对象,在执行bar()期间(至少通过*i)修改了指定对象,用于访问*i指定的对象的每个其他左值也应该具有基于{{1}的地址}。

访问了

i,其地址*tmp不基于tmp。因此,如果i(即,如果在某个调用中,i == tmp),则程序无法符合。在这种情况下,它的行为是不确定的。编译器可以自由地发出假定程序符合的代码,因此特别是在i == c->i - 合格的情况下,它可以发出代码,假定该语句都是

restrict

不会修改 *i = 4; ,而是声明

*tmp

不会修改 *tmp = 5; 。实际上,它似乎与*i的定义和表达意图一致,即编译器可以自由地做出这些假设。

特别是,如果编译器选择通过执行restrict的可能冗余加载来处理原始代码中别名的可能性,那么在*tmp - 限定版本中它可能会选择优化省略restrict。但是,生成的机器代码绝不是 required 在两种情况之间以任何方式存在差异。也就是说,通常,您不能依赖编译器来利用它可用的所有优化。

<强>更新

后续问题询问为什么load在特定情况下不执行特定优化。在其他任何事情之前,必须重申C编译器对执行给定源代码可能的任何特定优化没有任何责任,除非它们自己是文档。因此,人们通常无法从未执行给定优化的事实中得出任何结论,并且询问为什么没有执行给定优化很少有用。

关于你可以去的 - 我正在解释这个问题 - 就是询问所讨论的优化是否是一个符合标准的编译器可以执行的。在这种情况下,标准强调通过采取不寻常的步骤澄清clang没有对实施施加任何优化义务:

  

翻译人员可以自由地忽略使用restrict的任何或所有别名含义。

(C2011,6.7.3.1 / 6)

说到这个问题。

  1. 在此代码变体中,restrict是左值,其地址基于*tmp - 限定指针restrict。它指定的对象是通过函数范围内的左值访问的,并且也在该范围内修改(通过c,因此编译器当然可以看到它)。 *tmp的地址不基于*i,因此编译器可以自由地假设c没有别名*i,就像原始问题一样。

  2. 这种情况不同。虽然允许对struct成员进行限制 - 限制,但*tmp仅在符合普通标识符(C2011,6.7.3.1/1)时才有效,而结构成员名称不是(C2011) ,6.2.3)。在这种情况下,restrict无效,为了确保符合规定的行为,编译器必须考虑restrictc->i(和*i)是别名的可能性。< / p>