我正在考虑非标准
__restrict
关键字在 C 和 C++ 中的实用性,以及如何通过仔细声明(不相交)值对象来模拟其效果。
限制通常通过可能或可能不重叠内存地址的间接内存访问来解释:
int foo(Pointer1 a, Pointer2 b) { // adding non-standard `restrict` keyword might hint that dreaded_function_call is never called
*a = 5;
*b = 6;
if(*a == *b) dreaded_function_call(); // in isolation this function may or may not be called
}
如果编译器可以证明
a
和b
不重叠,那么dreaded_function_call()
永远不会在编译中被调用或引用。
这正是我在本示例中实现的目标,至少使用 GCC,
dreaded_function_call
甚至没有出现在生成的机器代码中。
#include<vector>
template<class It> void modify_v(It);
void dreaded_function_call();
template<class It1, class It2>
void foo(It1 a, It2 b) {
*a = 5;
*b = 6;
if(*a == *b) dreaded_function_call();
}
int main() {
std::vector<int> v1(10); modify_v(v1.begin());
std::vector<int> v2(10); modify_v(v2.begin());
foo(v1.begin() + 5, v2.begin() + 5);
}
但是,如果我稍微更改代码以通过单独的函数调用生成向量本身,我就会失去这种优化。 我可以观察到这一点,因为生成的代码将分支并仍然考虑调用
dreaded_function_call
的可能性。
std::vector<int> make_v();
...
int main() {
std::vector<int> v1 = make_v();
std::vector<int> v2 = make_v();
foo(v1.begin() + 5, v2.begin() + 5);
}
这是怎么回事?
make_v
通过副本返回,因此 v1
和 v2
应该是不相交的,就像上面的情况一样。
然而编译器并没有做同样的优化。
这是否只是错过了优化机会,或者说这种优化完全无效?
这里是用 GCC 说明这两种情况的编译代码:https://godbolt.org/z/WKKzs1edc
(Clang 给出相同的行为。)
每个
std::vector<int>
都持有一个指针,该指针指向保存元素的实际内存区域。
从标准中的库规范中我们知道,两个
std::vector<int>
对象不可能引用相同的元素对象或相同的存储。
但是,从实际的核心语言角度来看,没有什么可以阻止您编写一个
make_v
函数,该函数始终返回内部指针初始化为相同值的 std::vector<int>
对象。特别是,即使没有公共成员函数来实现这一点,也总是可以使用技巧来直接访问私有数据成员。
当然,您尝试执行此操作的任何方式都会在库规则下产生未定义的行为,但编译器(有一些较小的例外)不知道或考虑库规范,而是仅使用标准库代码就像任何其他代码一样。
在第一个示例中,编译器可以通过内联看到实际的
operator new
调用。对 operator new
的两次调用必须始终生成指向不相交内存的指针,而不会干扰 operator delete
调用。这是编译器优化器内置的知识。