这是啥意思:指向 void 的指针永远不会等于另一个指针?

Posted

技术标签:

【中文标题】这是啥意思:指向 void 的指针永远不会等于另一个指针?【英文标题】:What does this mean: a pointer to void will never be equal to another pointer?这是什么意思:指向 void 的指针永远不会等于另一个指针? 【发布时间】:2021-10-07 04:04:43 【问题描述】:

我的一个朋友从“理解和使用 C 指针 - Richard Reese,O'Reilly 出版物”中指出了第二个要点,我无法解释其中的 第一个 句子。我错过了什么?

指向无效的指针

指向 void 的指针是一个通用指针,用于保存对任何数据类型的引用。一个指向 void 的指针示例如下所示:

void *pv;

它有两个有趣的特性:

指向 void 的指针与指向 char 的指针具有相同的表示和内存对齐方式。 指向 void 的指针永远不会等于另一个指针。 但是,分配了 NULL 值的两个 void 指针将相等。

这是我的代码,不是来自书本,所有指针都具有相同的值并且相等。

#include <stdio.h>

int main()

  int a = 10; 
  int *p = &a; 
  void *p1 = (void*)&a;
  void *p2 = (void*)&a;

  printf("%p %p\n",p1,p2);
  printf("%p\n",p);
  
  if(p == p1) 
    printf("Equal\n");
  if(p1 == p2) 
    printf("Equal\n");  

输出:

 0x7ffe1fbecfec 0x7ffe1fbecfec
 0x7ffe1fbecfec
 Equal
 Equal

【问题讨论】:

看起来这本书写得不好(或者,至少是那部分)。它可能的意思是void 指针永远不会等于指向其他对象的指针。但我只能猜测。 这个说法听起来不对。也许他们心中有一些有效的含义,但我不清楚它可能是什么。指向 void 的指针用作通用指针类型(至少对于指向数据的指针,而不是函数指针)。它可以等于任何其他数据指针,具体取决于这些指针的定义方式。 书错了 这里有一个errata,上面写着:“指向 void 的指针永远不会等于另一个指针。”应该是:“指向 void 的指针永远不会等于另一个 指向 void 的指针”。推理:指向 void 的指针可以赋值为非 void 指针的值。不确定这是否会大大改善它。 @EOF 您可能正在考虑另一本书,Mastering C Pointers by Robert J. Traister。那个确实以几乎完全错误而闻名,并且是由对 C 或一般编程没有任何了解的人编写的。 【参考方案1】:

TL/DR:书错了。

我错过了什么?

没有,据我所知。甚至在 cmets 中呈现的勘误版本...

一个指向 void 的指针永远不会等于另一个指向 void 的指针。

... C 语言规范根本不支持。在作者依赖语言规范的范围内,相关文本将是第 6.5.9/6 段:

当且仅当两个指针都是空指针时,两个指针比较相等 是指向同一对象的指针(包括指向对象的指针和 开头的子对象)或函数,两者都是指向一个的指针 超过同一个数组对象的最后一个元素,或者一个是指向 一个超过一个数组对象的末尾,另一个是指向 恰好紧随其后的不同数组对象的开始 地址空间中的第一个数组对象。

void 是一种对象类型,尽管是“不完整”的类型。指向void 的有效且非空的指针是指向对象的指针,它们在规范表达的条件下相互比较。获取此类指针的常用方法是将不同(指针)类型的对象指针转换为void *。这种转换的结果仍然指向与原始指针相同的对象。

我最好的猜测是,这本书误解了规范,指出指向 void 的指针不应被解释为指向对象的指针。虽然有些特殊情况仅适用于指向void 的指针,但这并不意味着适用于对象指针的一般规定也不适用于 void 指针。

【讨论】:

查看 6.3.2.3 指针。它讨论了转换为另一种类型并返回,但从不讨论如果将两个指向 X 的指针转换为两个指向 Y 的指针并将它们作为指向 Y 的指针进行比较,当有实际上没有Y。比较描述总是提到“再回来”。没有 void there 类型的对象。对于char*,指针谈论它指向字节表示中的第一个字符;那是在那里。这可能是标准中的疏忽,但看起来它避免了允许您比较指针指向错误类型是否合法...... 首先,@Yakk-AdamNevraumont,没有一个我知道的 C 实现,只是在比较两个 void 指针是否相等时会注意到。其次,6.3.2.3与它关系不大。两个 void 指针的比较满足 6.5.9/2 中“以下之一应成立”约束中的第三种选择(我怀疑这本书犯了关键错误)。然后 6.5.9/5 表示,如果要比较的指针之一是指向 void 的指针,则另一个将转换为相同的指针以进行比较(在两者都是 void 指针的特殊情况下为无操作)[。 ..] [...] 如果生成的指针对不能相互比较,那将毫无意义。在这种情况下,规范只会直接说两个指针比较不相等。那么,对 6.5.9/6 唯一合理的解释是,这种指针转换导致指向同一个对象的指针,因此它们有可能相互比较相等。该类型void * 没有传达有关这些对象类型的信息,这是无关紧要的。我们显然可以(并且 C 确实)从指针值中知道它们指向的对象是否是同一个对象。 C 的一个有趣之处在于它支持奇怪的架构。考虑 80286 上的分段模式 - 由于指针的段+偏移性质,可能有两个指针指向具有完全不同表示的相同地址。这些指针可能不会比较相等。 @MarkRansom,我承认原则上可能并且在实践中可以观察到指向同一对象的两个指针具有不同的表示形式。而真正有趣的是,C 需要这样的指针对来比较彼此相等,尽管在表示上存在差异。【参考方案2】:

