博客作者提出了有关空指针解除引用的讨论:
我在这里提出了一些反驳论点:
他引用标准的主要推理理由是:
'& podhd-> line6'表达式是C语言中未定义的行为 当'podhd'是空指针时。
C99标准说明了以下'&'运营商地址 (6.5.3.2“地址和间接操作符”):
一元&的操作数运算符应该是一个函数 指示符,[]或一元*运算符的结果,或者左值 指定一个不是位字段且未声明的对象 寄存器存储类说明符。
表达式'podhd-> line6'显然不是函数指示符, []或*运算符的结果。这是一个左值表达式。然而, 当'podhd'指针为NULL时,表达式不指定 对象自6.3.2.3“指针”说:
如果将空指针常量转换为指针类型,则 结果指针,称为空指针,保证比较 不等于指向任何对象或函数的指针。
当“左值”在评估时没有指定对象时, 行为未定义“(C99 6.3.2.1”左值,数组和函数 标示符“):
左值是具有对象类型或不完整类型的表达式 除了虚空;如果一个左值没有指定一个对象 评估时,行为未定义。
所以,同样的想法简要说明:
当 - >在指针上执行,它被评估为左值 没有对象存在,因此行为未定义。
这个问题纯粹是基于语言的,我不会问一个给定的系统是否允许用任何语言篡改地址0的内容。
据我所知,取消引用其值等于nullptr
的指针变量没有限制,甚至认为指针与nullptr
(或(void *) 0
)的比较由于所述段落,常量可以在某些情况下在优化中消失,但这看起来像另一个问题,它不会阻止取消引用其值等于nullptr
的指针。请注意,我已经检查过其他SO问题和答案,特别是like this set of quotations,以及上面的标准引语,我没有偶然发现一些明显可以从标准中推断出的指针ptr
比较等于nullptr
,取消引用它将是未定义的行为。
我得到的最多是将常量(或者它的强制转换为任何指针类型)引用到什么是UB,但没有任何关于变量等于来自的值的变量nullptr
。
我想清楚地将nullptr
常量与保持值等于它的指针变量分开。但解决这两种情况的答案都是理想的。
我确实意识到,当与nullptr
等进行比较时,可以快速进行优化,并且可以简单地基于此来删除代码。
如果得出的结论是,如果ptr
等于nullptr
取消引用它的值肯定是UB,那么另一个问题如下:
答案 0 :(得分:11)
当你引用C时,解除引用空指针显然是来自此标准引用的未定义行为(强调我的):
(C11,6.5.3.2p4)"如果为指针指定了无效值, 一元*运算符的行为未定义 .102)"
102):" unary *运算符解除引用指针的无效值是空指针,地址与指向的对象类型不一致,地址和地址一个物体在其生命周期结束后的状态。"
C99中的相同引用与C89 / C90中的相似。
答案 1 :(得分:3)
<强> C ++ 强>
dcl.ref / 5。
不应引用引用,不引用引用数组,也不引用引用指针。该 引用声明应包含初始值设定项(8.5.3),除非声明包含显式 extern说明符(7.1.1),是类定义中的类成员(9.2)声明,或者是声明 参数或返回类型(8.3.5);见3.1。引用应初始化为引用有效对象或 功能。 [注意:特别是,在明确定义的程序中不能存在空引用,因为是唯一的方法 创建这样的引用将是绑定到通过空指针间接获得的“对象”, 导致未定义的行为。如9.6中所述,引用不能直接绑定到位字段。 - 结束说明]
该注释很有意义,因为它明确指出取消引用空指针是未定义的。
我确定它在更相关的背景下的其他地方说,但这已经足够了。
答案 2 :(得分:1)
我看到的答案是,关于NULL值可以被取消引用的程度,它是故意以未指定的方式依赖于平台,因为C11 6.3.2.3p5中的实现定义和P6。这主要是为了支持用于开发平台启动代码的独立实现,正如OP在其反驳链接中指出的那样,但也有托管实现的应用程序。
回复:
(C11,6.5.3.2p4)&#34;如果为指针指定了无效值,则一元*运算符的行为未定义.102)&#34;
102):&#34;由一元*运算符解除引用指针的无效值是空指针,一个地址与指向的对象类型不对齐,以及一个对象的地址结束后它的一生。&#34;
这是表达原样,因为脚注中的每个案例对于编译器所针对的特定平台可能都不是无效的。如果那里存在缺陷,那么它的价值就会无效&#34;应该用'#34;实现定义&#34;来斜体化和限定。对于对齐情况,平台可以使用任何地址访问任何类型,因此没有对齐要求,尤其是在支持地址翻转的情况下;并且平台可以假设对象的生命周期仅在应用程序退出后结束,通过malloc()为每个函数调用分配一个新帧用于自动变量。
对于空指针,在启动时,平台可能期望处理器使用的结构具有特定的物理地址,包括在地址0,并且在源代码中被表示为对象指针,或者可能需要定义引导过程的函数使用基地址为0.如果标准不允许取消引用,例如&#39;&amp; podhd-&gt; line6&#39;,其中平台要求podhd的基地址为0,那么汇编语言会需要访问该结构。类似地,软重启函数可能需要将0值指针取消引用作为void函数调用。托管实现可以认为0是可执行映像的基础,并且在加载之后将源代码中的NULL指针映射到该映像的头部,因为该结构需要处于该虚拟机的该实例的逻辑地址0处。
标准调用指针的是虚拟机虚拟地址空间中的更多句柄,其中对象句柄对允许的操作有更多要求。编译器如何发出代码,将特定处理器的这些句柄的要求考虑在内,这是未定义的。毕竟,一个处理器的效率可能不是另一个处理器。
对(void *)0的要求更多的是编译器发出的代码保证源使用(void *)0的表达式,显式地或通过引用NULL,存储的实际值将是这样的&# 39; t指向任何映射代码的任何有效函数定义或对象。这不一定是0!类似地,对于(void *)0到(obj_type)和(func_type)的强制类型转换,只需要获取分配的值,这些值将作为地址进行评估,编译器保证不会再使用对象或代码。与后者的区别在于它们是未使用的,而不是无效的,因此能够以定义的方式被解除引用。
测试指针相等性的代码然后将检查一个操作数是否是这些值中的一个,另一个是3中的一个,而不仅仅是相同的位模式,因为这个记分板的RTTI为a(null * )type,与void,obj和func指针类型区别于已定义的实体。标准可以更明确它是一个独特的类型,如果未命名,因为编译器只在内部使用它,但我想这被#34;空指针&#34;斜体。实际上,imo,a&#39; 0&#39;在这些上下文中是编译器的附加关键字标记,因为它需要识别(null *)类型,但是没有这样表征,因为这会使&lt;的定义变得复杂。标识符&gt;。
这个存储的值可以是SIZE_MAX,就像a(void *)0一样,在发出的应用程序代码中,例如,在实现时定义虚拟机的范围0到SIZE_MAX-4 * sizeof(void *)处理对代码和数据有效的处理。 NULL宏甚至可以定义为
(void *)SIZE_MAX,并且编译器要从上下文中找出它具有与0相同的语义。转换代码负责注意它是被选中的值,在指针&lt; - &gt;中指针强制转换,并提供适合作为对象或函数指针的指针。从指针&lt; - &gt;转换整数,隐式或显式,具有类似的检查和供应要求;特别是在(u)intptr_t字段覆盖( type *)字段的联合中。可移植代码可以防止编译器使用显式*(ptr == NULL?( type *)0:ptr)表达式来正确执行此操作。