0

我是否可以期望用户空间程序中的任何“数据”指针与地址 0 和 0xffffffff... 保持安全距离,以便我可以安全地为指针添加一个小偏移量而不检查溢出?当 p 是指向常量字符缓冲区或动态分配字符串的 char 指针(在现代 >= 32 位操作系统上)时,我可以安全地假设 p + n 不会溢出的最大正 n 是多少?

为避免混淆:我说的是溢出检查,而不是边界检查。例如:如果您有一个指针 p 指向具有 m 个字符的字符串的开头,并且您想在正偏移量 i 处访问该字符,那么您需要检查 i < m 或者您可以间接检查 p + i < p + 米。但是,在后一种情况下,您还必须确保 p + i 不会溢出,即您必须确保 p + i >= p。

更新:好的,如果 i > m,p + i 不是有效的标准 C,无论 p + i 是否实际取消引用或是否溢出。但是,我真正感兴趣的问题是是否存在一个小的 n ,而 p + n在实践中不会溢出。回答这个问题显然需要一些关于现代操作系统如何组织地址空间的知识。

更新 2:听到任何一个特定平台已经很有趣了,即使它不是通用的。最好不是一些晦涩难懂的嵌入式。x86 或基于 Power 的 32 位 Win、Linux 和 Mac 将是最有趣的。

4

8 回答 8

8

您可以安全地添加到指针(然后取消引用它)的唯一偏移量是将指针定位在您正在使用的内存块中的偏移量。此块必须已使用 new 或 malloc 分配,或者存在于堆栈中。

无论哪种方式,都可以保证内存存在(并且超过内存末尾的地址是一个合理的地址),否则如果您尝试错误,您将收到来自 new 的异常、来自 malloc 的 NULL 指针或未定义的行为在栈上分配。在任何情况下,您都不必检查溢出。

于 2009-04-05T09:42:59.987 回答
4

严格来说,答案是 0,因为p可能已经指向数组末尾之后的一个,这就是标准所说的有效。

在特定的实现中,您可能能够摆脱一些量,但这完全是实现定义的。已经有硬件检查 CPU 指令中指针的操作,也许现在仍然存在:如果p指向一个 2 个整数的数组,这样做p+3会导致 CPU 执行指令失败。另一方面,在大多数当前的硬件上,你可以摆脱很多。

于 2009-04-05T09:42:38.210 回答
3

根据您提供的信息,答案是 0。根据 C++ 标准,0 是唯一有效的答案。

如果您愿意冒险尝试未定义的行为(提示:不要),您必须向我们提供一些特定于平台的信息,并告别任何有关应用程序状态有效性的保证。您的应用程序可能仍在运行,但您依赖于操作系统和编译器编写者做出的任意且可能不断变化的决策。

如果我们知道您的平台的确切细节(主要是哪个 CPU、操作系统、哪个编译器),那么只要编译器或操作系统没有任何变化,就有可能给您一个通常可行的答案。

但似乎你正在以错误的方式处理这个问题。如果这对性能至关重要,就像您一直说的那样,请对其进行安排,以免指针溢出成为问题

严格来说,向指针添加任何使其指向之前指向的同一块内存的指针是未定义的。它可能会起作用,或者它可能在某些架构上表现得非常时髦。它可能会在意想不到的时候溢出,它可能会导致一些硬崩溃。这就是为什么该语言只是说“不允许”,以及为什么当你没有告诉我们它正在运行的平台时,我们不能说在实践中会发生什么。

C++ 指针不是内存地址。这就是编译器通常实现它的方式,是的,但它们遵循不同的规则。根据 CPU 指令集,有些事情对于内存地址是合法的,而对于指针是不合法的。

但说真的,我最好的建议是:退后一步,检查如何避免需要检查溢出。

于 2009-04-05T11:23:32.523 回答
2

在 C 中,指针算术和相对比较仅在“对象”中定义(不要与 C++ 对象混淆)。“对象”是一个变量(任何类型)或使用 malloc/calloc/realloc 分配的内存区域。您可以计算一个指向“过去”对象的指针,这将始终在符合标准的实现中工作。

从较低级别来看,指针通常实现为(无符号)整数。整数的大小足以容纳任何内存位置的地址。指针溢出的唯一方法是超出地址空间,而这在符合 C 标准的情况下是不可能的。

但是,如果您正在编写低级代码,并且忽略指针运算仅在对象内有效的限制,那么最好的方法是利用指针如何表示的知识。在大多数现代环境中,这与使用无符号整数算术检查溢出是一样的。

(例外情况是分段内存架构、8086 或 Multics 类型,以及我可能为了保持理智而从记忆中抑制的其他东西。)

于 2009-04-05T10:21:24.807 回答
1

在我看来,这似乎非常依赖于:

  • 操作系统
  • 底层硬件
  • 编译器

作为一项规则,并且仅据我所知,堆栈是在线性地址空间的顶部分配的。考虑到您正在运行各种运行时库,您的实际数据很可能不在该空间的顶部运行。无论哪种方式,如果您超出分配的区域,malloc您将遇到其他麻烦,如果您超出堆栈帧,您也会遇到麻烦。最重要的是,我认为您不必担心从 0xffffffffffffffff 到 0x0 的环绕,但您仍然必须确保不会超出静态、自动或手动分配的内存的界限。

于 2009-04-05T09:46:39.680 回答
0

对于地址溢出,我知道用于时间训练的有用技巧也适用于您的情况。

如果您的平台是 32 位,那么每个地址都是 32 位宽(无符号长型),您可以尝试以下宏:

#define address_after(a,b) ((long)(b) - (long)(a) < 0))

#define address_before(a,b) address_after(b,a)

然后你可以安全地比较地址为 address_after(p+i, p+m) 只有当 |mi| < 0x7FFFFFFF(这是常见的情况)。这个宏很好地处理了溢出问题。

于 2009-04-05T16:59:27.393 回答
0

这取决于两件事

  • 架构
  • 编译器

因此,唯一可以确定的方法是查阅编译器和架构的参考文档。

于 2009-04-05T09:43:23.450 回答
0

通常内存分配应该以块为单位进行,例如,即使你想使用 1 个字节,最小分配也应该始终是 2 的幂,例如,MFC 中的 CString 使用 128 个字节或 64 个字节,通过这样做你肯定会浪费一些空间,但也会减少计算量。如果您的分配是基于块完成的,那么您可以使用块大小和当前指针值来使用最大偏移量以避免溢出。

于 2009-04-05T09:51:45.723 回答