【问题标题】:Are two pointers comparing equal converted to an integer type compare equal?两个比较相等的指针是否转换为整数类型比较相等?
【发布时间】:2019-05-20 21:17:33
【问题描述】:

问题:如果指针比较相等,它们的整数转换值是否也相等?

例如:

void *ptr1 = //...
void *ptr2 = //...
printf("%d", ptr1 == ptr2); //prints 1

是不是说(intptr_t) ptr1 == (intptr_t) ptr2也是1

从务实的角度来说应该是对的。但考虑到标准在7.20.1.4(p1) 指定的内容:

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

    intptr_t

这与实现可以将相同的指针转换为不同的值(取决于一些奇怪的情况)并不矛盾,保持转换回来的值产生相同的指针。

所以,我认为不,比较相等的指针的整数转换值不一定彼此相等。

【问题讨论】:

  • 我无法想象两次以相同方式转换一个值会产生不同的结果。但我不能说我在任何地方都有证据。
  • 这似乎是编译器会忽略的明显无操作,但只是推测
  • @yhyrcanus 我进行了一些实验,整数确实具有相同的值,但我想确定它是否符合计数。
  • 在像 Intel 80286 这样的分段内存架构中,指向同一个对象的两个指针可能具有不同的值。

标签: c pointers language-lawyer


【解决方案1】:

你的分析是正确的。除了允许在§6.3.2.3 处与整数进行转换之外,该标准没有提及该转换的行为方式。诚然,intptr_t 有一个“往返”要求,但它不会阻止一次以上的旅行,编译器会根据一些约束或要求选择一个或另一个。

确实,C 标准并不要求 (intptr_t) ptr1 == (intptr_t) ptr2 持有。

【讨论】:

  • 我想我们甚至不能暗示如果(intptr_t) ptr == 0 那么ptr == NULL 即使空指针常量的定义是一个具有值0 的整数常量表达式。跨度>
  • @SomeName - 确实如此。
  • 与这个问题没有太大关系,但无论如何我认为有必要在转换为intptr_t之前显式地将任何不同于void *的指针类型显式转换为void *,不是吗?
  • 这取决于你所说的“必要”是什么意思,@SomeName。往返保证适用于void *。其他指针类型到intptr_t 的转换具有实现定义的行为,甚至可能是未定义的行为,但它们是允许的,因此在这个意义上不需要首先转换为void *。然而,在实践中,作为实现的质量问题,您通常可以依赖于通过intptr_t 往返任何对象指针类型。
  • @JohnBollinger 我的意思是,如果一个实现支持intptr_t,那么在没有任何额外的实现定义的假设的情况下,可以像obj_t * --> void * --> intptr_t --> void * --> obj_t *那样往返指向对象类型的指针。
【解决方案2】:

在几乎所有实现中,当且仅当它们的表示相等时,两个指针才相等,但标准不保证这一点。

ptr1 == ptr2 这一事实并不意味着ptr1ptr2 具有相同的表示。 N1570 6.5.9 第 6 段:

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

例如,假设一个指针表示为一个由两部分组成的实体,第一部分标识内存段,第二部分标识该段内的字节偏移量。如果两个段可以重叠,则同一内存地址可以有两种不同的指针表示。这两个指针会比较相等(并且生成的代码可能需要做一些额外的工作才能做到这一点),但如果转换为intptr_t 只是复制表示然后(intptr_t)ptr1 != (intptr_t)ptr2

(指针到整数的转换也有可能使表示标准化。)

这种可能性就是为什么 ==!= 被很好地定义为指向不同对象的指针,但关系运算符(<<=>>=)未定义。相等运算符必须确定两个指针是否指向同一位置,但允许关系运算符仅比较偏移量并忽略基本部分(假设每个对象都在单个段中)。实际上,几乎所有现代系统都有一个单一的地址空间,并且相等和关系运算符始终如一地工作,即使标准不要求它们这样做。

【讨论】:

  • " 或者一个是指向一个数组对象末尾的指针,另一个是指向另一个数组对象的开头的指针,该数组对象恰好紧跟在第一个数组对象之后地址空间” -- 哇,这是合法的吗?这非常令人惊讶。
  • @JL2210:这是合法的,因为不允许这样做是不切实际的。给定int x; int y; int *p0 = &x + 1; int *p1 = &y; if (p0 == p1) ...,如果y恰好在内存中紧跟x,那么p0和p1`将指向同一个内存位置。让p0 == p1 产生错误的结果将需要不合理的额外工作。
【解决方案3】:

指针大小介于两个整数类型之间的实现(例如分段模式 80386,其中指针为 48 位)可能会处理如下内容:

uintptr_t my_uintptr = (uintptr_t)myptr;

通过将 myptr 存储到 my_uintptr 的前 48 位中并让其余位保持任意值,前提是稍后的转换 myptr = (void*)my_uintptr; 会忽略这些位的值。

由于不能保证重复转换 same 指针到 uintptr_t 会产生相同的值,同样也不能保证在被转换的指针比较相等的情况下,尽管由不同的方式。

但是,如果一个实现记录了指针和整数的存储格式,并记录了转换是如何执行的,并且如果在不支持更强大的语义保证的情况下,一个行为不可能以与该文档一致的方式表现,那么预期执行将维护这些保证。我不认为标准要求实现以与其文档一致的方式作为一致性条件,但是质量实现应该按照文档的行为的概念应该是不言而喻的,标准不应该需要要求它。

【讨论】:

  • 这是一个伟大的不受折磨的例子,说明这种假设可能会落空。
  • @BenZotto:有任何理由假设失败的硬件平台很少见。描述事物如何存储但随后以与之不一致的方式运行的实现在实践中是一个更大的问题。就个人而言,我认为任何真正努力产生高质量实施的人都会寻求避免这种不一致,而不管标准是否禁止它们,但并不是每个人都这么认为。
  • 哦,绝对的,我 100% 同意你的看法。我很欣赏您在回答中也提出了这一点。但我被感动到评论只是因为在阅读这个问题时,它似乎是一个非常愚蠢的语言律师头发分裂,你巧妙地提供了一个理论陷阱的真实例子。 (在实践中,当然,几乎可以肯定的是,一个实现几乎肯定会像那样古怪!)
  • @BenZotto:我不知道是否有任何使用 48 位指针的 80386 编译器支持 64 位整数类型,或者它们是否根本没有定义 uintptr_t,因为到那时 C99 添加了 64 -bit 类型,x86 上的几乎所有内容都使用“平面”32 位模式,所有内容都使用单个段。此外,我认为该模式的质量编译器应该提供一个选项来显式清除 uintptr_t 的高位,而不考虑标准是否需要它,即使它还提供了一个使这些位不确定的选项。
  • @AlexShpilkin:除了严格符合程序之外,该标准不会“禁止”任何此类事情。是否在非便携式程序中支持此类结构的问题留作其管辖范围之外的“实施质量”问题。不幸的是,clang 和 gcc 的作者拒绝承认该标准并未试图禁止低质量的实现,因此一致性本身并不是质量的衡量标准。
猜你喜欢
  • 2017-09-22
  • 1970-01-01
  • 2013-08-18
  • 2016-03-05
  • 2021-09-14
  • 2023-03-11
  • 2010-12-05
  • 2016-03-06
相关资源
最近更新 更多