在C和C ++中,常数0被用作空指针。但是在问题““指向特定固定地址的指针”中,似乎可以使用分配固定地址。在任何系统中,是否有任何可想象的需要,无论是什么低级任务,来访问地址0?
如果有,那么如何解决0是空指针的所有问题呢?
如果没有,是什么确保没有这样的需求?
在C和C ++中,常数0被用作空指针。但是在问题““指向特定固定地址的指针”中,似乎可以使用分配固定地址。在任何系统中,是否有任何可想象的需要,无论是什么低级任务,来访问地址0?
如果有,那么如何解决0是空指针的所有问题呢?
如果没有,是什么确保没有这样的需求?
uintptr_t address = 0;
void *p = (void *) address;
请注意,这与执行以下操作不同:
void *p = 0;
后者总是产生空指针值,而前者在一般情况下则不会。前者通常会产生一个指向物理地址0
的指针,这个地址可能或可能不是给定平台上的空指针值。
void *p = (void*)0x12345678
呢?至少在某些平台上(比如我使用的 ARM 微控制器),这会将一个绝对、物理内存地址赋给指针。这通常用于 SFR(特殊功能寄存器,即内存映射 I/O)。 - jacobq顺便提一下:你可能会感兴趣地知道,使用微软的C++编译器,在32位机器上,一个空指针成员将被表示为位模式0xFFFFFFFF。也就是说:
struct foo
{
int field;
};
int foo::*pmember = 0; // 'null' member pointer
pmember将具有位模式“全为1”。这是因为您需要此值来与其他空指针值区分开来。
int foo::*pmember = &foo::field;
因为我们希望指向结构体foo的偏移量为0,所以位模式确实将是“全零位模式”。
其他C++编译器可能会选择不同的空成员指针的位模式,但关键观察点在于它不会是你可能期望的全零位模式。
你的前提是错误的。当你将一个值为0的整数常量分配给一个指针时,它就变成了一个空指针常量。然而,这并不意味着一个空指针一定指向地址0。相反,C和C++标准都非常明确地说明,空指针可能指向除零以外的某个地址。
实际上,你需要设置一个空指针所指向的地址,但这个地址可以是任何你选择的地址。当你把0转换成一个指针时,它必须指向你选择的那个地址——但这才是真正所需的。例如,如果你决定将整数转换为指针意味着将0x8000加到整数上,那么空指针实际上将指向地址0x8000而不是地址0。
值得注意的是,对空指针进行间接引用会导致未定义行为。这意味着你不能在可移植的代码中这样做,但这并不意味着你根本无法这样做。当你编写针对小型微控制器等设备的代码时,通常会包含一些不可移植的代码片段。从某个地址读取可能会给你一些传感器的值,而写入同一地址可能会激活一个步进电机(仅仅是一个例子)。下一个设备(即使使用完全相同的处理器)可能会连接到普通RAM而不是这些地址。
即使空指针确实指向地址0,这也不能阻止你使用它来读取和/或写入该地址上的任何内容——只是不能以便于移植的方式这样做——但这并不是很重要。如果地址0被解码为连接到其他不同于普通存储的东西,那么你可能无法完全地可移植地使用它。
(void *) 0
是特殊的。int x = 0; (void *) x
不是特殊的。它肯定不是可移植的,但这不是需要访问特定内存位置的人们所担心的问题。 - David Thornley如果一台机器在空指针中使用非零比特模式,则当程序员请求通过写入“0”或“NULL”来创建一个空指针时,编译器将生成该模式。因此,在内部空指针为非零的机器上将NULL定义为0与在其他任何机器上定义一样有效,因为编译器必须(并且可以)仍然针对指针上下文中看到的未装饰的0来生成机器的正确空指针。
您可以通过从非指针上下文中引用零来访问地址零。
内存地址0也被称为零页面。这是由BIOS填充的,包含关于系统上运行的硬件的信息。所有现代内核都保护该内存区域。您不应该需要访问此内存,但如果您想要访问,则需要从内核空间内进行访问,一个内核模块就可以解决问题。
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。如果没有,你就不必担心它。;)
(当然这个答案不符合标准。)
记住,在所有正常情况下,您实际上看不到特定的地址。 当您分配内存时,操作系统会向您提供该内存块的地址。
当您获取变量的引用时,变量已经在由系统确定的地址上分配。
因此,访问地址零并不是真正的问题,因为当您跟随指针时,您不关心它指向哪个地址,只要它是有效的:
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 这个问题只有在某些情况下才会成为问题,比如你正在直接访问物理内存。也许你正在编写操作系统内核或类似的东西。在这种情况下,你将要写入特定的内存地址(尤其是那些映射到硬件寄存器的地址),因此你可能需要写入地址零。但是这时你真的绕过了 C++,依赖于你的编译器和硬件平台的具体实现。
当然,如果你需要写入地址零,这是可能的。只有常量 0 表示空指针。非常量整数值零不会产生空指针,如果被分配给一个指针。
所以你可以简单地这样做:
int i = 0;
int* zeroaddr = (int*)i;
现在,zeroaddr将指向地址零(*),但严格来说它不是一个空指针,因为零值不是常量。
(*):这并非完全正确。C++标准仅保证整数和地址之间存在“实现定义的映射”。它可以将0转换为地址0x1633de20或任何其他地址。但通常映射是直观和明显的,其中整数0被映射到地址零)