英文:
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
ST
belongs to a certain list of types; this list includes more than dynamic typeDT
; specifically,ST
can beDT
, its signed/unsigned variants, andchar
,unsigned char
, andstd::byte
(basic.lval#11). - pointers themselves have no such requirement: if we just evaluate pointer
p
itself, static typeST
and dynamic typeDT
need not be related, because astatic_cast
fromvoid*
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.)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论