Must lvalues of type T identify objects of type T? If `p` has type `T *`, does `&*p` require `p` to actually point to an object of type `T`?

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

Must lvalues of type T identify objects of type T? If `p` has type `T *`, does `&*p` require `p` to actually point to an object of type `T`?

问题

In C++,给定 ST *p*p 产生一个左值 (expr.unary.op#1),如果静态类型 ST 是原始类型,则将 *p 转换为右值实际上 访问读取 *p (conv.lval, basic.lval#11)。但假设我们 *p 转换为右值。我们可以评估 &*p,或者绑定一个引用到 *p 等等。p 仍然必须指向 动态 类型 DT 的对象或函数。
必须让 STDT 有某种关联吗?严格来说,规则 &*p 甚至可能需要 ST = DT,但这是荒谬的,因为它比关于实际访问的规则更加限制!引用 expr.unary.op#1 的话:

> 一元 * 运算符执行 <i>间接寻址</i>。它的操作数必须是类型为 “指向 T 的指针”的 prvalue,其中 T 是对象或函数类型。该运算符产生一个类型为 T 的左值,表示操作数所指向的对象或函数。

因此,标题的问题是:类型 T 的左值是否必须指向类型 T 的对象?"显然不是",我认为,但标准是否在任何地方回答了这个问题?


我期望 &*p 必须比访问 *p 更不受限制,并且可以比评估 p 更受限制 - 这些情况有更清晰的规则:

  1. 访问指针有严格的要求("严格别名"):只有当静态类型 ST 属于某个类型列表时,实际访问才是定义良好的行为;这个列表包括比动态类型 DT 更多的类型;具体而言,ST 可以是 DT、它的有符号/无符号变体以及 charunsigned charstd::byte (basic.lval#11)。
  2. 指针本身没有这样的要求:如果我们只评估指针 p 本身,静态类型 ST 和动态类型 DT 不必相关,因为从 void* 到任意类型的 static_cast 如果满足对齐要求,则有定义行为 (expr.static.cast#14)。

注意:根据 https://stackoverflow.com/a/21053756/53974,似乎只期望 p 指向对象或函数。

编辑:为了具体性,考虑下面的函数 f

#include &lt;iostream&gt;

short* f(int *ip) { // 假设 `p` 实际上指向 DT = int
  void *vp = ip;
  // ^^ 合法,根据 https://eel.is/c++draft/conv.ptr#2

  short* sp = static_cast&lt;short*&gt; (vp);
  // 合法,根据 [expr.static.cast#14]
  return &amp;*sp;
}

int main(int argc, char ** argv) {
    int i = 0;
    short* sp = f(&amp;i);
    void* vp = sp;
    int* ip = static_cast&lt;int*&gt;(vp);
    *ip = 1;
    std::cout &lt;&lt; &quot;I: &quot; &lt;&lt; i &lt;&lt; std::endl;

    return 0;
}
英文:

In C++, given ST *p, *p produces an lvalue (expr.unary.op#1), and if the static type ST is primitive converting *p to an rvalue actually accesses and reads *p (conv.lval, basic.lval#11). But suppose we don't convert *p to an rvalue. We can evaluate &amp;*p, or bind a reference to *p, etc. p must still point to an object or function of dynamic type DT.
Must ST be somehow related to DT? Strictly speaking, rules &amp;*p might even require ST = DT, but that's absurd because it's more restrictive than rules about actual accesses! Quoting expr.unary.op#1:

> The unary * operator performs <i>indirection</i>. Its operand shall be a prvalue of type “pointer to T”, where T is an object or function type. The operator yields an lvalue of type T denoting the object or function to which the operand points.

Hence the title question: Must lvalues of type T point to objects of type T? "Clearly not", I think, but does the standard answer this anywhere?


I expect &amp;*p must be less restrictive than accessing *p, and can be more restrictive than evaluating p — and those cases have clearer rules:

  1. accessing pointers has strict requirements ("strict aliasing"): actual accesses are only defined behavior if static type ST belongs to a certain list of types; this list includes more than dynamic type DT; specifically, ST can be DT, its signed/unsigned variants, and char, unsigned char, and std::byte (basic.lval#11).
  2. pointers themselves have no such requirement: if we just evaluate pointer p itself, static type ST and dynamic type DT need not be related, because a static_cast from void* to an arbitrary type has defined behavior if alignment requirements are satisfied (expr.static.cast#14).

Note: according to https://stackoverflow.com/a/21053756/53974, it seems (only?) expected that p points to an object or function.


EDIT: For concreteness, consider function f below.

#include &lt;iostream&gt;

short* f(int *ip) { // assume `p` actually points to DT = int
  void *vp = ip;
  // ^^ legal, per https://eel.is/c++draft/conv.ptr#2

  short* sp = static_cast&lt;short*&gt; (vp);
  // legal, per [expr.static.cast#14]
  return &amp;*sp;
}

int main(int argc, char ** argv) {
    int i = 0;
    short* sp = f(&amp;i);
    void* vp = sp;
    int* ip = static_cast&lt;int*&gt;(vp);
    *ip = 1;
    std::cout &lt;&lt; &quot;I: &quot; &lt;&lt; i &lt;&lt; std::endl;

    return 0;
}

答案1

得分: 3

以下是翻译好的部分:

引用的段落对涉及对象的(动态)类型不施加任何要求。如果您初始化

int i;
auto &amp;r=*reinterpret_cast&lt;unsigned*&gt;(&amp;i);

强制转换的结果引用i,尽管其类型为unsigned*,而且类似地,r是类型为unsigned的lvalue,引用**int对象i(而不是尝试引用该地址处不存在的unsigned对象而导致未定义行为)。通过该lvalue访问i在[basic.lval]/11中被允许(如前所述),尽管标准未指示此类访问的实际效果**。 (可能使用/更新位模式,但是当通过char lvalue变异指针变量的字节时存在微妙之处。)

英文:

The quoted passage does not impose any requirement on the (dynamic) type of the object in question. If you initialize

int i;
auto &amp;r=*reinterpret_cast&lt;unsigned*&gt;(&amp;i);

the result of the cast refers to i despite having the type unsigned*, and similarly r is an lvalue of type unsigned that refers to the int object i (not undefined behavior from trying to refer to a nonexistent unsigned object at that address). Accessing i via that lvalue is blessed by [basic.lval]/11 (as mentioned), although the standard fails to indicate the actual effects of such access. (Presumably the bit pattern is used/updated, but there are subtleties when, say, the bytes of a pointer variable are mutated through a char lvalue.)

huangapple
  • 本文由 发表于 2023年5月15日 07:26:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/76250080.html
匿名

发表评论

匿名网友

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

确定