1

我需要使标准库函数tolower静态而不是“公共”范围。

我正在使用 IAR Embedded Workbench 编译器使用 MISRA C:2004 进行编译。编译器声明tolower为内联:

  inline
  int tolower(int _C)
  {
    return isupper(_C) ? (_C + ('A' - 'a')) : _C;
  }

我从编译器收到以下错误:

Error[Li013]: the symbol "tolower" in RS232_Server.o is public but
is only needed by code in the same module - all declarations at
file scope should be static where possible (MISRA C 2004 Rule 8.10)  

以下是我建议的解决方案:

  1. 在另一个模块中使用tolower,在虚拟环境中,以便多个模块需要它。
  2. 实现功能而不使用tolower. 这是一个嵌入式系统。
  3. 添加一个“STATIC”宏,默认定义为空,但可以在包含头文件static之前定义。ctype.h

我正在寻找 MISRA 链接器错误的解决方案。我宁愿tolower只为 RS232_Server 翻译单元设置静态功能(如果我tolower在标准头文件中设置静态,可能会影响其他未来的项目。)

编辑1:

编译器是用于 ARM 处理器的 IAR Embedded Workbench 6.30。
我在 32 位模式(不是 Thumb 模式)下使用 ARM7TDMI 处理器。
tolower函数与调试端口一起使用。

编辑2:

我也收到了_LocaleC_isupper错误_LocaleC_tolower

解决方案:

  1. 我按照 Michael Burr 的建议将此问题通知了供应商。
  2. 由于本地化问题,我决定不重写库例程。
  3. 我按照 gbulmer 的建议在 main.c 文件中实现了一个函数指针;然而,这将是令人难以置信的评论,因为它应该在 IAR 解决他们的问题后被删除。
4

3 回答 3

2

我建议您禁用此特定的 MISRA 检查(您应该能够仅针对RS232_Server翻译单元执行此操作),而不是使用您建议的解决方法之一。在我看来,规则 8.10 的效用非常小,跳过建议的变通办法中的各种障碍似乎比仅仅禁用规则更有可能带来更高的风险。请记住,MISRA 的目的是使 C 代码不太可能出现错误。

请注意,MISRA 承认“在某些情况下可能需要偏离规则”,并且有一个记录在案的“偏离程序”(MISRA-C 2004 中的第 4.3.2 节)。

如果您出于某种原因不会或无法禁用该规则,我认为您可能应该tolower()在自己的函数中重新实现 's 功能,尤其是在您不必处理语言环境支持的情况下。使用 IAR 打开支持事件也可能是值得的。您可以使用规则 3.6 来刺激它们,即“生产代码中使用的所有库都应编写为符合 [MISRA-C]”。

于 2012-03-30T06:49:52.023 回答
1

谁销售 MISRA 链接器?它似乎有一个疯狂的错误。

您可以通过int (*foo)(int) = tolower;在文件范围内获取地址来解决它吗?

编辑:我的理由是:
a。这是我这十年见过的最愚蠢的事情,所以它可能是一个错误,但是
b。将其推向边缘情况(通过全局导出其名称的符号)可能会关闭它。

为了这是正确的行为,即不是一个错误,它必须是一个 MISRA 错误,以包含任何库函数一次,如 initialise_system_timer、initialise_watchdog_timer、...,这让我很头疼。

编辑:另一个想法。再次基于这是一个极端情况错误的假设。

理论:也许编译器正在做内联生成函数的实现。然后该函数(当然)没有被调用。所以链接器规则看到了那个未调用的函数。

GNU 有防止内联的选项。你能对 tolow 的使用做同样的事情吗?这会改变错误吗?你可以做一个测试

#define inline /* nothing */

在包含 ctype.h 之前,然后取消定义宏。

另一个测试是定义:

#define inline static inline

在包括 ctype.h 之前,这是我期望的内联版本。

EDIT2:我认为应该报告一个错误。我想 IAR 有一个解决方法。我会接受他们的建议。

睡了一夜之后,我强烈怀疑问题inline int tolower()不是static inline int toloweror int tolower,因为它最有意义。拥有一个未被调用的公共函数似乎是程序中错误的症状。

