【问题标题】:C/C++: Pointer ArithmeticC/C++:指针算术
【发布时间】:2012-07-27 16:10:46
【问题描述】:

我在 Pointer Arithmetic 中读了一点,我遇到了 2 件我无法理解的东西,也不知道它的用途

address_expression - address_expression

还有

address_expression > address_expression

谁能给我解释一下,它们是如何工作的以及何时使用。

编辑:

我的意思是,如果我只取两个地址并减去它们,它们会产生什么

如果我取两个地址并比较它们,结果是什么或比较基于

编辑: 我现在明白了减去地址的结果,但是比较地址我还是不明白。

我知道 1

【问题讨论】:

  • Title 宣布 C++,但标签包括 C,C++。如果目标是双语和双语解决方案,则标题应删除 C++。

标签: c++ c pointers math pointer-arithmetic


【解决方案1】:

这里有几个答案表明指针是数字。这不是 C 标准指定的指针的准确描述。

在很大程度上,您可以将指针视为数字和内存中的地址,前提是 (a) 您了解指针减法将差异从字节转换为元素(被减去的指针的类型),并且 ( b) 你了解这个模型打破的限制。

以下使用 1999 C 标准(ISO/IEC 9899,第二版,1999-12-01)。我希望以下内容比提问者要求的更详细,但鉴于这里的一些错误陈述,我认为应该提供准确和准确的信息。

根据 6.5.6 第 9 段,您可以减去两个指向同一数组元素的指针或指向数组最后一个元素的指针。所以,如果你有int a[8], b[4];,你可以从指向a[2] 的指针中减去指向a[5] 的指针,因为a[5] 和a[2] 是同一个数组中的元素。您还可以从指向 a[8] 的指针中减去指向 a[5] 的指针,因为 a[8] 是数组最后一个元素的后一个元素。 (a[8] 不在数组中;a[7] 是最后一个元素。)您不能从指向 b[2] 的指针中减去指向 a[5] 的指针,因为 a[5] 不在与 b[2] 相同的数组。或者,更准确地说,如果你做这样的减法,行为是不确定的。请注意,未指定的不仅仅是结果;你不能指望你会得到一些可能是荒谬的数字:behavior 是未定义的。根据 C 标准,这意味着 C 标准没有说明任何结果。你的程序可以给你一个合理的答案,或者它可以中止,或者它可以删除文件,所有这些结果都将符合 C 标准。

如果您执行允许的减法,则结果是从第二个指向元素到第一个指向元素的元素数。因此,a[5]-a[2] 是 3,a[2]-a[5] 是 -3。无论a 是什么类型,这都是正确的。需要 C 实现将字节(或它使用的任何单位)的距离转换为适当类型的元素。如果 a 是一个双精度数组,每个数组有 8 个字节,那么 a[5]-a[2] 是 3,对于 3 个元素。如果a 是一个字符数组,每个字符包含一个字节,那么a[5]-a[2] 是 3,对于 3 个元素。

为什么指针永远不仅仅是数字?在某些计算机上,尤其是较旧的计算机上,寻址内存更为复杂。早期计算机的地址空间很小。当制造商想要制造更大的地址空间时,他们也想保持与旧软件的一些兼容性。由于硬件限制,他们还必须实施各种寻址内存的方案,这些方案可能涉及在内存和磁盘之间移动数据或更改处理器中控制如何将地址转换为物理内存位置的特殊寄存器。对于在这样的机器上工作的指针,它们必须包含更多的信息,而不仅仅是一个简单的地址。因此,C 标准不仅仅将指针定义为地址,还允许您对地址进行算术运算。只定义了合理数量的指针算术,并且需要 C 实现提供必要的操作以使该算术工作,但仅此而已。

即使在现代机器上,也可能会出现并发症。在 Digital 的 Alpha 处理器上,指向函数的指针不包含函数的地址。它是函数描述符的地址。该描述符包含函数的地址,并包含正确调用函数所必需的一些附加信息。

关于关系运算符,例如>,C 标准在 6.5.8 第 5 段中说,您可以比较可以减去的相同指针,如上所述,您还可以比较指向成员的指针聚合对象(结构或联合)。指向数组成员(或其结束地址)的指针以预期的方式进行比较:指向较高索引元素的指针大于指向较低索引元素的指针。指向同一个联合的两个成员的指针比较相等。对于指向结构的两个成员的指针,指向后面声明的成员的指针大于指向前面声明的成员的指针。

只要不超出上述限制,就可以将指针看作是内存地址的数字。

通常,C 实现很容易提供 C 标准所需的行为。即使计算机具有复合指针方案,例如基地址和偏移量,通常数组的所有元素都将使用彼此相同的基地址,而结构的所有元素将使用彼此相同的基地址。因此编译器可以简单地减去或比较指针的偏移部分,以获得所需的差异或比较。

