在 C 中取消引用指向 0 的指针
Posted
技术标签:
【中文标题】在 C 中取消引用指向 0 的指针【英文标题】:Dereferencing a pointer to 0 in C 【发布时间】:2014-02-01 23:46:28 【问题描述】:有时内存地址 0x0 处的数据非常有价值——以 x86 实模式 IVT 为例:它从 0x0 开始并包含指向中断处理程序的指针:0x00 处的 dword 是指向除以零错误处理程序的指针。
但是,C11 语言标准禁止取消引用空指针[WG14 N15706.5.3.2],这些空指针定义为用0 初始化的指针或用空指针[WG14 N15706.3.2.3] 初始化的指针,有效地禁止了第一个字节。
人们如何在需要时实际使用 0x0?
【问题讨论】:
你指的是C++11吗?该标准明确指出 nullptr 根本不是整数。 IE nullptr != 0x0. 能否请您参考 C11 规范的相关部分? @GreenAsJade 不,这里不是 C++,只是普通 C。 空指针是从(void *)0
等表达式中得到的指针,但它不一定与指向地址零的指针相同。
@alvits 在实模式(16 位模式)下,没有。实模式下没有用户空间和内核空间的分离。
【参考方案1】:
C 不禁止取消引用空指针,它只是使它成为未定义的行为。
如果您的环境可以取消引用包含地址0x0
的指针,那么您应该可以这样做。 C 语言标准没有说明这样做时会发生什么。 (在大多数环境中,结果将是程序崩溃。)
一个具体的例子(如果我没记错的话):在基于 68k 的 Sun 3 计算机上,取消引用空指针不会导致陷阱;相反,操作系统将零值存储在内存地址零处,并且取消引用空指针(指向地址零)将产生该零值。这意味着,例如,C 程序可以将空指针视为指向空字符串的有效指针。某些软件有意或无意地依赖于这种行为。在将软件移植到基于 SPARC 的 Sun 4 时,这需要大量的清理工作,这会陷入空指针取消引用。 (我清楚地记得读过这篇文章,但我找不到参考资料;如果我能找到它,我会更新它。)
注意,空指针不一定地址为零。更准确地说,空值的表示可能是也可能不是全位为零。它很常见,但不能保证。 (如果不是,那么(void*)0
的整数到指针的转换是不平凡的。)
comp.lang.c FAQ 的第 5 节讨论了空指针。
【讨论】:
实际上这让我想知道,UB 是否曾经为指针分配任意数字并取消引用? comp.lang.c 常见问题解答中的答案看起来有点过于繁琐:是的,正式他们没有将 0 分配给指针,但他们的精神是用零填充空间而且,正如您所指出的,这并不总是空指针表示。 我认为在大多数情况下,它会调用未定义的行为,因为内存的较低部分是为操作系统的子例程(中断服务例程)的地址保留的。 @gfv 定义的实现是事实上的 UB,除非没有恶魔通过鼻子的可能性 @MikeWarren:不一定。行为是未定义的,这特别意味着,就 C 标准而言,任何事情都可能发生; “糟糕的时光”并不是唯一的可能性。一些系统在地址 0 处有一个可读值 0(这导致了为此类系统编写的大量有趣的移植程序到更严格的系统,这些系统被解除引用空指针所困)。【参考方案2】:人们如何在需要时实际使用 0x0?
通过:
用汇编语言编写所需的代码,或者 用 C 编写代码并验证其编译器是否为所需操作生成正确的汇编语言【讨论】:
当指针为空或地址为 0x0 时,它是否在物理上指向 0x0 地址?即当我们认为操作系统具有虚拟内存概念时? @Koushik:不,虚拟内存意味着特定进程地址空间中的地址0x0不一定指向物理地址0x0。 如果您正在使用只有物理内存的嵌入式系统,那么是的,它确实指向地址 0x0。在 Keith Thompson 引用的示例中,如果内存物理上不存在,则 MC68xxx 系列 CPU 将引发总线错误(异常) 糟糕 - 评论编辑超时:MC68xxx 系统中 0x0 处的内存必须存在,因为这是重置向量所在的位置。上电时,CPU 将从 0x0000000..0x000003 获取 32 位值并将其加载到堆栈指针中,然后从 0x0000004..0x000007 获取 32 位并将该值用作初始指令指针......然后关闭到它将参加的比赛。【参考方案3】:声明:
char * x = 0;
不一定将 0x0 放入 x。它将为当前架构和编译器定义的空指针值放入 x 中。
现在,实际上,观察到的所有常用编译器/处理器最终都会将 32(或 64)个 0 位连续放入寄存器或存储位置以响应该语句,因此,如果内存地址为 0是有用的,然后,正如其他人所指出的那样,您会陷入使用正式未定义的行为。然而,曾几何时,有一个硬件的“空指针”是某种位模式,不是全为零,谁知道呢,可能还会有。
【讨论】:
我多年前使用的(罗技,我认为)Modula-2 的实现将 NIL 指针实现为 FFFF:FFFF(分段 16 位土地)。当然,它不是 C,而且规则也不同(即,你不能只做if (p) ...
)。
@Greg 有趣的未定义行为! FFFF:FFFF
比 0000:0000
更具情境性,因为它可能被解释为线性地址 10FFEF
或 00FFEF
,具体取决于地址位 20 是否启用,并且踩在这些位置中的任何一个位置可能会导致两种不同的各种麻烦。
@JeffreyHantin:不仅如此,在该地址读取(或写入)超过一个字节会导致各种怪异。
@GregHewgill 当然 if (p)
会起作用,因为它不会测试 0 模式,但确实会测试 NULL
指针模式的存在(或不存在)。跨度>
@glglgl:当然可以,但我的意思是if (p)
(与NULL
或nullptr
进行隐式比较)不是有效的Modula- 2 语法,等价的必须是IF p # NIL
,其中比较是显式。【参考方案4】:
附件 J 当...时是未定义的行为
一元 * 运算符的操作数具有无效值 (6.5.3.2)。
在您提到的同一个脚注中,它说空指针是无效值。因此,它不是被禁止的,而是未定义的行为。至于地址0x0
和空指针的区别,见Is memory address 0x0 usable?。
空指针不一定是地址 0x0,因此可能是 架构可以选择另一个地址来表示空 指针,您可以从 new 中获取 0x0 作为有效地址。
空指针是由操作系统保留还是 C++ 实现未指定,但普通的 new 永远不会返回 空指针,无论它的地址是什么(nothrow new 是不同的 兽)。所以,回答你的问题:
内存地址0x0可用吗?
也许,这取决于特定的实现/架构。
换句话说,如果您确定系统不会导致崩溃,请随时使用0x0
。
【讨论】:
正式地,未定义的行为可以包括使用 0x0,就像它是正常的记忆一样,但是依赖未定义的行为在未来可能会很痛苦。 @gfv 重要的是有一个区别。使用0x0
是否安全取决于具体情况。【参考方案5】:
操作系统使用指向中断例程的指针表来调用适当的中断。通常(在大多数操作系统中)指针表存储在低位内存中(前几百左右的位置),这些位置保存各种设备的中断服务程序的地址。
所以当你这样做时
char *ptr = 0x0;
那么您很可能正在使用中断服务例程的地址来初始化您的指针。取消引用(或修改)属于操作系统的内存位置最有可能导致程序崩溃。
因此,最好不要初始化指向0x0
的指针并取消引用它,直到您确认它不属于操作系统。
【讨论】:
如果你真的在写操作系统怎么办?你仍然需要一种方法来做这种事情。 @GregHewgill;真的。但一般情况下,您不能取消引用属于操作系统的地址。 内核空间和用户空间不是分开了吗? @hacks - 请赐教。如果应用程序在用户空间中运行,地址 0x0 不是相对于用户空间基地址吗? @alvits;我希望你很了解Dual-Mode-Operation,即内核模式和用户模式。当您运行应用程序时,您的系统处于用户模式。当它请求系统调用时,会发生从用户模式到内核模式的转换以完成请求。以上是关于在 C 中取消引用指向 0 的指针的主要内容,如果未能解决你的问题,请参考以下文章