3

在C 语言标准的第 6.7.3.1 节中restrict,它说:

  1. 令 D 是一个普通标识符的声明,它提供了一种将对象 P 指定为指向类型 T 的限制限定指针的方法。

  2. ...

  3. 在下文中,如果(在对 E 求值之前执行 B 的某个序列点)修改 P 以指向它以前指向的数组对象的副本,则称指针表达式 E 基于对象 P会改变 E 的值。

我不明白这是在说什么 - 从字面上看:

  • 谁说 P 指向“数组对象的副本”?
  • 为什么P“以前”指向任何东西?也就是说,谁说我们改变了它的价值?
  • 假设 E 是本地范围的指针。为什么修改E 指针本身以外的任何指针表达式会“改变 E 的值”?它可能会改变 E 指向的值。正确的?

有人可以帮我解释那段文字以便更有意义吗?

(受此答案启发)

4

4 回答 4

1

谁说 P 指向“数组对象的副本”?

指针算术是根据指向数组元素的指针定义的(在 C 2018 6.5.6 8 和 9 中)。为此,单个对象被视为一个元素的数组。所以,只要我们有任何非空对象指针,在这个模型中,它就指向一个数组。

为什么P“以前”指向任何东西?也就是说,谁说我们改变了它的价值?

您引用的文字是说“要确定是否E基于P,让我们假设复制P指向的数组,然后将P指针分配给副本中的相应位置。” 因此,您引用的文字是说我们正在更改 的值P,然后我们将 的值E与此更改进行比较,而没有更改。

假设 E 是本地范围的指针。为什么修改 E 指针本身以外的任何指针表达式会“改变 E 的值”?它可能会改变 E 指向的值。正确的?

对象和值没有范围。标识符有范围。但是让我们考虑一个具有块范围的标识符:

// P is a pointer into A.
// S is the size of A.
// A is the start of an array not contained in any other array.
void foo(char *P, size_t S, char *A)
{
    void *E = P+2;
}

为了说明,假设P值为 0x1004 并且A是 0x1000。是E基于P? 那么,鉴于上述情况,E是0x1006。假设我们在定义 之前考虑这段代码E

    char *N = malloc(S);
    memcpy(N, A, S);
    P = P - A + N;

假设malloc返回 0x2000。的价值E是多少?它将是 0x2006。这与 0x1006 不同。因此E是基于P.

另一方面,考虑一下:

void foo(char **P, size_t S, char **A)
{
    #if OnOrOff
        char *N = malloc(S);
        memcpy(N, A, S);
        P = P - A + N;
    #endif
    char **E = P[3];
}

现在,值会E根据OnOrOff真假而改变吗?A不,在任何一种情况下,它都会直接或从副本接收作为 的引用元素的值。P可能指向AN不影响 的值的事实E。所以这E不是基于P.

于 2019-02-25T23:50:21.707 回答
1

“基于”的定义旨在定义指针之间的传递关系,但它的实际措辞会产生一个不可行的定义,据我所知,它与任何实际的编译器行为都不匹配。

传递应用以下规则会更简单(这就是编译器似乎所做的):如果*p是类型的指针T*,则以下指针是“基于”的p

  • p+(intExpr)或者p-(intExpr)
  • (otherType*)p
  • &*p
  • &p->structMemberofNonArrayType或者&p->unionMemberofNonArrayType
  • p->structMemberofArrayType或者p->unionMemberofArrayType
  • &p[intExpr]
  • 基于上述任何一项的任何指针

我不认为标准真的很清楚(someType*)someIntegerFunction((uintptr_t)p),我也不认为编译器作者很清楚。

请注意,除了涉及强制转换的表达式之外,任何通过上述表达式q派生的任何表达式,和之间的差异都将独立于 持有的地址。puintptr_t(char*)p(char*)qp

顺便说一下,这是一个有问题的极端案例的示例:

int test1(int * restrict p1, int * restrict p2, int n)
{
    int *restrict p3 = p1+n;
    // How would p4 and p5 be affected if p3 were replaced
    // with a pointer to a copy here?
    int *p4 = p3;
    if (p3 != p1) p4=p1;
    int *p5 = p2 + (p3 == p1);
    *p3 = 1;
    *p5 = 2;
    return *p4;
}

使用传递方式形成基于另一个的指针,如果n为零,p4显然将基于p3. 然而, Pointerp5不会派生自p3,因为没有“基于”步骤的序列可以派生出它的值。

尝试n==0通过用指向数组副本的指针替换来将标准中给出的规则应用于案例p3不会影响 的值p4,但会影响 的值p5。这意味着它p4不是基于p3,而是基于p5某种方式。

我认为这样的结果是荒谬的,我认为标准的作者也是如此,但它遵循标准中给出的规则,如措辞。

于 2019-02-26T23:25:23.290 回答
0

在阅读了几条评论以及@EricPostpischil 的回答之后,我试图综合我认为更清晰的内容,虽然更长一点,措辞来澄清事情并回答提出的问题。

原文:

在下文中,如果(在对 E 求值之前执行 B 的某个序列点)修改 P 以指向它以前指向的数组对象的副本,则称指针表达式 E 基于对象 P会改变 E 的值。

澄清文本:

在下文中,如果在评估 E 之前更改 P(具有某些限制)会导致 E 评估为不同的值,则称指针表达式 E 基于对象 P。限制是:

  • 琐碎的理智限制: P 的修改必须发生在序列点
  • P 只能修改为指向它最初指向的相同副本。
    (因为一般来说,我们可以认为指针总是指向一个数组对象 - P 只能设置为指向该数组对象的副本)。
于 2019-02-27T00:16:11.827 回答
0

第 3 点可以用代码表示为(大致):

#define E(P)  ( (P) + 1 )   // put the expression you want to test here

extern T obj;    // T is some type
T copy = obj;

if ( E(&obj) != E(&copy) )
    printf("the expression in the macro is based on P")

标准中使用的正式语言定义允许E是不确定的和其他病理情况(例如(P) + rand() % 5),而我的示例不允许。

标准版本是我们将结果与相同上下文中E(&obj)的结果进行比较。E(&copy)

于 2019-02-27T00:32:33.353 回答