2

虽然有很多例子[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) { ... }

编辑

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

  1. 我将函数的定义(是否restrict考虑过传递c->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) { ... }
    
4

2 回答 2

2

“这个新的标头会修复该负载吗?”,--> 否。restrict引用i并访问其字段:

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

但当它们反过来用于访问其他数据时,不会扩展以限定这些字段。

#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
于 2016-12-12T17:30:15.947 回答
2

我无法看到传递性如何应用在这里,但我可以谈谈你的例子。

编译器是否需要额外的load指令来最后访问tmp(返回值),因为它无法推断*i并且*tmp没有别名?

正如您已经恰当地证明的那样,编译器确实无法安全地推断出这一点,*i并且*tmp不会在您的原始代码中使用别名。这并不意味着编译器特别需要发出运算符load的抽象机器语义所暗示的指令*,但它确实需要注意以某种方式处理别名问题。


如果是这样,[restrict-qualifying parameter i] 会修复该负载吗?

restrict在函数定义中为参数添加-qualificationi对程序的行为提出了以下附加要求(源自 C2011,6.7.3.1/4 的文本):在每次执行期间bar(),因为i是(通常)基于 i,并且*i是用于访问它指定的对象,并且该指定对象在执行期间被修改bar()*i至少通过),用于访问指定对象的每个其他左值*i也应具有基于i.

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

  *i = 4;

不修改*tmp,那语句

  *tmp = 5;

不修改*irestrict事实上,编译器可以自由地做出这些假设的定义和表达意图似乎是一致的。

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

更新

后续问题询问为什么clang在特定情况下不执行特定优化。首先,必须重申 C 编译器不负责执行任何可能对给定源代码进行的特定优化,除非它们自己记录。因此,人们通常无法从没有执行给定优化这一事实中得出任何结论,并且询问为什么没有执行给定优化也很少有用。

尽你所能——我正在从这个角度解释这些问题——是询问所讨论的优化是否是符合标准的编译器可以执行的优化。在这种情况下,该标准强调,通过采取不寻常的澄清步骤,restrict对实现没有任何优化义务:

翻译者可以自由地忽略任何或所有使用restrict.

(C2011, 6.7.3.1/6)

话虽如此,关于问题。

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

  2. 本案不同。尽管允许对结构成员进行限制限定,restrict但仅当它限定普通标识符(C2011,6.7.3.1/1)时才有效,而结构成员名称不是(C2011,6.2.3)。在这种情况下,restrict没有效果,并且为了确保符合行为,编译器必须考虑c->i*i(和*tmp)是别名的可能性。

于 2016-12-12T19:16:48.920 回答