常量0用作C和C ++中的空指针。但是在问题"Pointer to a specific fixed address"中似乎有一些可能用于分配固定地址。在任何系统中,对于任何低级别任务,是否有任何可能的需要来访问地址0?
如果有,那么如何解决0是空指针和所有?
如果没有,是什么让它确定没有这种需要?
答案 0 :(得分:66)
无论是在C还是在C ++中,空指针值都与物理地址0
无关。您在源代码中使用常量0
设置指向空指针值的事实只不过是一段 语法糖 即可。编译器需要将其转换为在特定平台上用作空指针值的实际物理地址。
换句话说,源代码中的0
没有任何物理重要性。例如,它可能是42
或13
。即语言作者,如果他们如此高兴,可能已经做到了这一点,你必须做p = 42
才能将指针p
设置为空指针值。同样,这并不意味着必须为空指针保留物理地址42
。编译器需要将源代码p = 42
转换为机器代码,将实际的物理空指针值(0x0000
或0xBAAD
)填充到指针p
中。这就是现在的常量0
。
另请注意,C和C ++都不提供严格定义的功能,允许您为指针分配特定的物理地址。因此,关于“如何将0地址分配给指针”的问题正式无效。您无法在C / C ++中为指针指定特定地址。但是,在实现定义的功能领域,显式的整数到指针转换旨在产生这种效果。所以,你可以按照以下方式进行操作
uintptr_t address = 0;
void *p = (void *) address;
请注意,这与执行
不同void *p = 0;
后者总是产生空指针值,而前者通常不会产生空指针值。前者通常会生成一个指向物理地址0
的指针,它可能是也可能不是给定平台上的空指针值。
答案 1 :(得分:18)
切线说明:您可能有兴趣知道使用Microsoft的C ++编译器,成员的NULL指针将表示为32位计算机上的位模式0xFFFFFFFF。那就是:
struct foo
{
int field;
};
int foo::*pmember = 0; // 'null' member pointer
pmember将具有“全部”的位模式。这是因为您需要此值来区分
int foo::*pmember = &foo::field;
其中位模式确实是'全零' - 因为我们希望将偏移量0放入结构foo中。
其他C ++编译器可能会为成员的空指针选择不同的位模式,但关键的观察是它不会是您可能期望的全零位模式。
答案 2 :(得分:12)
你是从一个错误的前提出发的。为指针指定值为0的整型常量时,变为空指针常量。这样做不,但是,意味着空指针必然引用地址0.恰恰相反,C和C ++标准都非常清楚,空指针可能引用到零以外的某个地址。
它归结为:你做必须留出一个空指针引用的地址 - 但它实际上可以是你选择的任何地址。当你将零转换为指针时,它必须引用所选择的地址 - 但这就是所有真正需要的地址。例如,如果您决定将整数转换为某个点意味着将0x8000添加到整数,则空指针实际上将引用地址0x8000而不是地址0。
还值得注意的是,取消引用空指针会导致未定义的行为。这意味着您无法在便携式代码中执行此操作,但不意味着您根本无法执行此操作。当您为小型微控制器等编写代码时,包含一些根本不可移植的代码片段是相当常见的。从一个地址读取可能会从某个传感器中获取值,而写入相同的地址可能会激活步进电机(例如)。下一个设备(甚至使用完全相同的处理器)可能已连接,因此这两个地址都改为普通RAM。
即使空指针 引用地址0,也不会阻止您使用它来读取和/或写入发生在该地址的任何内容 - 它只会阻止您这样做便携式 - 但这并不重要。地址零通常很重要的唯一原因是,如果它被解码为连接到正常存储以外的其他东西,那么你可能无论如何都不能完全移植它。
答案 3 :(得分:9)
编译器会为您解决此问题(comp.lang.c FAQ):
如果机器对空指针使用非零位模式,编译器有责任在程序员通过写入“0”或“NULL”空请求时请求它。因此,在内部空指针非零的机器上将#defining NULL设置为0与任何其他指针一样有效,因为编译器必须(并且可以)仍然生成机器的正确空指针以响应在指针上下文中看到的未修饰的0。
您可以通过从非指针上下文引用零来获得零地址。
答案 4 :(得分:7)
实际上,C编译器很乐意让你的程序尝试写入地址0.在运行时检查每个指针操作的NULL指针会有点贵。在计算机上,程序将崩溃,因为操作系统禁止它。在没有内存保护的嵌入式系统上,程序确实会写入地址0,这通常会导致整个系统崩溃。
地址0在嵌入式系统上可能很有用(一个不在计算机中的CPU的通用术语;它们可以运行从立体声到数码相机的所有内容)。通常,系统的设计使您不需要写入地址0.在我知道的每种情况下,它都是某种特殊地址。即使程序员需要写入它(例如,设置中断表),他们只需要在初始引导序列期间写入它(通常是一小段汇编语言来为C设置环境)。
答案 5 :(得分:6)
在x86上,地址0(或更确切地说,0000:0000)及其附近的实模式是中断向量的位置。在过去的糟糕时期,您通常会将值写入中断向量以安装中断程序(或者如果您更加自律,则使用MS-DOS服务0x25)。 MS-DOS的C编译器定义了一个far指针类型,当指定为NULL或0时,它将在其段部分中接收位模式0000,在其偏移部分中接收0000。
当然,一个行为不端的程序意外地写入了一个值为0000:0000的远指针会导致机器上发生非常糟糕的事情,通常将其锁定并强制重启。
答案 6 :(得分:6)
内存地址0也称为Zero Page。这由BIOS填充,包含有关系统上运行的硬件的信息。所有现代内核都保护着这个内存区域。您永远不需要访问此内存,但如果您需要在内核中执行此操作,则内核模块将执行此操作。
答案 7 :(得分:5)
在链接的问题中,人们正在讨论在微控制器中设置固定地址的问题。当你对微控制器进行编程时,那里的所有东西都要低得多。
你甚至没有台式机/服务器PC的操作系统,也没有虚拟内存和那些东西。因此,可以访问特定地址的内存,甚至是必要的。在现代的桌面/服务器PC上它是无用的,甚至是危险的。
答案 8 :(得分:3)
我使用gcc为摩托罗拉HC11编译了一些代码,它没有MMU,0是一个非常好的地址,并且很惊讶地发现要写入地址0,你只需写信给它。 NULL和地址0之间没有区别。
我明白为什么。我的意思是,在每个内存位置都可能有效的架构上定义一个唯一的NULL是不可能的,所以我猜gcc作者只是说0对于NULL来说是否足够好,无论它是否是有效地址。
char *null = 0;
; Clears 8-bit AR and BR and stores it as a 16-bit pointer on the stack.
; The stack pointer, ironically, is stored at address 0.
1b: 4f clra
1c: 5f clrb
1d: de 00 ldx *0 <main>
1f: ed 05 std 5,x
当我将它与另一个指针进行比较时,编译器会生成常规比较。这意味着它绝不会将char *null = 0
视为特殊的NULL指针,实际上指向地址0和“NULL”指针的指针将是相等的。
; addr is a pointer stored at 7,x (offset of 7 from the address in XR) and
; the "NULL" pointer is at 5,y (offset of 5 from the address in YR). It doesn't
; treat the so-called NULL pointer as a special pointer, which is not standards
; compliant as far as I know.
37: de 00 ldx *0 <main>
39: ec 07 ldd 7,x
3b: 18 de 00 ldy *0 <main>
3e: cd a3 05 cpd 5,y
41: 26 10 bne 53 <.LM7>
所以为了解决原始问题,我想我的答案是检查你的编译器实现,并找出他们是否甚至不愿意实现一个唯一值NULL。如果没有,您不必担心。 ;)
(当然这个答案不符合标准。)
答案 9 :(得分:1)
这完全取决于机器是否具有虚拟内存。带有它的系统通常会在那里放置一个不可写的页面,这可能是你习惯的行为。然而,在没有它的系统中(现在通常是微控制器,但它们过去常常比较常见),那么在该区域中通常会有非常有趣的事情,例如中断表。我记得在8位系统时代就已经把这些东西搞砸了;很有趣,而且当你不得不重置系统并重新开始时,太太痛苦了。 : - )
答案 10 :(得分:1)
是的,您可能想要访问内存地址0x0h。 为什么你想要这样做是依赖于平台的。处理器可能将此用作复位向量,以便写入它会导致CPU复位。它也可以用作中断向量,作为某些硬件资源(程序计数器,系统时钟等)的存储器映射接口,或者它甚至可以作为普通的旧存储器地址有效。关于内存地址为零,没有什么必然是神奇的,它只是历史上用于特殊目的的一个(重置向量等)。类似C语言遵循这一传统,使用零作为NULL指针的地址,但实际上底层硬件可能会或可能不会将地址零视为特殊。
访问地址零的需求通常仅出现在引导加载程序或驱动程序等低级细节中。在这些情况下,编译器可以提供选项/编译指示来编译一段代码而不进行优化(以防止将零指针作为NULL指针提取出来),或者可以使用内联汇编来访问真正的地址零。
答案 11 :(得分:0)
C / C ++不允许您写入任何地址。当用户访问某个禁止的地址时,操作系统可以发出信号。 C和C ++确保从堆中获得的任何内存都不同于0。
答案 12 :(得分:0)
如果用户违反某些必要条件并且没有任何好处,我有时会使用地址为零的加载(在已知平台上保证会出现段错误)故意在库代码中的信息命名符号崩溃向我提出异常的方法。 “Segfault at someFunction$xWasnt16ByteAligned
”是一个非常有效的错误消息,提醒某人他们做错了什么以及如何修复它。也就是说,我不建议养成这种习惯。
答案 13 :(得分:0)
如果我没记错的话,在AVR微控制器中,寄存器文件被映射到RAM的地址空间,寄存器R0被映射到地址0x00。这显然是有目的的,显然Atmel认为有些情况下,方便访问地址0x00而不是明确写入R0。
在程序存储器中,在地址0x0000处有一个复位中断向量,并且在编程芯片时,该地址显然是被访问的。
答案 14 :(得分:0)
写入地址为零可以完成,但这取决于几个因素,例如您的操作系统,目标架构和MMU配置。实际上,它可以是一个有用的调试工具(但并非总是如此)。
例如,几年前在使用嵌入式系统(可用的调试工具很少)时,我们遇到了导致热重启的问题。为了帮助找到问题,我们使用 sprintf(NULL,...); 和9600波特串行电缆进行调试。正如我所说 - 很少有调试工具可用。通过我们的设置,我们知道热重启不会破坏前256字节的内存。因此,在热重启之后,我们可以暂停加载器并转储内存内容以找出重启之前发生的事情。
答案 15 :(得分:0)
请记住,在所有正常情况下,您实际上并未看到特定地址。 当您分配内存时,操作系统会为您提供该内存块的地址。
当您获取变量的引用时,该变量已在系统确定的地址处分配。
因此,访问地址零并不是一个真正的问题,因为当你按照指针时,你不关心它指向的地址,只关注它是有效的:
int* i = new int(); // suppose this returns a pointer to address zero
*i = 42; // now we're accessing address zero, writing the value 42 to it
所以如果你需要访问地址零,它通常会正常工作。
如果由于某种原因你直接访问物理内存,那么0 == null的东西才真正成为一个问题。也许你正在编写一个OS内核或类似的东西。在这种情况下,您将要写入特定的内存地址(尤其是那些映射到硬件寄存器的地址),因此您可能需要写入地址为零。但是你真的绕过了C ++并依赖于你的编译器和硬件平台的具体细节。
当然,如果你需要写入地址为零,那是可能的。只有常量 0
表示空指针。如果赋给指针,非常量整数值零将不会产生空指针。
所以你可以简单地做这样的事情:
int i = 0;
int* zeroaddr = (int*)i;
现在zeroaddr将指向地址零(*),但严格来说,它不会是一个空指针,因为零值不是常数。
(*):那不是完全是真的。 C ++标准仅保证整数和地址之间的“实现定义映射”。它可以将0转换为地址0x1633de20`或其喜欢的任何其他地址。但是映射通常是直观且明显的,其中整数0映射到地址零)
答案 16 :(得分:-1)
它可能让很多人感到惊讶,但在核心C语言中没有特殊的空指针。如果在物理上可行,您可以完全自由地读取和写入地址0.
下面的代码甚至没有编译,因为没有定义NULL:
int main(int argc, char *argv[])
{
void *p = NULL;
return 0;
}
OTOH,下面的代码编译,如果硬件/操作系统允许,你可以读写地址0:
int main(int argc, char *argv[])
{
int *p = 0;
*p = 42;
int x = *p; /* let's assume C99 */
}
请注意,我在上面的例子中没有包含任何内容。
如果我们开始从标准C库中包含东西,那么就会神奇地定义NULL。据我记得,它来自string.h
。
NULL仍然不是核心C特性,它是许多C库函数的约定,用于指示指针的无效性。给定平台上的C库将NULL定义为无论如何都无法访问的内存位置。我们在Linux PC上试试吧:
#include <stdio.h>
int main(int argc, char *argv[])
{
int *p = NULL;
printf("NULL is address %p\n", p);
printf("Contents of address NULL is %d\n", *p);
return 0;
}
结果是:
NULL is address 0x0
Segmentation fault (core dumped)
所以我们的C库定义了NULL来解决零问题,结果证明它是不可访问的。
但它不是C编译器,甚至不是专门处理零地址的C库函数printf()
。他们都乐意尝试正常使用它。当printf
试图从地址零读取时,操作系统检测到了分段错误。