哈佛架构平台上的NULL指针问题

我们本周在这里遇到了有趣的问题。

我们在C上使用哈佛架构嵌入式平台,该平台具有16位数据地址和32位代码地址。

使用函数指针时会出现此问题。 如果你有像这样的代码

if (fp) fp(); 

要么

 if (fp != 0) fp(); 

一切都好。

但是如果你有像这样的代码

 if (fp != NULL) fp(); 

然后,因为NULL定义为(void *) 0 ,编译器(在这种情况下为gcc)a)不会发出警告,b)对函数指针进行16位比较而不是32位比较。 只要你的函数指针没有发生在64k边界上就可以了,所以所有底部的16位都是0。

目前,我们有大量代码,其中包含对NULL的显式检查。 它们中的大多数将是数据指针,但其中一些将是函数指针。 快速grep for != NULL== NULL显示超过3000个结果,许多人通过手动检查。

所以,我们现在想要的也是

  1. 找到比较函数指针(但不是数据指针)的所有情况的方法(所以我们可以将它们与我们定义为32位0的FP_NULL进行比较),或者

  2. 以这样的方式重新定义NULL,以便它做正确的事情。

  3. (或者,我想,更新我们的gcc端口以检测并正确处理这种情况)。

我无法想到任何适用于1的方法。我能想到的唯一方法是将NULL重新定义为0函数指针,这对于绝大多数针对数据指针的比较来说都是非常浪费的。 (32位比较是4条指令,16位比较是1条指令)。

有什么想法或建议吗?

在我看来,最简单的方法是将所有出现的NULL替换为0 。 这适用于函数指针(如您所说)和对象指针。

这是(2)将NULL重新定义为普通0的变体。

但是你无法将函数指针与NULL进行比较的事实是你的实现中的一个错误。 C99指出,对象和函数指针都可以比较空指针常量,并且NULL应该扩展到这个常量。

来自C-FAQ问题5.8的小增加:

问:NULL对指向函数的指针有效吗?
答:是(但见问题4.13 )

将函数指针与(void *) 0

(回复R ..的评论)。 我相信使用函数指针和(void *) 0在一起是明确定义的。 在我的推理中,我将参考C99草案1256的部分,但不会引用大部分内容以保持其可读性。 它也适用于C89。

  • 6.3.2.3(3)定义整数常量表达式0并将这种表达式转换为(void *)作为空指针常量 。 并且: “如果将空指针常量转换为指针类型,则结果指针(称为空指针)将保证与指向任何对象或函数的指针不相等。”
  • 6.8.9为(以及其他)指针操作数和空指针常量定义==!=操作数。 对于这些: “如果一个操作数是指针而另一个是空指针常量,则空指针常量将转换为指针的类型。”

结论:在fp == (void *) 0 ,空指针常量被转换为fp的类型。 这个空指针可以与fp进行比较,如果它指向一个函数,则保证不等于fp 。 赋值( = )有一个类似的子句,所以fp = (void *) 0; 也是明确定义的C.

你可以试试这个:

 #ifdef NULL #undef NULL #endif #define NULL 0 

你描述的方式应该有效:

6.3.2.3/3值为0的整型常量表达式,或者类型为void *的表达式,称为空指针常量。 如果将空指针常量转换为指针类型,则保证将结果指针(称为空指针)与指向任何对象或函数的指针进行比较。

因此,NULL被重新定义为非0(void*)0 (或等效的?)或者您的编译器不符合。

尝试在所有文件中的所有#includes之后重新定义NULL(到0 ):-)

只是为了踢:在问题文件上尝试gcc -E (输出预处理的源)并检查NULL的扩展

您可以尝试分段黑客(实际上只是一个黑客攻击),因此您可以使用快速16位比较,没有任何风险。 在每个n * 0x10000边界创建大小为4(甚至更小)的分段,因此永远不会存在真正的function。

这取决于您的嵌入式设备内存空间,如果这是一个好的或非常糟糕的解决方案。 如果你有1MB普通Flash,它可能会工作,永远不会改变。 如果你有64MB的Nand Flash,那将是痛苦的。

以下是一些建议:

  1. 暂时将NULL更改为(char*)0或其他不可隐式转换为函数指针的内容。 这应该给出与非匹配指针的每次比较的警告。 然后,您可以通过像grep这样的工具运行生成的编译器输出,并查找函数指针的典型模式,如(*)(

  2. 将NULL重新定义为0 (不将强制转换为void *)。 这是NULL的另一个有效定义,可能会为您做正确的事,但不能保证。

编辑实现的系统头以替换所有的出现

 #define NULL ((void *)0) 

 #define NULL 0 

然后向供应商提交错误报告。 由于供应商编译器中的错误,您不必修改您的(完全正确的,虽然丑陋的样式)代码。