即使有文档,所有的编码方法都有缺点。

  1. 我强烈支持 OP,我不会更改标准头文件。我认为有几个原因。例如,工具链(带有一组新的标头)的未来升级会破坏旧应用程序,如果它得到维护。或者只是在不同的机器上构建应用程序会产生错误,合并两个明显正确的应用程序可能会产生错误,...。不会有什么好的结果。-100

  2. 用于#define ...使错误消失。我不喜欢使用宏来更改标准库。这似乎是一个长期坏主意的种子。如果将来程序的另一部分使用具有类似问题的另一个函数,则“修复”的应用会变得更糟。一些没有经验的开发人员可能会得到维护代码的工作,并且“学习”包装奇怪的#define诡计#include是“正常”的做法。将奇怪的宏变通方法包装起来成为公司的“惯例” #include <ctype.h>,这种变通方法在修复后仍然存在多年。-20

  3. 使用命令行编译器选项关闭内联。如果这可行,并且语义是正确的,即在两个源文件中包含头文件和使用函数不会导致多个定义的函数,那么可以。我预计它会导致错误,但值得在错误报告中确认。它为将来出现的其他人设置了一个令人沮丧的陷阱。如果不同inline使用了标准库函数,由于某种原因,人们必须查看生成的代码,它也不会被包含在内。他们可能会有点疯狂地想知道为什么不尊重 inline。我猜想他们查看生成代码的原因是因为性能至关重要。根据我的经验,人们会花大量时间查看代码,在查看有效程序的构建脚本之前会感到困惑。如果将抑制内联用作修复程序,我确实觉得在任何地方都比在一个文件中执行要好。至少语义是一致的,并且可能会注意到高级注释或文档。任何使用构建脚本作为“快速检查”的人都会得到一致的行为,这可能会导致他们查看文档。-1(到处都没有内联)-3(一个文件上没有内联)

  4. 在第二个源文件中以虚假的方式使用 tolower。这样做的好处是构建系统不会被额外的编译器选项“感染”。它也是一个小文件,可以解释正在解决的错误。我不太喜欢这个,但比起摆弄标准标题,我更喜欢它。我目前担心的是它可能不起作用。它可能包括链接器无法解析的两个副本。我确实喜欢它比奇怪的“获取地址并查看链接器是否关闭”更好(我认为这是测试边缘情况的一种有趣方式)。-2

  5. 编写自己的代码。我不了解应用程序的更广泛背景。我的反应不是替换库函数的代码,因为我关心测试(以及引入更多代码的单元测试)和长期维护。我对字符 I/O 更加紧张,因为应用程序可能需要能够处理更广泛的字符集,例如 UTF-8 编码,而用户定义的 tolower 往往会不同步。这听起来确实像一个非常具体的应用程序(调试到端口),所以我可以应付。我不喜欢编写额外的代码来解决看起来像错误的事情,特别是如果它是商业软件。-5

  6. 您能否将错误转换为警告而不会丢失所有其他检查?我仍然觉得它是工具链中的一个错误,所以我更希望它是一个警告,而不是沉默,以便有一个事件和一些文档的钩子,并且出现另一个错误的可能性更小。否则关闭错误。+1(警告) 0(错误)

关闭错误似乎失去了(恕我直言)IAR 欠您一个解释和长期修复的“企业意识”,但它似乎比弄乱构建系统、使用标准库向 futz 编写宏脏东西要好得多,或者编写自己的代码会增加成本。

可能只是我,但我不喜欢编写代码来解决商业产品的缺陷。感觉这是供应商应该很高兴有机会证明其许可成本的地方。我记得微软向我们收取了事故费用,但如果问题被证明是他们的,事故和修复都是免费的。正确的做法似乎是给他们一个赚钱的机会。产品会有错误,所以默默地解决它而不给他们机会修复它似乎也没有多大帮助。

于 2012-03-30T00:51:10.173 回答
1

首先,MISRA-C:2004 不允许 inline 也不允许 C99。即将到来的 MISRA 2012 将允许它。如果您尝试通过 MISRA-C:2004 静态分析器运行 C99 或非标准代码,那么所有的赌注都将失败。MISRA 检查器应该为您提供 inline 关键字的错误。

我相信代码的 MISRA-C 兼容版本如下所示:

static uint8_t tolower(uint8_t ch)
{
  return (uint8_t)(isupper(ch) ? (uint8_t)((uint8_t)(ch + 'A') - 'a') : 
                                 ch);
}

对此的一些评论: MISRA 鼓励char字符文字的类型,但同时警告不要使用普通 char 类型,因为它具有实现定义的签名。因此我使用 uint8_t 代替。我认为假设存在带有负索引的 ASCII 表是很愚蠢的。

(_C + ('A' - 'a'))肯定不是 MISRA 兼容的,因为 MISRA 认为它包含两个隐式类型提升。MISRA 将字符文字视为 char,而不是像 C 标准那样的 int。

此外,您必须在每个表达式之后将类型转换为基础类型。并且因为 ?: 运算符包含隐式类型提升,您也必须将其结果类型转换为基础类型。

由于这被证明是一个相当难以理解的混乱,最好的想法是完全忘记 ?: 并重写函数。同时我们可以摆脱对有符号计算的不必要依赖。

static uint8_t tolower (uint8_t ch)
{
  if(isupper(ch))
  {
    ch = (uint8_t)(ch - 'A');
    ch = (uint8_t)(ch + 'a');
  }
  return ch;
}
于 2012-04-02T18:57:04.193 回答