2个`restrict`修饰的指针可以比较相等吗?

huangapple go评论76阅读模式
英文:

Can 2 `restrict`-ed pointers compare equal?

问题

int foo(void *restrict ptr1, void *restrict ptr2)
{
  if (ptr1 == ptr2) {
    return 1234;
  } else {
    return 4321;
  }
}

restrict 暗示了由指针指向的内存不会被任何其他指针别名。鉴于这一点,ptr1ptr2 不能指向相同的区域,因此比较是显而易见的,foo() 在所有情况下都应返回 4321

然而,clanggcc 并不这样看(https://godbolt.org/z/fvPd4a1vd)。这是一个被忽略的优化还是有其他原因?

英文:
int foo(void *restrict ptr1, void *restrict ptr2)
{
  if (ptr1 == ptr2) {
    return 1234;
  } else {
    return 4321;
  }
}

restrict implies the the memory pointed to by a pointer is not aliased by any other pointer. Given that, then ptr1 and ptr2 cannot point to the same region, so the comparison is tautological and foo() should return 4321 in all cases.

Yet clang and gcc do not see it this way (https://godbolt.org/z/fvPd4a1vd). Is this a missed optimization or is there another reason?

答案1

得分: 11

根据C99 Draft N1256第6.7.3节的规定:

> 通过一个限定为restrict的指针访问的对象与该指针有特殊关联。这种关联在下面的6.7.3.1中定义,要求对该对象的所有访问都直接或间接使用该特定指针的值。117) 限定符的预期使用(类似于寄存器存储类)是为了促进优化,并且从构成符合程序的所有预处理翻译单元中删除限定符的所有实例不会改变其含义(即,可观察行为)。

将指针声明为_restrict_可以确保编译器不会有其他指针修改受限指针指向的内存位置。

然而,您仍然可以有两个限定指针指向相同的内存区域。

再次提到C99 Draft N1256中第6.7.3.1的EXAMPLE 3:

void h(int n, int * restrict p, int * restrict q, int * restrict r)
{
    int i;
    for (i = 0; i < n; i++)
        p[i] = q[i] + r[i];
}

这是标准中的注释:

> [h的函数参数声明]说明了如何通过两个限定指针来别名未修改的对象。特别是,如果a和b是不相交的数组,调用形式h(100, a, b, b)具有定义行为,因为在函数h中未修改数组b。

所以,对于您的问题的答案是:不,这不是GCC或Clang错过的优化。

英文:

According to the section 6.7.3 of the C99 Draft N1256:

> An object that is accessed through a restrict-qualified pointer has a special association
with that pointer. This association, defined in 6.7.3.1 below, requires that all accesses to
that object use, directly or indirectly, the value of that particular pointer.117) The intended
use of the restrict qualifier (like the register storage class) is to promote
optimization, and deleting all instances of the qualifier from all preprocessing translation
units composing a conforming program does not change its meaning (i.e., observable
behavior).

Declaring a pointer as restrict ensures the compiler that no other pointer will modify the memory location pointed by the restricted pointer.

However, You can still have two restricted pointers pointing to the same memory region.

Again, the EXAMPLE 3 in 6.7.3.1 of the C99 Draft N1256 comes in handy:

void h(int n, int * restrict p, int * restrict q, int * restrict r)
{
    int i;
    for (i = 0; i < n; i++)
        p[i] = q[i] + r[i];
}

This is the comment from the standard:

> [The function parameter declarations of h] illustrate how an unmodified object can be aliased through two restricted pointers. In particular, if a and b are disjoint arrays, a call of the form h(100, a, b, b) has defined behavior, because array b is not modified within function h.

So, the answer to your question is: No, this is not a missed optimization by GCC or Clang.


As pointed out by Peter in the comments there could be a missing optimization based on the following undefined behavior related to restricted pointers:

> An object which has been modified is accessed through a restrict-qualified pointer to
a const-qualified type, or through a restrict-qualified pointer and another pointer that
are not both based on the same object (6.7.3.1).

Essentially, if two restricted pointers are not used to modify the data they are pointing to (which is the case of your function foo and the function h in my answer), no undefined behavior would occur even if they are pointing to the same memory region. Therefore, the compiler cannot say anything about their values at compilation time: they could be equal as well as different.

However, a different situation is the following:

int compare_pointers(int *restrict ptr1, int *restrict ptr2)
{
    *ptr1 = 0;
    *ptr2 = 1;
    if (ptr1 != ptr2) {
        return 1234;
    } else {
        return 4321;
    }
}

Since both pointers are used to modify data they must be associated to different memory region otherwise an undefined behavior would occur (this is a consequence of the restrict keyword) and so an optimization could remove the subsequent comparison and return 1234 instead. However, both GCC and Clang don't perform such optimization as shown by Peter's comment.

答案2

得分: 9

没有,这在这种情况下不是一个被忽略的优化机会,因为指向的对象没有被修改,也没有通过指针访问。

6.7.3.1 restrict的正式定义

如果L被用于访问它所指的对象X的值,而X也被修改了(以任何方式),则需要满足以下要求:T不能被const修饰。用于访问X值的每个其他左值也应该基于P的地址。

的确,在这里restrict是无用的,因为两个指针都是const

将代码更改为:

int foo(int *restrict ptr1, int *restrict ptr2)
{
  int i = *ptr1;
  ++*ptr2;
  *ptr1 = i + 1;
  if (ptr1 == ptr2) {
    return 1234;
  } else {
    return 4321;
  }
}

我们可以看到,这里gcc知道restrict,但在后一种情况下未能应用优化机会,而clang根本不知道restrict

英文:

No, this is not a missed optimization opportunity in this case, because a pointed-to object is not modified and is not accessed through either pointer.

6.7.3.1 Formal definition of restrict:
> If L is used to access the
value of the object X that it designates, and X is also modified (by any means), then the following
requirements apply: T shall not be const-qualified. Every other lvalue used to access the value of
X shall also have its address based on P.

Indeed, restrict is useless here, since both pointers are to const.

Changing the code to:

int foo(int *restrict ptr1, int *restrict ptr2)
{
  int i = *ptr1;
  ++*ptr2;
  *ptr1 = i + 1;
  if (ptr1 == ptr2) {
    return 1234;
  } else {
    return 4321;
  }
}

we can see that gcc is aware of restrict, but fails to apply the optimization opportunity in this latter case, whereas clang is not aware of restrict at all.

huangapple
  • 本文由 发表于 2023年7月20日 21:54:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/76730618.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定