但是,如果你在这样的计算机上减去指向不同数组的指针,你会得到奇怪的结果。由基地址和偏移量形成的位模式可能看起来比另一个指针更大(当解释为单个整数时),即使它指向内存中的较低地址。这是您必须遵守 C 标准设定的规则的原因之一。

【讨论】:

  • 非常棒的答案:我可以说我在读完这篇文章后学到了一些东西。您确实成功地提供了具体而有启发性的推理,说明为什么它不像“地址只是数字”那么简单,以及为什么规范在这一点上是具体的(或者更确切地说,将其留给实现)。我将编辑自己的答案以使其更好,或完全删除它。谢谢
  • 两个指针相减得到的类型是什么? ptrdiff_t? uintptr_t?还有什么?
  • @jww:两个指针相减的结果是ptrdiff_t类型。
  • @dreamcrash:不,“让你和他们一起做算术”是“公正”的一部分。 C 标准没有将指针定义为地址,并且 C 标准不允许您对它们进行算术运算(指针作为地址)。但我会改写一下。
【解决方案2】:

指针减法得到相同类型的两个指针之间的数组元素个数。

例如,

int buf[10] = /* initializer here */;

&buf[10] - &buf[0];  // yields 10, the difference is 10 elements

指针比较。例如,对于> 关系运算符:> 操作产生1,如果左侧的指向数组元素或结构成员在右侧指向的数组元素或结构成员之后并且它产生0 否则。记住数组和结构是有序的序列。

 &buf[10] > &buf[0];  // 1, &buf[10] element is after &buf[0] element

【讨论】:

  • 谢谢。尽管其他答案提供了更多细节,并向我解释了很多我不知道的事情。这是最直接的答案,回答了我的主要问题
  • -> 仅适用于指向同一数组的指针。在其他任何事情上使用它们是未定义的行为。
  • @FredOverflow 或者数组的最后一个元素(就像我的两个示例中一样),对于关系运算符,您也可以将运算符用于相同的结构或联合对象。
  • 有意思,我刚刚验证了struct规则,标准确实保证了。这是 C++11 扩展吗?无论如何,我要 +1。
  • "在两个相同类型的指针之间。"不是一个字符串足够的条件。它应该是“在相同类型的两个指针和相同数组的元素之间(或 1 次传递)。”
【解决方案3】:

两个指针地址相减返回该类型的元素个数

因此,如果您有一个整数数组和两个指针,减去这些指针将返回 int 值之间的数量,而不是字节数。与 char 类型相同。因此,您需要注意这一点,尤其是在使用字节缓冲区或宽字符时,您的表达式正在计算正确的值。如果您需要基于字节的缓冲区偏移量来存储不使用单个字节进行存储的内容(int、short 等),您需要先将指针转换为 char*。

【讨论】:

  • 次要细节:对于char 类型,减去总是计算字节数,因为char 被C 标准定义为一个字节。
  • 另外,需要注意的是,如果p1 - p2p1 < p2 的两个指针不指向同一个超对象内的子对象(同一个数组内的元素),则结果是不确定的。
  • @DietrichEpp 这有点误导,因为字节的 C 定义不一定是八位字节。
【解决方案4】:

在解释指针算法时我喜欢使用的一个类比——包括它的工作原理和局限性——是考虑街道地址。

假设在榆树街的相同大小的地段上有一堆房子,所有地段都有 50 英尺宽。假设我想知道从 #12 Elm Street 到 #46 Elm Street 的距离,并且假设我想知道这个距离是 houses 的数量,而不是英尺的距离。嗯,很明显,我可以从 46 中减去 12,得到 34 个房子的答案。 (其实,当然比这复杂一点,因为街道两边可能都有房子,但我们暂时先忽略这个问题。)

假设在第 10 大道有一堆工业建筑,位于更大的地段,全部 100 英尺宽。我仍然可以减去街道编号,我会得到建筑物数量(不是英尺)的距离。

这类似于 C 中的指针减法,您会得到按指向对象大小缩放的差异。您确实不会以原始字节的形式获得答案(类似于街道地址类比中的英尺)。

但是街道地址类比帮助我们理解的另一件事是为什么我们不能使用指针算法来处理指向不同数组的指针。假设我想知道从 #12 Elm Street 到 #30 10th Avenue 的距离。减去地址不起作用!这是没有意义的。您无法有意义地减去或比较不同街道上的地址,就像您无法有意义地减去或比较指向不同数组的指针一样。

