英文:
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 的对象或函数。
必须让 ST 与 DT 有某种关联吗?严格来说,规则 &*p 甚至可能需要 ST = DT,但这是荒谬的,因为它比关于实际访问的规则更加限制!引用 expr.unary.op#1 的话:
> 一元 * 运算符执行 <i>间接寻址</i>。它的操作数必须是类型为 “指向 T 的指针”的 prvalue,其中 T 是对象或函数类型。该运算符产生一个类型为 T 的左值,表示操作数所指向的对象或函数。
因此,标题的问题是:类型 T 的左值是否必须指向类型 T 的对象?"显然不是",我认为,但标准是否在任何地方回答了这个问题?
我期望 &*p 必须比访问 *p 更不受限制,并且可以比评估 p 更受限制 - 这些情况有更清晰的规则:
- 访问指针有严格的要求("严格别名"):只有当静态类型
ST属于某个类型列表时,实际访问才是定义良好的行为;这个列表包括比动态类型DT更多的类型;具体而言,ST可以是DT、它的有符号/无符号变体以及char、unsigned char和std::byte(basic.lval#11)。 - 指针本身没有这样的要求:如果我们只评估指针
p本身,静态类型ST和动态类型DT不必相关,因为从void*到任意类型的static_cast如果满足对齐要求,则有定义行为 (expr.static.cast#14)。
注意:根据 https://stackoverflow.com/a/21053756/53974,似乎只期望 p 指向对象或函数。
编辑:为了具体性,考虑下面的函数 f。
#include <iostream>
short* f(int *ip) { // 假设 `p` 实际上指向 DT = int
void *vp = ip;
// ^^ 合法,根据 https://eel.is/c++draft/conv.ptr#2
short* sp = static_cast<short*> (vp);
// 合法,根据 [expr.static.cast#14]
return &*sp;
}
int main(int argc, char ** argv) {
int i = 0;
short* sp = f(&i);
void* vp = sp;
int* ip = static_cast<int*>(vp);
*ip = 1;
std::cout << "I: " << i << 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 &*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 &*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 &*p must be less restrictive than accessing *p, and can be more restrictive than evaluating p — and those cases have clearer rules:
- accessing pointers has strict requirements ("strict aliasing"): actual accesses are only defined behavior if static type
STbelongs to a certain list of types; this list includes more than dynamic typeDT; specifically,STcan beDT, its signed/unsigned variants, andchar,unsigned char, andstd::byte(basic.lval#11). - pointers themselves have no such requirement: if we just evaluate pointer
pitself, static typeSTand dynamic typeDTneed not be related, because astatic_castfromvoid*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 <iostream>
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<short*> (vp);
// legal, per [expr.static.cast#14]
return &*sp;
}
int main(int argc, char ** argv) {
int i = 0;
short* sp = f(&i);
void* vp = sp;
int* ip = static_cast<int*>(vp);
*ip = 1;
std::cout << "I: " << i << std::endl;
return 0;
}
答案1
得分: 3
以下是翻译好的部分:
引用的段落对涉及对象的(动态)类型不施加任何要求。如果您初始化
int i;
auto &r=*reinterpret_cast<unsigned*>(&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 &r=*reinterpret_cast<unsigned*>(&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.)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论