重新定义NULL


118

我正在为地址0x0000有效且包含端口I / O的系统编写C代码。因此,访问NULL指针的任何可能的错误都将保持未被检测到的状态,同时会导致危险的行为。

出于这个原因,我希望将NULL重新定义为另一个地址,例如无效的地址。如果我不小心访问了这样的地址,我将得到一个硬件中断,可以处理该错误。我恰好可以访问此编译器的stddef.h,因此实际上可以更改标准标头并重新定义NULL。

我的问题是:这会与C标准冲突吗?据我从标准7.17可以看出,该宏是实现定义的。标准中是否有其他地方声明NULL 必须为0?

另一个问题是,无论数据类型如何,大量编译器都将所有内容都设置为零,从而执行静态初始化。即使该标准规定编译器应将整数设置为零,并将指针设置为NULL。如果我为编译器重新定义NULL,那么我知道这种静态初始化将失败。即使我大胆地手动更改了编译器标头,我也可以认为这是不正确的编译器行为吗?因为我可以肯定地知道该特定的编译器在执行静态初始化时不会访问NULL宏。


3
这是一个非常好的问题。我没有答案,但是我不得不问:您确定无法将有效内容移到0x00并让NULL是无效的地址,例如在“正常”系统中吗?如果不能,那么唯一可以使用的安全无效地址就是可以确定可以分配并mprotect确保安全的地址。或者,如果平台没有ASLR等,则地址超出平台物理内存。祝好运。
Borealid 2011年

8
如果您的代码正在使用,它将如何工作if(ptr) { /* do something on ptr*/ }?如果将NULL定义为与0x0不同的值,它将起作用吗?
Xavier T.

3
C指针与内存地址没有强制关系。只要遵守指针算术规则,指针值就可以是任何值。大多数实现选择使用内存地址作为指针值,但是只要是同构,它们就可以使用任何东西。
datenwolf

2
@bdonlan这也将违反MISRA-C中的(建议性)规则。
伦丁

2
@Andreas Yep这也是我的想法。不应允许硬件人员设计应在其中运行软件的硬件!:)
Lundin

Answers:


84

C标准不需要将空指针指向机器的地址零。但是,将0常量强制转换为指针值必须得到一个NULL指针(第6.3.2.3/3节),并且将空指针评估为布尔值必须为false。这可以是一个有点尴尬,如果你真的想要一个零个地址,而NULL不是零个地址。

但是,通过对编译器和标准库进行(大量)修改,NULL在仍然严格符合标准库的情况下,可以用备用位模式表示并非不可能。这是足够简单地改变的定义NULL然而本身,然后NULL将评估为true。

具体来说,您需要:

  • 安排指针分配(或强制转换为指针)中的文字零以转换为其他不可思议的值,例如-1
  • 安排在指针和常量整数之间进行相等性测试,0以代替以检查魔术值(第6.5.9 / 6节)
  • 安排所有将指针类型评估为布尔值的上下文,以检查与魔术值的相等性,而不是检查零。这来自于相等性测试语义,但是编译器可能会在内部不同地实现它。参见§6.5.13/ 3,§6.5.14/ 3,§6.5.15/ 4,§6.5.3.3/ 5,§6.8.4.1/ 2,§6.8.5/ 4
  • 正如caf所指出的,更新静态对象(第6.7.8 / 10节)和部分复合初始化器(第6.7.8 / 21节)初始化的语义,以反映新的空指针表示形式。
  • 创建访问真实地址零的替代方法。

有一些事情你具备处理。例如:

int x = 0;
void *p = (void*)x;

此后,p不能保证是空指针。只需要处理常量分配(这是访问真实地址零的好方法)。同样地:

int x = 0;
assert(x == (void*)0); // CAN BE FALSE

也:

void *p = NULL;
int x = (int)p;

x不能保证是0

简而言之,C语言委员会显然考虑了这种情况,并为那些选择NULL的替代表示形式的人员进行了考虑。您现在要做的就是对编译器进行重大更改,嘿,您已经完成了:)

附带说明,在编译器适当之前,可以通过源代码转换阶段实现这些更改。也就是说,您将添加一个预处理器-> NULL转换->编译器->汇编器->链接器,而不是正常的预处理器->编译器->汇编器->链接器。然后,您可以进行如下转换:

p = 0;
if (p) { ... }
/* becomes */
p = (void*)-1;
if ((void*)(p) != (void*)(-1)) { ... }

这将需要完整的C解析器,类型解析器以及对typedef和变量声明的分析,以确定哪些标识符对应于指针。但是,这样做可以避免必须对编译器的代码生成部分进行适当的更改。clang可能对实现此功能有用-我了解它在设计时就考虑了这种转换。当然,您仍然可能需要对标准库进行更改。


