为啥 C++11 包含一个关于比较 void 指针的奇怪子句?
Posted
技术标签:
【中文标题】为啥 C++11 包含一个关于比较 void 指针的奇怪子句?【英文标题】:Why does C++11 contain an odd clause about comparing void pointers?为什么 C++11 包含一个关于比较 void 指针的奇怪子句? 【发布时间】:2019-06-19 02:34:34 【问题描述】:在检查另一个问题的参考资料时,我注意到 C++11 中有一个奇怪的子句,位于 [expr.rel] ¶3:
可以比较指向
void
(指针转换后)的指针,结果定义如下:如果两者 指针表示相同的地址或者都是空指针值,如果运算符是,则结果是true
<=
或>=
和false
否则;否则结果未指定。
这似乎意味着,一旦两个指针被强制转换为void *
,它们的顺序关系就不再得到保证;例如,这个:
int foo[] = 1, 2, 3, 4, 5;
void *a = &foo[0];
void *b = &foo[1];
std::cout<<(a < b);
似乎未指定。
有趣的是,这个子句在 C++03 中不存在,而在 C++14 中消失了,所以如果我们以上面的例子应用 C++14 的措辞,我会说 ¶3.1
如果两个指针指向同一数组的不同元素或其子对象,则指向具有较高下标的元素的指针比较大。
将适用,因为 a
和 b
指向同一数组的元素,即使它们已被强制转换为 void *
。请注意,¶3.1 的措辞在 C++11 中几乎相同,但似乎被 void *
子句覆盖。
我的理解是否正确?在 C++11 中添加并立即删除的那个奇怪的子句有什么意义?或者它可能仍然存在,但被标准的其他部分移动/暗示?
【问题讨论】:
这并不奇怪,C 和 C++ 是类型化语言,将整数数组的地址分配给 void* 与类似分配不同,因此需要强制转换。 static_castvoid *
,因此不需要显式转换,尽管确实在某些奇怪的架构(想到分段内存)上转换为void *
可能不是普通的按位复制;尽管如此,我仍然无法想象一个架构,其中“常规指针”到“大,void
指针”转换不会保留同一数组的元素之间的排序关系。
我可以将图像指针比较实现为“计算 a 和 b 之间的元素数量;如果它是负数,则 bvoid*,因为没有void[]
。
@MSalters:我也可以想象a - b
被实现为int c; while((c = rand()) + b == a);
,但这并不意味着它几乎是明智的。 :-) 另外,我希望它违反算法/容器部分中某处所述的时间复杂度要求。
根据该条款的措辞char c; bool b = (void *)&c == (void *)&c
将是错误的,完全没有理由。我读对了吗?
【参考方案1】:
TL;DR:
在 C++98/03 中,该子句不存在,并且标准没有为void
指针指定关系运算符(核心问题 879,参见本文末尾);
在 C++11 中添加了关于比较 void
指针的奇怪条款来解决它,但这反过来又引发了另外两个核心问题 583 和 1512(见下文);
这些问题的解决需要删除该子句并用 C++14 标准中的措辞替换,这允许“正常”void *
比较。
核心问题 583:Relational pointer comparisons against the null pointer constant
与空指针常量的关系指针比较第 8.9 节 [expr.rel]
在 C 中,这是格式错误的(参见 C99 6.5.8):
void f(char* s) if (s < 0) ...but in C++, it's not. Why? Who would ever need to write (s > 0) when they could just as well write (s != 0)?
自 ARM 以来(可能更早),这一直是该语言; 显然是因为指针转换(7.11 [conv.ptr])需要 每当操作数之一为 指针类型。所以它看起来像“null-ptr-to-real-pointer-type” 转换与其他指针转换搭上了顺风车。
提议的决议(2013 年 4 月):
issue 1512 的解决方案解决了此问题。
核心问题 1512:Pointer comparison vs qualification conversions
指针比较与限定转换部分:8.9 [expr.rel]
根据 8.9 [expr.rel] 第 2 段,描述指针 比较,
指针转换 (7.11 [conv.ptr]) 和限定转换 (7.5 [conv.qual]) 对指针操作数(或指针 操作数和一个空指针常量,或两个空指针常量, 至少其中一个是非集成的)将它们带到他们的 复合指针类型。这似乎使以下 格式不正确的示例,
bool foo(int** x, const int** y) return x < y; // valid ? because int** cannot be converted to const int**, according to the rules of 7.5 [conv.qual] paragraph 4.
这对于指针比较来说似乎太严格了,而且当前 实现接受示例。
提议的决议(2012 年 11 月):
上述问题的解决方法的相关摘录见论文:Pointer comparison vs qualification conversions (revision 3)。
以下内容还解决了核心问题 583。
5.9 expr.rel 第 1 至 5 段的更改:
在本节中,以下语句(C++11 中的奇数子句)已被删除:
可以比较指向
void
(指针转换后)的指针,其结果定义如下: 如果两个指针表示相同的地址或都是空指针值,则结果为true
如果运算符为@ 987654332@ 或>=
和false
否则;否则结果未指定
并且添加了以下语句:
如果两个指针指向同一数组的不同元素或其子对象,则指向具有较高下标的元素的指针比较大。 如果一个指针指向数组的一个元素或其子对象,而另一个指针指向数组的最后一个元素,则后者的指针比较大。
所以在C++14 (n4140) section [expr.rel]/3 的最终工作草案中,发现上述陈述与决议时的陈述相同。
挖掘添加这个奇怪子句的原因让我看到了更早的第 879 期:Missing built-in comparison operators for pointer types。 该问题的提议解决方案(2009 年 7 月)导致增加了该条款,该条款于 2009 年 10 月被 WP 投票通过。
这就是它被包含在 C++11 标准中的原因。
【讨论】:
这解释了为什么它被删除,但为什么它首先被添加?委员会试图通过限制void *
的比较来达到什么目的?或者我错了,我的问题中的示例导致当前 C++ 上的未指定行为(IOW 他们只是改变了措辞)?
@MatteoItalia:我试图通过查找 C++11 标准中的“与 C++03 的兼容性”部分来寻找添加它的原因,但找不到任何相关内容。当前的 C++(17) 也没有以任何形式提及此子句(据我可以检查标准),因此行为可能只是未指定。
“古怪条款”与任一核心问题均无关。由于问题解决方案重写了子条款,因此它更像是一种临时解决方案。
@MatteoItalia:请参阅对答案的编辑,它回答了为什么首先添加这个奇怪的子句的问题。
@MatteoItalia:流程现在看起来更多结构化。感谢您的编辑。以上是关于为啥 C++11 包含一个关于比较 void 指针的奇怪子句?的主要内容,如果未能解决你的问题,请参考以下文章