虽然有许多示例[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
)时删除了提取负载。但是,我不明白为什么接下来的两个修改在以下情况下不会删除最终加载:
我更新了函数的定义(restrict
是c->i
的传递?):
int bar(container_s * restrict c, int *i) { ... }
我更新结构如下(为什么编译器不能推断不需要额外的负载?):
typedef struct container_s {
int x;
int * restrict i;
} container_s;
int bar(container_s *c, int *i) { ... }
答案 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)
说到这个问题。
在此代码变体中,restrict
是左值,其地址基于*tmp
- 限定指针restrict
。它指定的对象是通过函数范围内的左值访问的,并且也在该范围内修改(通过c
,因此编译器当然可以看到它)。 *tmp
的地址不基于*i
,因此编译器可以自由地假设c
没有别名*i
,就像原始问题一样。
这种情况不同。虽然允许对struct成员进行限制 - 限制,但*tmp
仅在符合普通标识符(C2011,6.7.3.1/1)时才有效,而结构成员名称不是(C2011) ,6.2.3)。在这种情况下,restrict
无效,为了确保符合规定的行为,编译器必须考虑restrict
和c->i
(和*i
)是别名的可能性。< / p>