我可以想要访问地址零?

常量0用作C和C ++中的空指针。 但是在“指向特定的固定地址的指针”的问题中,似乎有一些可能的用途来分配固定的地址。 在任何系统中,对于访问地址0,是否有任何可能的低需求任务?

如果存在,那么0是空指针又是如何解决的呢?

如果不是的话,是什么使得确定没有这样的需求?

C和C ++中的空指针值都不以任何方式绑定到物理地址0在源代码中使用常数0来设置指针指向空指针值的事实不过是一个语法糖 。 编译器需要将其转换为在特定平台上用作空指针值的实际物理地址。

换句话说,源代码中的0没有任何物理重要性。 例如,它可能是4213 。 也就是说,语言作者,如果他们如此高兴,可以做到这一点,你必须做p = 42 ,以便将指针p设置为空指针值。 同样,这并不意味着物理地址42必须为空指针保留。 编译器需要将源代码p = 42转换成机器代码,将实际的物理空指针值( 0x00000xBAAD0xBAAD到指针p 。 这正是现在如何与常数0

还要注意的是,C和C ++都不提供一个严格定义的特性,允许你为一个指针指定一个特定的物理地址。 所以你关于“如何将0地址赋给指针”的问题没有答案。 你不能指定一个特定的地址给C / C ++中的指针。 但是,在实现定义的特性领域,显式的整数到指针转换是为了达到这个效果。 所以,你会这样做

 uintptr_t address = 0; void *p = (void *) address; 

请注意,这和做不一样

 void *p = 0; 

后者总是产生空指针值,而前者通常不会。 前者通常会产生一个指向物理地址0的指针,该指针可能是也可能不是给定平台上的空指针值。

在切记:您可能有兴趣知道,使用Microsoft的C ++编译器,在32位机器上,成员的NULL指针将表示为位模式0xFFFFFFFF。 那是:

 struct foo { int field; }; int foo::*pmember = 0; // 'null' member pointer 

会员将拥有“全部”的位模式。 这是因为你需要这个值来区分它

 int foo::*pmember = &foo::field; 

其中位模式确实是“全零” – 因为我们想要将0抵消到结构foo中。

其他C ++编译器可能会为成员的空指针select不同的位模式,但关键的观察是,它不会是您可能期望的全零位模式。

你从一个错误的前提开始。 当您将一个整数常量赋值为0的指针指向一个空指针常量。 然而,这并不意味着空指针必然指向地址0.恰恰相反,C和C ++标准都非常清楚,空指针可能指向除零之外的某个地址。

它涉及到的是:你必须放置一个空指针所指的地址 – 但它可以是任何你select的地址。 当您将零转换为指针时,必须引用该选定的地址 – 但这只是真正需要的。 举例来说,如果您决定将整数转换为一个点意味着将0x8000添加到整数,则空指针实际上将指向地址0x8000而不是地址0。

还值得注意的是,解引用空指针会导致未定义的行为。 这意味着你不能用便携式代码来做,但这并不意味着你根本就做不到。 在为小型微控制器编写代码时,包含一些根本不可移植的代码是相当常见的。 从一个地址读取可能会给你一些传感器的值,而写入相同的地址可能激活一个步进电机(例如)。 下一个设备(甚至使用完全相同的处理器)可能会被连接起来,所以这两个地址都被称为普通RAM。

即使空指针确实指向地址0,也不能阻止你使用它读取和/或写入在该地址发生的任何事情 – 它只是阻止你这么做,而不能真的很重要。 地址零的唯一原因通常是重要的,如果它被解码连接到非正常存储的东西,所以你可能无法完全移植它。

编译器为你处理( comp.lang.c FAQ ):

如果一台机器对空指针使用非零位模式,编程人员在编程人员请求时通过写入“0”或“NULL”来产生一个空指针。 因此,在内部空指针非零的机器上将NULL定义为0与其他的一样有效,因为编译器必须(并且可以)仍然生成机器正确的空指针,以响应在指针上下文中看到的未修改的0。

您可以通过从非指针上下文中引用零来得到零地址。

在实践中,C编译器会高兴地让你的程序尝试写入地址0.在运行时检查每个指针操作的空指针会花费很多。 在电脑上,程序会因操作系统禁止而崩溃。 在没有内存保护的embedded式系统上,程序确实会写入地址0,这往往会使整个系统崩溃。

地址0可能对embedded式系统(一种不在计算机中的CPU的通用术语;它们运行从立体声到数码相机的所有内容)有用。 通常情况下,系统的devise使你不需要写地址0.在任何情况下,我知道这是一种特殊的地址。 即使程序员需要写入(例如设置一个中断表),他们只需要在初始引导序列(通常是一些简单的汇编语言来为C语言设置环境)中写入。

内存地址0也被称为零页 。 这由BIOS填充,并包含有关系统上运行的硬件的信息。 所有现代的内核都保护这个区域的内存。 你永远不需要访问这个内存,但是如果你想要你从内核的地方做到这一点,一个内核模块将会做到这一点。

在x86上,地址0(更确切地说是0000:0000)及其实模式附近是中断向量的位置。 在过去不好的时候,你通常会把值写入中断向量来安装中断handers(或者如果你更严格的话,使用MS-DOS服务0x25)。 用于MS-DOS的C编译器定义了一个远指针types,当指定为NULL或0时,它将在其部分部分接收位模式0000,并在其偏移部分接收0000。

当然,一个意外地写到一个远指针的值为0000:0000的行为exception的程序会在机器上发生非常糟糕的事情,通常是将其locking并强制重启。

在链接的问题中,人们正在讨论在微控制器中设置固定地址。 当你编程一个微控制器的时候,一切都在一个很低的水平上。

你甚至没有在桌面/服务器电脑方面的操作系统,你没有虚拟内存和东西。 所以,在特定的地址访问内存是可以的,甚至是必要的。 在现代台式机/服务器PC上,这是无用的,甚至是危险的。

我用摩托罗拉HC11编译了一些代码,它没有MMU,0是一个非常好的地址,并且很遗憾地发现写入地址0时,你只需写信给它。 NULL和地址0没有区别。

我可以看到为什么。 我的意思是,在每个内存位置可能有效的体系结构上定义一个唯一的NULL是不可能的,所以我猜测gcc作者只是说NULL对于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。 如果没有,你不必担心。 ;)

