C restrict 限定符是否传递?
Is the C restrict qualifier transitive?
虽然有许多示例 [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
) 时删除了提取负载。但是,我不明白为什么接下来的两个修改在以下情况下没有删除最终负载:
我将函数的定义更新为:
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) { ... }
"would this new header fix that load?", --> 否。restrict
引用 i
,并访问其字段:
... requires that all accesses to that object use, directly or indirectly, the value of that particular pointer... 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
我无法理解这里如何应用传递性,但我可以谈谈你的例子。
Does the compiler need an extra load
instruction for the last access of tmp
(the returned value) because it cannot infer that *i
and *tmp
do not alias?
编译器确实无法安全地推断出 *i
和 *tmp
不会在您的原始代码中使用别名,正如您恰当地证明的那样。这并不意味着编译器特别需要发出 *
运算符的抽象机器语义隐含的 load
指令,但它确实需要注意处理别名问题 不知何故.
If so, would [restrict-qualifying parameter i
] fix that load?
在函数定义中向参数i
添加restrict
限定条件,对程序的行为提出以下附加要求(源自C2011,6.7.3.1/4的文本):在 bar()
的每次执行期间,因为 i
是(平凡地) 基于 i
,并且 *i
用于访问对象它指定,并且指定的对象在 bar()
的执行期间被修改(至少通过 *i
),用于访问由 *i
指定的对象的每个其他左值也应基于其地址在 i
.
*tmp
被访问,其地址 tmp
不是基于 i
。因此,如果 i == tmp
(即,如果在某些调用中 i == c->i
),则程序无法符合。在这种情况下,它的行为是未定义的。编译器可以自由地发出假定程序符合的代码,因此特别是,在 restrict
限定的情况下,它可以发出假定语句
*i = 4;
不修改*tmp
,即声明
*tmp = 5;
不修改*i
。事实上,这似乎与 restrict
的定义和表达的意图一致,即编译器可以自由地精确地做出这些假设。
特别是,如果编译器选择通过执行 *tmp
的可能冗余加载来处理原始代码中出现别名的可能性,那么在符合 restrict
的版本中它可能会选择通过省略 load
来优化。但是,生成的机器代码决 不需要 在两种情况之间有任何区别。也就是说,一般来说,您不能依赖编译器来利用它可用的所有优化。
更新:
后续问题询问为什么clang
在特定情况下不执行特定优化。首先,必须重申,C 编译器不负责对给定源代码执行任何可能的特定优化,除非它们自己记录。因此,人们通常不能从未执行给定优化的事实中得出任何结论,而且询问为什么未执行给定优化也很少有用。
关于您所能做的——我正在从这个角度解释这些问题——是问所讨论的优化是否是符合标准的编译器可以拥有的 执行。在这种情况下,标准强调通过采取不寻常的步骤来澄清 restrict
不会对实现施加优化义务:
A translator is free to ignore any or all aliasing implications of uses of restrict
.
(C2011, 6.7.3.1/6)
话虽如此,继续提问。
在此代码变体中,*tmp
是一个左值,其地址基于 restrict
限定指针 c
。它指定的对象在函数范围内通过左值访问,并在该范围内修改(通过 *tmp
,因此编译器当然可以看到它)。 *i
的地址不是基于 c
,因此编译器可以自由假设 *i
不会别名 *tmp
,就像在原始问题中一样。
这种情况不同。尽管允许限制限定结构成员,但 restrict
只有在限定 普通标识符 (C2011, 6.7.3.1/1) 时才有效,其中结构成员名称是不是(C2011,6.2.3)。在这种情况下,restrict
没有效果,并且为了确保符合行为,编译器必须考虑 c->i
和 *i
(以及 *tmp
)是别名的可能性。
虽然有许多示例 [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
) 时删除了提取负载。但是,我不明白为什么接下来的两个修改在以下情况下没有删除最终负载:
我将函数的定义更新为:
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) { ... }
"would this new header fix that load?", --> 否。restrict
引用 i
,并访问其字段:
... requires that all accesses to that object use, directly or indirectly, the value of that particular pointer... 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
我无法理解这里如何应用传递性,但我可以谈谈你的例子。
Does the compiler need an extra
load
instruction for the last access oftmp
(the returned value) because it cannot infer that*i
and*tmp
do not alias?
编译器确实无法安全地推断出 *i
和 *tmp
不会在您的原始代码中使用别名,正如您恰当地证明的那样。这并不意味着编译器特别需要发出 *
运算符的抽象机器语义隐含的 load
指令,但它确实需要注意处理别名问题 不知何故.
If so, would [restrict-qualifying parameter
i
] fix that load?
在函数定义中向参数i
添加restrict
限定条件,对程序的行为提出以下附加要求(源自C2011,6.7.3.1/4的文本):在 bar()
的每次执行期间,因为 i
是(平凡地) 基于 i
,并且 *i
用于访问对象它指定,并且指定的对象在 bar()
的执行期间被修改(至少通过 *i
),用于访问由 *i
指定的对象的每个其他左值也应基于其地址在 i
.
*tmp
被访问,其地址 tmp
不是基于 i
。因此,如果 i == tmp
(即,如果在某些调用中 i == c->i
),则程序无法符合。在这种情况下,它的行为是未定义的。编译器可以自由地发出假定程序符合的代码,因此特别是,在 restrict
限定的情况下,它可以发出假定语句
*i = 4;
不修改*tmp
,即声明
*tmp = 5;
不修改*i
。事实上,这似乎与 restrict
的定义和表达的意图一致,即编译器可以自由地精确地做出这些假设。
特别是,如果编译器选择通过执行 *tmp
的可能冗余加载来处理原始代码中出现别名的可能性,那么在符合 restrict
的版本中它可能会选择通过省略 load
来优化。但是,生成的机器代码决 不需要 在两种情况之间有任何区别。也就是说,一般来说,您不能依赖编译器来利用它可用的所有优化。
更新:
后续问题询问为什么clang
在特定情况下不执行特定优化。首先,必须重申,C 编译器不负责对给定源代码执行任何可能的特定优化,除非它们自己记录。因此,人们通常不能从未执行给定优化的事实中得出任何结论,而且询问为什么未执行给定优化也很少有用。
关于您所能做的——我正在从这个角度解释这些问题——是问所讨论的优化是否是符合标准的编译器可以拥有的 执行。在这种情况下,标准强调通过采取不寻常的步骤来澄清 restrict
不会对实现施加优化义务:
A translator is free to ignore any or all aliasing implications of uses of
restrict
.
(C2011, 6.7.3.1/6)
话虽如此,继续提问。
在此代码变体中,
*tmp
是一个左值,其地址基于restrict
限定指针c
。它指定的对象在函数范围内通过左值访问,并在该范围内修改(通过*tmp
,因此编译器当然可以看到它)。*i
的地址不是基于c
,因此编译器可以自由假设*i
不会别名*tmp
,就像在原始问题中一样。这种情况不同。尽管允许限制限定结构成员,但
restrict
只有在限定 普通标识符 (C2011, 6.7.3.1/1) 时才有效,其中结构成员名称是不是(C2011,6.2.3)。在这种情况下,restrict
没有效果,并且为了确保符合行为,编译器必须考虑c->i
和*i
(以及*tmp
)是别名的可能性。