15

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

address_expression - address_expression

并且

address_expression > address_expression

有人可以向我解释一下它们是如何工作的以及何时使用它们。

编辑:

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

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

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

我知道 1<2,但是一个地址如何大于另一个地址以及它们的比较对象是什么

4

7 回答 7

35

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

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

以下使用 1999 C 标准 (ISO/IEC 9899, Second edition, 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] 相同的数组。或者,更准确地说,如果你做这样的减法,行为是不确定的。请注意,未指定的不仅仅是结果;你不能指望你会得到一些可能是荒谬的数字:行为未定义。根据 C 标准,这意味着 C 标准没有说明任何结果。你的程序可以给你一个合理的答案,或者它可以中止,或者它可以删除文件,所有这些结果都将符合 C 标准。

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

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

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

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

只要您保持在上述约束范围内,您就可以将指针视为内存地址的数字。

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

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

于 2012-07-30T01:02:09.497 回答
27

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

例如,

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

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

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

 &buf[10] > &buf[0];  // 1, &buf[10] element is after &buf[0] element
于 2012-07-30T00:12:47.443 回答
5

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

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

于 2012-07-30T00:04:18.567 回答
0

在解释指针算法时,我喜欢使用一个类比——它是如何工作的,以及它的局限性——是考虑街道地址。

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

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

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

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

于 2021-09-25T12:37:44.810 回答
-1

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

int strLength = (last_char_address - first_char_address) + 1;

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

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

于 2012-07-29T23:49:11.823 回答
-1

指针通常可以被认为只是表示内存地址的数字,例如 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 链表。您可以在此处找到有关它的更多详细信息。如果您愿意,我可以对此进行扩展;在评论中告诉我。

于 2012-07-30T00:09:58.880 回答
-2

您可以通过多种方式将地址视为 an 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]now 指的是什么是p[1]. 这避免了不得不做一些p = &p[1]不必要的取消引用之类的事情。

于 2012-07-30T00:21:13.277 回答