为啥 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

如果两个指针指向同一数组的不同元素或其子对象,则指向具有较高下标的元素的指针比较大。

将适用,因为 ab 指向同一数组的元素,即使它们已被强制转换为 void *。请注意,¶3.1 的措辞在 C++11 中几乎相同,但似乎被 void * 子句覆盖。

我的理解是否正确?在 C++11 中添加并立即删除的那个奇怪的子句有什么意义?或者它可能仍然存在,但被标准的其他部分移动/暗示?

【问题讨论】:

这并不奇怪,C 和 C++ 是类型化语言,将整数数组的地址分配给 void* 与类似分配不同,因此需要强制转换。 static_cast(foo); @SPlatten:任何数据指针都隐式转换为void *,因此不需要显式转换,尽管确实在某些奇怪的架构(想到分段内存)上转换为void *可能不是普通的按位复制;尽管如此,我仍然无法想象一个架构,其中“常规指针”到“大,void 指针”转换不会保留同一数组的元素之间的排序关系。 我可以将图像指针比较实现为“计算 a 和 b 之间的元素数量;如果它是负数,则 bvoid*,因为没有void[] @MSalters:我也可以想象a - b 被实现为int c; while((c = rand()) + b == a);,但这并不意味着它几乎是明智的。 :-) 另外,我希望它违反算法/容器部分中某处所述的时间复杂度要求。 根据该条款的措辞char c; bool b = (void *)&amp;c == (void *)&amp;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@ 或 &gt;=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 指针的奇怪子句?的主要内容,如果未能解决你的问题,请参考以下文章

指向类方法错误c ++ 11的指针数组

C++11:std::function<void()> func;

关于函数指针的比较

关于void*类型的用法(相当于OC中的id类型)

c语言中,为啥不能对字符指针变量指向的字符串再赋值?

void 型指针的高阶用法,你掌握了吗?