提问人:doug 提问时间:8/22/2023 最后编辑:Jan Schultkedoug 更新时间:8/26/2023 访问量:196
P1 和 P2 是指向整数的指针,如果 P2>P1 有效,则 P2-P1 有效吗?
p1 and p2 are pointers to ints, if p2>p1 is valid, is p2-p1 valid?
问:
可以比较指向类成员变量的指针,结果取决于声明序列。例如,在编译器资源管理器中的此示例是有效的,并返回 true (1):
struct A {
int a0 = 1;
int a1 = 2;
};
consteval int foo()
{
A a;
int* p1 = &a.a0;
int* p2 = &a.a1;
return p2 > p1;
}
int main()
{
return foo();
}
因此,人们会期望这将返回指针之间的距离(以 int 对象为单位)。它在运行时确实如此。编译器资源管理器p2-p1
struct A {
int a0 = 1;
int a1 = 2;
};
int foo()
{
A a;
int* p1 = &a.a0;
int* p2 = &a.a1;
return p2 - p1;
}
int main()
{
return foo();
}
但不是在编译时。GCC、CLANG 和 MSVC 的行为都不同。GCC 编译并返回预期值,CLANG 抱怨 UB,MSVC 编译但返回 4,而不是 1,以字节为单位的距离!编译器资源管理器
CLANG:注意:减去的指针不是同一数组的元素
哪个是正确的?
答:
15赞
Ryan Haining
8/22/2023
#1
不能减去指向对象的指针,除非它们是同一数组的元素。这是未定义的行为,因此您可以获得不同的结果。
如果 P 和 Q 分别指向同一数组对象 x 的数组元素 i 和 j,则表达式 P - Q 的值为 i − j。
否则,行为是未定义的。
http://eel.is/c++draft/expr.add#5.2
Clang 将其标记为未定义的行为是正确的,GCC 和 MSVC 在做任何他们想做的事情时都是正确的,因为它是未定义的行为。如果函数是,那么只有 clang 是正确的,其他的应该发出诊断consteval
评论
0赞
doug
8/22/2023
是的,这也是我的想法。我发现奇怪的是,即使指针指向不同的对象,也可以合法地进行比较。我唯一知道第一个是合法的,但不是第二个,所以我想我会测试它。
4赞
Peter
8/22/2023
从技术上讲,如果行为未定义,所有编译器都是正确的,因为该标准没有施加任何要求,并且根本不需要诊断未定义的行为。更公平地说,clang 提供了一种诊断,以一种人类更容易理解的方式识别实际问题,即编译器之间的区别在于实现的质量,而不是正确性。
0赞
Ryan Haining
8/23/2023
@Peter是的,说得好,我编辑了最后一行
0赞
doug
8/23/2023
@Peter 编译器在编译期间计算包含 UB 的常量表达式(不包括库调用中出现的 UB)时,必须失败/发出诊断。这就是 in foo 的原因。它在编译时强制 eval。请参见:[常量表达式 ([expr.const]) 的计算从不表现出在 [intro] 到 [cpp] 中明确指定为未定义的行为。(eel.is/c++draft/defns.undefinedconsteval
)
1赞
doug
8/24/2023
@Peter UB 通常允许编译器和程序执行任何操作或不执行任何操作。但有一个例外。许多 UB 行为禁止表达式成为核心常量。当这种情况发生在编译时评估中时,它们无法进行常量评估,需要诊断。请参阅此处关于需要检测什么以及何时需要检测 UB 的相当详细的讨论 评论中也有很多很好的背景。
评论