我正在为地址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)有一些事情你不具备处理.例如:
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可能对实现它很有用 - 我知道它的设计考虑了这样的转换.当然,您仍然可能需要对标准库进行更改.
caf*_*caf 19
该标准规定,值为0的整型常量表达式或转换为该void *类型的表达式是空指针常量.这意味着它(void *)0总是一个空指针,但是给定int i = 0;,(void *)i不一定是.
C实现由编译器及其头部组成.如果您修改标头以重新定义NULL,但不修改编译器以修复静态初始化,那么您已创建了一个不合规的实现.这是整个实现一起采取不正确的行为,如果你打破它,你真的没有别人责备;)
你必须修复的不仅仅是静态初始化,当然 - 给定一个指针p,if (p)相当于if (p != NULL),由于上面的规则.
保留NULL并将IO作为特殊情况处理IO端口,可能使用汇编程序编写的例程,因此不受标准C语义的限制.IOW,不要重新定义NULL,重新定义端口0x00000.
请注意,如果您正在编写或修改C编译器,则无论如何定义NULL,避免解除引用NULL所需的工作(假设在您的情况下CPU没有帮助)都是相同的,因此更容易保留NULL定义为零,并确保不能从C取消引用零.