2
好的,我没有在6.3.2.3节中找到该文本,但我怀疑在某处会出现这样的声明:)。我猜这回答了我的问题,按照标准,除非我想编写一个新的C编译器来备份我,否则我不允许重新定义NULL :)
Lundin

2
一个不错的技巧是破解编译器,以便指针<->整数转换将特定值异或为XOR,该特定值是无效指针,但仍然微不足道,以至于目标体系结构可以廉价地做到这一点(通常,该值只有一个位设置) ,例如0x20000000)。
Simon Richter

2
您需要在编译器中更改的另一件事是使用复合类型对对象进行初始化-如果对对象进行了部分初始化,则必须将没有显式初始化器的任何指针都初始化为NULL
caf

20

该标准指出,值为0的整数常量表达式或转换为void *类型的此类表达式为空指针常量。这意味着,(void *)0始终是一个空指针,但考虑int i = 0;(void *)i大可不必。

C实现由编译器及其标头组成。如果您修改标题以重新定义NULL,但不修改编译器以修复静态初始化,那么您已经创建了一个不一致的实现。在一起的整个实现都有不正确的行为,如果您破坏了它,您真的没有人要怪了;)

你必须解决的不仅仅是静态initialisations,当然-给一个指针pif (p)相当于if (p != NULL),由于上述规则。


8

如果使用C std库,则会遇到可以返回NULL的函数的问题。例如,malloc文档指出:

如果函数未能分配所请求的内存块,则返回空指针。

由于malloc和相关函数已经被编译为具有特定NULL值的二进制文件,因此,如果重新定义NULL,则除非可以重建整个工具链(包括C std库),否则将无法直接使用C std库。

同样由于std库使用NULL,如果在包含std标头之前重新定义NULL,则可能会覆盖标头中列出的NULL定义。内联的任何内容都与已编译的对象不一致。

相反,我将定义自己的NULL(“ MYPRODUCT_NULL”)以供您自己使用,并避免使用C std库或从中进行转换。


6

单独保留NULL并将IO到端口0x0000视为特例,可能使用汇编程序编写的例程,因此不受标准C语义的约束。IOW,不要重新定义NULL,而是重新定义端口0x00000。

请注意,如果您正在编写或修改C编译器,则无论如何定义NULL,避免取消引用NULL(假设在您的情况下CPU不能提供帮助)所需的工作都是相同的,因此保留NULL定义会更容易为零,并确保永远不能从C取消引用零。


仅当意外访问NULL时才会出现问题,而不是有意访问端口时才会出现问题。为什么要重新定义端口I / O?它已经可以正常工作了。
伦丁

2
不小心或不@Lundin,NULL可以将在C程序中使用间接引用*pp[]p(),所以编译器只需要关心那些为了保护IO端口0×0000。
2011年

@Lundin问题的第二部分:一旦从C内部限制对地址零的访问,就需要另一种方式访问​​端口0x0000。用汇编器编写的函数可以做到这一点。从C中可以将端口映射到0xFFFF或其他任何内容,但是最好使用函数并忽略端口号。
2011年

3

考虑到其他人提到的重新定义NULL的极端困难,也许更容易重新定义对众所周知的硬件地址的取消引用。创建地址时,请在每个知名地址上加1,以便您知名的IO端口为:

  #define CREATE_HW_ADDR(x)(x+1)
  #define DEREFERENCE_HW_ADDR(x)(*(x-1))

  int* wellKnownIoPort = CREATE_HW_ADDR(0x00000000);

  printf("IoPortIs" DEREFERENCE_HW_ADDR(wellKnownIoPort));

如果将您关心的地址组合在一起,并且可以放心,将地址加1不会与任何内容发生冲突(多数情况下不应该发生冲突),则可以安全地进行此操作。然后,您无需担心以以下形式重建工具链/ std lib和表达式:

  if (pointer)
  {
     ...
  }

仍在工作

疯狂,我知道,但是只是以为我会把这个想法丢掉。


仅当意外访问NULL时才会出现问题,而不是有意访问端口时才会出现问题。为什么要重新定义端口I / O?它已经可以正常工作了。
伦丁

@LundIn我想您必须选择哪个更麻烦,调整重新构建整个工具链或更改代码的这一部分。
Doug T.

2

空指针的位模式可能与整数0的位模式不同。但是NULL宏的扩展必须是空指针常量,即可以转换为(void *)。

为了在保持一致性的同时获得所需的结果,您必须修改(或配置)您的工具链,但这是可以实现的。


1

你在找麻烦。重新定义NULL为非null值将破坏此代码:

   如果(myPointer)
   {
      // myPointer不为null
      ...
   }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.