C 2018 6.5.9 6 说:

两个指针比较相等当且仅当两个指针都是空指针,都是指向同一个对象(包括一个指向一个对象和一个在其开头的子对象)或函数的指针,两者都是指向最后一个元素的指针相同的数组对象,或者一个是指向一个数组对象末尾的指针,另一个是指向另一个数组对象的开头的指针,该数组对象恰好紧跟在地址空间中的第一个数组对象之后。

所以,假设我们有:

int a;
void *p0 = &a;
void *p1 = &a;

那么,如果p0p1“指向同一个对象”,p0 == p1 必须评估为真。然而,人们可能会将标准解释为void * 不指向任何东西,而它是void *;它只保存将其转换回其原始类型所需的信息。但我们可以测试这种解释。

考虑两个指针比较相等的规范,如果它们指向一个对象和一个在其开头的子对象。这意味着给定int a[1];&amp;a == &amp;a[0] 应该评估为真。但是,我们不能正确使用&amp;a == &amp;a[0],因为指针的== 的约束要求操作数指向兼容的类型,或者其中一个或两个是void *(允许使用const 之类的限定符)。但是aa[0] 既没有兼容的类型,也没有void

在我们比较指向该对象及其子对象的指针的情况下,出现完全定义情况的唯一方法是至少将其中一个指针转换为void *或指向字符类型的指针(因为这些在转换中得到特殊处理)。我们可以将标准解释为仅表示后者,但我认为更合理的解释是包含void *。其意图是将(void *) &amp;a == (void *) &amp;a[0] 解释为指向对象a 的指针与指向对象a[0] 的指针的比较,即使这些指针采用void * 的形式。因此,这两个void * 应该相等。

【讨论】:

但是,人们可能会将标准解释为 void * 不指向任何东西,而它是 void * IMO the definition of void 不支持这一点解释。 “[A]n 无法完成的不完整对象类型”仍然可以被寻址/指向。它只是不能通过void * 取消引用或访问。 @AndrewHenle:我不认为你真的不同意这个答案。答案并不提倡您引用的解释;相反,整个答案的其余部分都致力于说明这种解释实际上是没有意义的。【参考方案3】:

this Draft C11 Standard 的以下部分完全驳斥了所提出的主张(即使在 comment by GSerg 中的“勘误表”中提到了澄清)。

6.3.2.3 指针

1    指向void 的指针可以转换为指向任何对象类型的指针或从指向任何对象类型的指针转​​换。指向任意的指针 对象类型可以转换为指向void 的指针并再次返回; 结果应与原始指针比较。

或者,这部分来自同一标准草案:

7.20.1.4 能够保存对象指针的整数类型

1    以下类型指定有符号整数类型 任何指向void 的有效指针都可以转换为此的属性 类型,然后转换回指向void的指针,结果将 比较等于原始指针:intptr_t

【讨论】:

这并没有真正解决问题的确切问题,是吗?这表示转换 back again 的结果应该与原始类型指针比较相等,而不是它应该比较相等,同时仍然是 void* @GSerg 我添加了第二个摘录,它更具体到 void* 类型的两个值。【参考方案4】:

指针只是内存中的一个地址。如果它们为 NULL 或指向相同的地址,则任何两个指针都是相等的。您可以继续讨论结构、联合等语言如何发生这种情况。但归根结底,它只是带有内存位置的代数。

【讨论】:

某些类型的指针数学是未定义的行为,因此指针只是整数地址的推理在其他情况下并不总是那么简单。 C 标准为分段和其他方式为同一实际地址提供多种表示打开了大门,因此如果您以不同的方式导出它们,它们不必比较相等。 (这通常涉及未定义的行为,因此您也无法安全地取消引用这样的指针。)无论如何,这个答案对于这个问题来说并没有错,但要小心过度简化。 C 并不像带有平面内存模型的 asm 那样简单。 @PeterCordes:为了让事情变得更有趣,在由 clang 的优化器处理的语言中,将指针转换为整数然后比较这些整数可能会导致与整数相等或与他们是不平等的。【参考方案5】:
指向 void 的指针永远不会等于另一个指针。但是,分配了 NULL 值的两个 void 指针将相等。

由于该声明中提到了NULL,我认为这是一个错误的输入。声明应该类似于

指向 void 的指针永远不会等于 NULL 指针。但是,分配了 NULL 值的两个 void 指针将相等。

这意味着任何指向 void 的有效指针永远不会等于 NULL 指针。

【讨论】:

以上是关于这是啥意思:指向 void 的指针永远不会等于另一个指针?的主要内容,如果未能解决你的问题,请参考以下文章

void *(*routine)(void *) 在 C 中是啥意思? [复制]

请问 对于指向函数的指针,前面的括号(int (*)(void*,void*))代表啥呢?谢谢

(void(*)(void)) &shellcode啥意思?

数据结构链表中,p是指针,L是链表,那么p=L是啥意思,是p指向L的头结点,还是p等于L的每个值

while(p) 是啥意思? (p 是指向 C 样式字符串的第一个元素的指针)

C语言中NULL是啥意思