【问题标题】:Issue with NULL pointers on Harvard Architecture platform哈佛架构平台上的 NULL 指针问题
【发布时间】:2011-04-22 19:37:47
【问题描述】:

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

我们正在哈佛架构嵌入式平台上使用 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 的显式检查。它们中的大多数将是数据指针,但其中一些将是函数指针。 != NULL 或 == NULL 的快速 grep 显示超过 3000 个结果,许多人需要手动检查。

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

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

  2. 重新定义 NULL 以使其做正确的事情。

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

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

有什么想法或建议吗?

【问题讨论】:

  • 只是为了记录,gcc 的哪个版本对你这样做?
  • 它是 gcc 3.3.3,但它是我们自己的平台端口。
  • 这肯定是实现中的一个错误。 (void *) 0 被 C 官方认可为 空指针常量,这意味着它必须在函数指针上下文(包括比较)中转换为正确的空指针值。换句话说,(void *) 0 不是数据指针,即使它似乎包含对数据指针类型的强制转换,它仍然是那个“灵活的”空指针常量。编译器似乎假定(void *) 0 严格来说是一个数据指针,这是不正确的。

标签: c null harvard-architecture


【解决方案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。

【讨论】:

  • 我立即想到这是 GCC 的问题,所以我支持你。重新定义(或者甚至丑陋的 GCC 来做同样的事情)NULL 到 0 也可以,我认为。
  • 请注意,C 允许将NULL 定义为普通的0 而不是(void *)0。像这样令人讨厌的实现可能应该使用该选项和#define NULL 0,我认为在这种情况下您有理由修复其损坏的标准头文件,以便您可以使用@987654343 @ 指定。
  • @R..:我完全同意。这就是我回答的(未说明的)理由。
  • 现在有一个问题。将NULL 与函数指针一起使用已被标准明确定义,但是将(void *)0 与函数指针一起使用是否已明确定义?我对此表示怀疑。
  • 只是将NULL 转换为0,即s/NULL/0/g 是不行的。考虑一个需要指针的可变参数函数,其中给出了NULL。将其更改为 0 会导致灾难。
【解决方案2】:

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

#define NULL ((void *)0)

与

#define NULL 0

然后向供应商提交错误报告。您不应该因为供应商编译器中的错误而修改您的(完全正确,尽管样式丑陋)的代码。

【讨论】:

    【解决方案3】:

    您可以尝试分段破解(实际上只是破解),这样您就可以使用快速的 16 位比较,没有任何风险。 在每个 n*0x10000 边界处创建大小为 4(甚至更小)的分段,因此永远不存在真正的函数。

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

    【讨论】:

    • 有趣的方法! +1 原创性,如果不是愉快的话 :-)
    【解决方案4】:

    以下是一些建议:

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

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

    【讨论】:

      【解决方案5】:

      你描述的方式应该可行:

      6.3.2.3/3 值为 0 的整数常量表达式,或转换为 void * 类型的表达式称为空指针常量。如果将空指针常量转换为指针类型,则生成的指针(称为空指针)保证与指向任何对象或函数的指针不相等。

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

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

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

      【讨论】:

      【解决方案6】:

      你可以试试这个:

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

      【讨论】:

      • 为什么是#ifdef?如果符号已经未定义,#undef 不是已经是空操作了吗?
      • 公平点,可能取决于您使用的预处理器。这可能不是我 11 年前使用的那个:)
      • 我没有 C99 的标准,但我不希望与 C11 有区别,它说 #undef 是“如果指定的标识符当前未定义为宏名称”。所以它不应该(至少在今天)取决于你的实现,除非你必须使用一个损坏的。 (仅供参考;此代码没有错误)
      猜你喜欢
      • 2012-03-11
      • 1970-01-01
      • 2017-06-12
      • 1970-01-01
      • 2016-04-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多