重新定义NULL

Lun*_*din 118 c null

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

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

我的问题是:这会与C标准发生冲突吗?据我所知,从标准中的7.17开始,宏是实现定义的.标准中的其他地方是否有任何内容表明NULL 必须为0?

另一个问题是,无论数据类型如何,大量编译器都会通过将所有内容设置为零来执行静态初始化.尽管标准规定编译器应将整数设置为零并将指针设置为NULL.如果我将为我的编译器重新定义NULL,那么我知道这样的静态初始化将失败.我是否可以将其视为不正确的编译器行为,即使我手动大胆地更改了编译器头?因为我确实知道这个特定的编译器在进行静态初始化时不访问NULL宏.

bdo*_*lan 82

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;
Run Code Online (Sandbox Code Playgroud)

在此之后,p不保证是空指针.只需要处理常量赋值(这是访问真地址零的好方法).同样:

int x = 0;
assert(x == (void*)0); // CAN BE FALSE
Run Code Online (Sandbox Code Playgroud)

也:

void *p = NULL;
int x = (int)p;
Run Code Online (Sandbox Code Playgroud)

x不保证是0.

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

作为旁注,可以在编译器正确之前使用源代码转换阶段实现这些更改.也就是说,您需要添加预处理器 - > NULL转换 - >编译器 - >汇编程序 - >链接器,而不是预处理器 - >编译器 - >汇编程序 - >链接器的正常流程.然后你可以进行如下转换:

p = 0;
if (p) { ... }
/* becomes */
p = (void*)-1;
if ((void*)(p) != (void*)(-1)) { ... }
Run Code Online (Sandbox Code Playgroud)

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

  • 好的,我没有在6.3.2.3节中找到该文本,但是我怀疑* somewhere * :)会有这样的声明。我猜这回答了我的问题,按照标准,除非我愿意编写一个新的C编译器来备份我,否则不允许重新定义NULL :) (2认同)
  • 一个很好的技巧是破解编译器,使指针< - >整数转换XOR一个特定的值,这是一个无效的指针,并且仍然足够微不足以使目标体系结构可以廉价地执行(通常,这将是一个单位设置的值) ,例如0x20000000). (2认同)
  • 在编译器中需要更改的另一件事是使用复合类型初始化对象 - 如果对象被部分初始化,那么任何不存在显式初始化的指针必须初始化为"NULL". (2认同)

caf*_*caf 19

该标准规定,值为0的整型常量表达式或转换为该void *类型的表达式是空指针常量.这意味着它(void *)0总是一个空指针,但是给定int i = 0;,(void *)i不一定是.

C实现由编译器及其头部组成.如果您修改标头以重新定义NULL,但不修改编译器以修复静态初始化,那么您已创建了一个不合规的实现.这是整个实现一起采取不正确的行为,如果你打破它,你真的没有别人责备;)

你必须修复的不仅仅是静态初始化,当然 - 给定一个指针p,if (p)相当于if (p != NULL),由于上面的规则.


Dou*_* T. 7

如果使用C std库,则会遇到可返回NULL的函数问题.例如,malloc文档说明:

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

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

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

我会为你自己的用途定义你自己的NULL,"MYPRODUCT_NULL",并避免或转换为/到C std库.


Apa*_*ala 6

保留NULL并将IO作为特殊情况处理IO端口,可能使用汇编程序编写的例程,因此不受标准C语义的限制.IOW,不要重新定义NULL,重新定义端口0x00000.

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

  • @Lundin无论是否意外,NULL只能**使用`*p`,`p []`或`p()`在C程序中取消引用,因此编译器只需要关心那些保护IO端口的程序0×0000. (2认同)