【讨论】:

    【解决方案5】:

    指针通常可以被认为只是表示内存地址的数字,例如 0x0A31FCF20(或十进制的 2736770848)或 0xCAFEDEAD(有时系统使用它来指示错误,我不记得细节了。)

    指针比较通常用于对指针数组进行排序。当您需要检查指针是否在指针列表中时,指针的排序数组很有帮助;如果列表已排序,则不必查看列表的每个元素来确定指针是否在该列表中。您需要使用比较来对列表进行排序。

    当您有一个指向数据块的指针并且您需要访问不在数据块开头的内容时,通常会使用指针算法。例如:

    const char *string = "hello world!"
    const char *substring = string+6;
    std::cout << string << "\n";
    std::cout << substring << std::endl;
    

    这将输出:

    hello world!
    world!
    

    这里我们得到了“hello world!”前 6 个字符之后的字符串,即"world!"。请记住,如果可能,您应该在可用的地方使用std::string。一个与指针算法非常相似的概念是随机访问迭代器。

    减去指针可以帮助您找到这两个指针之间的距离。如果您有一个指向数组第一个元素的指针,以及一个指向数组最后一个元素之后的一个元素的指针,则减去这两个指针可以帮助您找到数组的大小。

    另一种可能将指针视为整数的情况是链表的优化版本,称为 XOR 链表。您可以在here 找到有关它的更多详细信息。如果您愿意,我可以对此进行扩展;在 cmets 中告诉我。

    【讨论】:

    • 指针不仅仅是数字。在某些平台上,指针是基地址和偏移量,基地址和偏移量的不同组合可以指向同一个位置。
    • 在您对上述帖子发表评论后,我正在编辑此内容。这有帮助吗?
    【解决方案6】:

    第一个表达式从另一个指针中减去一个指针。作为为什么这可能有用的一个简单示例,请考虑一个 C 字符串。字符串在连续的内存中,所以如果你有字符串的第一个字符的地址和最后一个字符的地址,你可以通过以下方式找到字符串的长度:

    int strLength = (last_char_address - first_char_address) + 1;
    

    这种指针算术是类型感知的,这意味着算术的结果表示两个指针之间的元素数量 - 特定类型。在上面使用char 的示例中,区别在于字符数。这同样适用于例如指向两个structs 的指针。

    同样,您的第二个表达式只是比较指针,结果将是 1 或 0。作为一个非常简单的示例,数组的元素 5 的地址始终是 &gt; 的地址元素4&amp;string[4] &gt; &amp;string[5] 为真。

    【讨论】:

    • 我的意思是,如果我只取两个地址并减去它们,它们会产生什么如果我取两个地址并比较它们,结果或比较基于什么
    • 减法产生一个数字。在我的示例中,数字表示地址之间的距离,或之间的内存地址数。根据参数比较是真还是假
    • 两件事:strLength 有一个错误,很高兴解释 C 指针算术和汇编中的等效算术之间的区别——即减去两个 @987654327 @指针会给你一个不同的结果,而不是你先将它们转换为char *
    • 指针不一定是简单的内存地址。 C 标准为某些平台使用的更复杂的寻址形式留出了空间。此外,C 中的指针减法不仅仅是从另一个地址中减去一个地址。它还将地址差异除以指向对象的大小。更准确地说,C 中的减法运算符应用于指向同一数组中的两个对象(或数组的结束地址)的指针的结果是从一个对象到下一个对象的元素数。
    • @MohamedAhmedNabil:如果比较指向数组中两个对象的指针(或数组的结束地址,即数组中实际最后一个元素之后的元素的地址),那么指向数组中索引较大的元素的指针大于指向数组中索引较小的元素的指针。如果比较指向结构对象中两个成员的指针,则指向后面元素的指针大于指向前面元素的指针。如果您比较指向上述以外的事物的指针,则行为未定义。
    【解决方案7】:

    您可以通过多种方式将地址视为int。唯一的区别是int 表示该地址中的大小数量。例如,如果int * p 恰好具有234 的值(来自例如p = new int[12]; 的一些安全指令),它表示地址234。如果我们使用p += 1;,它只是添加, int-size 方面。现在p 是(假设此示例为 4 字节 int)238,又名p[1]。事实上p[x] 等价于*(p+x)。您可以像 int 一样进行比较。在某些情况下,这很有用,例如在给定的示例中,p[0] 现在指的是 p[1]。这避免了不得不做类似p = &amp;p[1] 这样不必要地取消引用的事情。

    【讨论】:

    • int * p=234 是非常错误的,如果被误导的编译器允许,是很危险的。用 g++ 5.3.0 的话来说,这是从 'int' 到 'int*' 的无效转换。假设 sizeof(int)==4 同样错误
    • 是的,当然。我的意思是如果int * p的内部值在某些指令(例如p = new int[12];)之后恰好是234,我们可以安全地对其进行指针运算。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-10-20
    • 1970-01-01
    • 1970-01-01
    • 2013-06-03
    • 2023-01-30
    相关资源
    最近更新 更多