restrict限定符能否提示编译器外部函数不修改内存及相关优化问题
restrict关键字的优化问题 背景案例
首先看无restrict的基础场景:
int f(int *p); static inline int g0(int *p) { *p=0; f(NULL); // 可能修改*p return *p==0; } int caller0(int *q) { return g0(q); }
这里编译器必须重新读取*p后再做比较,因为p可能与f可访问的内存产生别名。
再看带restrict的函数参数场景:
static inline int g1(int *restrict p) { *p=0; f(NULL); // 无法修改*p return *p==0; } int caller1(int *q) { return g1(q); }
借助restrict,编译器可推断f修改*p是非法的,因此可直接返回1,省去读取和比较操作,Clang 17会执行此优化。
但手动内联的等价代码中,Clang并未优化:
int caller2(int *q) { { int *restrict p=q; *p=0; f(NULL); return *p==0; } }
核心问题
- 编译器是否允许优化
caller2? - 若允许优化
caller2,是否也允许优化以下实际场景中的caller3?
int caller3(int *q) { ... // 多行代码 { int *restrict p=q; *p=0; ... // 执行有用操作 f(q); if (*p==0) { ... } // 执行有用操作 } ... // 多行代码 }
- 以下标准解读是否正确?
根据标准解读,
q并非基于p,且*p通过restrict限定指针修改,因此编译器可认为f不会修改*p(甚至f不允许读取*q,编译器可将*p=0重排至f调用之后,不过f可合法读写*(q+1))。若正确,说明可在函数内部灵活使用restrict向编译器提供别名相关提示。
问题解答
1. 编译器是否允许优化caller2?
完全允许。根据C标准对restrict的定义,restrict限定的指针p是其指向对象的唯一合法访问路径(在p的有效作用域内)。在caller2的代码块中,p被声明为restrict,*p被赋值为0后,f(NULL)没有任何合法途径访问*p——f的参数是NULL,无法关联到p指向的对象。因此编译器完全有权推断*p的值在f调用后仍为0,直接返回1,省去读取和比较操作。Clang当前未做此优化仅为实现层面的遗漏,而非标准不允许。
2. 是否允许优化caller3?
同样允许。虽然f的参数是q,但q并非从restrict指针p派生的指针。根据restrict规则,在p的作用域内,所有对*p的修改都必须通过p进行;若f通过q修改*p,属于未定义行为。编译器可假设这种未定义行为不会发生,即f不会通过q修改*p,进而优化掉if (*p==0)的读取操作,直接认定条件为真。
3. 标准解读的正确性
你的解读完全正确:
q不是p的派生指针,f通过q访问*p违反restrict约束,属于未定义行为,编译器可忽略这种可能性;- 编译器不仅可以判定
f不会修改*p,还可基于restrict的别名约束进行指令重排(例如将*p=0移到f调用之后,只要不影响程序可观察行为); f对*(q+1)的读写合法,因为这不属于p指向的对象,restrict仅约束p指向的单个对象(或连续数组起始),不影响相邻内存。
这说明在函数内部使用局部restrict指针是有效的,能够向编译器提供精确的别名提示,帮助生成更高效的代码。
内容的提问来源于stack exchange,提问作者log65536