(当然这个答案不符合标准。)

这完全取决于机器是否具有虚拟内存。 带有它的系统通常会在那里放置一个不可写的页面,这可能是您习惯的行为。 然而,在没有它的系统中(通常是微控制器,但过去常常是这样),那么在这个领域通常会有非常有趣的事情,例如中断表。 我记得在8位系统的日子里,这些东西被黑客窃取了。 有趣的,当你不得不硬重置系统并重新开始时,不要痛苦。 🙂

是的,你可能想要访问内存地址0x0h。 为什么你会想这样做是平台依赖的。 处理器可能会将其用于复位向量,从而写入该复位向量会导致CPU复位。 它也可以用作中断向量,作为某些硬件资源(程序计数器,系统时钟等)的内存映射接口,或者甚至可以作为一个普通的旧内存地址有效。 内存地址零不一定是神奇的,它只是历史上用于特殊目的(复位向量等)的一个。 C类语言遵循这个传统,使用零作为NULL指针的地址,但实际上底层硬件可能会或可能不会将地址零看作是特殊的。

访问零地址的需求通常只在像引导程序或驱动程序这样的低级细节中出现。 在这些情况下,编译器可以提供选项/编译指示来编译一段代码而不进行优化(以防止将零指针作为NULL指针提取出去),或者使用内联汇编来访问真正的地址零。

C / C ++不允许你写任何地址。 当用户访问某个禁止的地址时,可以提高信号的操作系统。 C和C ++确保从堆中获得的任何内存将不同于0。

我有时使用地址为零的负载(在一个已知的平台上,这将确保段错误)故意崩溃在图书馆代码中的信息命名的符号,如果用户违反了一些必要的条件,没有任何好方法扔一个例外可以给我。 “ Segfault at someFunction$xWasnt16ByteAligned ”是一个相当有效的错误信息,提醒某人他们做错了什么,以及如何解决它。 也就是说,我不会build议养成这样的习惯。

写入地址为零可以完成,但这取决于几个因素,如操作系统,目标体系结构和MMUconfiguration。 实际上,它可以是一个有用的debugging工具(但并不总是)。

例如,几年前,在开发embedded式系统时(使用less量的debugging工具),我们遇到了一个导致热重启的问题。 为了帮助find问题,我们正在使用sprintf(NULL,…)进行debugging; 和一个9600波特的串行电缆。 正如我所说 – 可用的debugging工具很less。 通过我们的设置,我们知道热重启不会损坏前256个字节的内存。 因此,在暖启动之后,我们可以暂停加载程序并转储内存以查找重新启动之前发生的事情。

请记住,在所有正常情况下,您实际上并没有看到特定的地址。 当你分配内存的时候,操作系统提供给你这块内存的地址。

当你引用一个variables时,这个variables已经被分配到一个由系统决定的地址。

所以访问地址零并不是一个真正的问题,因为当你按照一个指针,你不关心它指向的地址,只是它是有效的:

 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映射到地址零)

这可能让很多人感到惊讶,但是在核心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试图从地址0读取时,是检测到分段错误的操作系统。

如果我没有记错,在一个AVR微控制器中,寄存器文件被映射到RAM的地址空间,寄存器R0在地址0x00。 这显然是有目的的,显然Atmel认为有些情况下,访问地址0x00比较方便,而不是明确写入R0。

在程序存储器中,在地址0x0000处有一个复位中断向量,并且在对芯片进行编程时,该地址显然也是要访问的。