3

[为清晰起见更新了组织和内容]

真正的问题

对于 C 语言来说,帮助程序员在他/她打字时编写对特定于项目的类似 printf 的调试函数的安全和正确调用的好方法是什么?

C 宏?C包装函数?代码编辑器宏或模板?其他?

背景问题和答案

许多软件使用 printf 或类似 printf 的函数进行调试,无论是在出现问题时的临时功能,还是用于调试日志。然而它很容易出错。

Q1:我们怎么知道?
A1:静态分析器有针对 printf-mismatch 错误的类别——这是一种常见的错误类别——我经常看到这些工具在 C 代码中调用这些警告。

Q2:这个错误的子类是什么?
A2:主要是格式说明符错误,格式说明符数量错误。通常真正的错误是相反的:错误的变量类型,或打印输出的变量数量错误。

Q3:我们为什么关心?
A3:充其量,导致错误的日志信息并妨碍调试。最坏的情况是软件崩溃。

Q4:有没有人尝试过解决这个问题?
A4:当然,虽然我还没有看到任何 C 语言(与 C++ 或其他语言相反),例如:

http://www.ddj.com/cpp/184401999?pgno=1 http://mi.eng.cam.ac.uk/~er258/cvd/tag/html/group__printf.html

这些产品和其他产品对我来说缺少什么,除了现在我正在研究用 C 编写的产品并且需要为 C 解决问题之外,它们是事后解决方案。他们可以避免崩溃,并且可以提供错误的警告解释,以及了什么问题,但他们当然无法猜测程序员的意图是什么(尤其是上面的 Q&A #2)。

Q5:为什么使用 printf 这么容易出错?
A5:因为编写 printf 调用需要程序员将变量的类型和数量、格式说明符、自由文本字符串常量和标点符号(所有这些看起来非常相似)放在一行中。

4

5 回答 5

8

gcc 提供-Wformat有关 printf/scanf/strftime/strfmon 格式错误的警告。

$ gcc -Wformat -c -o test.o test.c
test.c: In function ‘main’:
test.c:5: warning: format ‘%s’ expects type ‘char *’,
          but argument 2 has type ‘int’
$ cat test.c
#include <stdio.h>

int main(int argc, const char *argv[])
{
     printf("%s\n", 0);
     return 0;
}
于 2009-12-28T15:14:36.407 回答
4

在 GCC 中,有一种内置方法可以防止使用函数:

#pragma GCC poison printf

它比“-Wall”更好,因为它是一个错误,而不是一个警告。我不知道 printf 将如何被替换 - 可能会被许多专门的功能所取代。

参见GCC 编译指示

于 2009-12-28T15:47:30.757 回答
3

使用可以检查类型和格式的编译器printf()。大多数现代编译器应该能够做到这一点。对于 GCC,-Wall是您的朋友(或者-Wformat如果您只想要格式检查)。有关详细信息,请参阅警告选项

除此之外,您唯一的选择是将 a 添加#define printf ILLEGAL_DO_NOT_USE到项目的公共头文件中,并提供以安全方式完成相同工作的不同功能。祝你好运;)

[编辑] 您的问题是 C 不能将类型信息附加到本身作为参数传递的东西上。所以你可以做的是沿着这些思路:

safe_printf_like_function("%d %s %c\n", INT_TYPE(value), STRING_TYPE(s), CHAR_TYPE(c));

宏必须包含对类型的强制转换(以便编译器在您传入时可以注意到类型是错误的),而且它必须扩展为带有类型信息的东西。

缺点:如果你向他们提供这样的 API,任何 C 程序员都会痛苦地尖叫。C 不应该是这样的。C 不安全。时期。本来就是不安全的。通过设计、习惯和传统。如果你想要一个安全网,C 不适合你。

也就是说,您可以在 C 中实现一定程度的安全性,但要付出一定的代价:您必须禁止在代码中的任何地方使用可变参数。指针和数组必须包含在检查大小等的代码中。所以是的,这是可能的,但它不再是 C。

面对现实,C 是 1972 年的。它很古老,而且很明显。近 35 年来,没有人设法找到一种使 C 安全的聪明方法(请参阅 C++ 以了解尝试和您可以期待的成功数量)。

于 2009-12-28T15:15:13.923 回答
1

我最好的运气是使用lintor之类的工具,splint并在事情没有通过 100% 时推销绝对适合。结合某种代码检查(以处理警告被正确抑制的情况),这足以完成工作,尽管它绝对不是一个理想的解决方案。

于 2009-12-28T14:58:27.097 回答
0

这不是一个容易解决的问题。如果您要编写特定的模式(例如,您可以使用宏或(最好)包装函数。如果不复制复杂性"%s: %d",就无法复制 的功能。printf()

代码编辑器会发现很难检查,因为确定值类型(这是避免不匹配所必需的)需要解析 C 程序。C 编译器本身可能会发出警告,但没有多少会发出警告(如果有的话,我会喜欢的)。

C++ 避免了 iostreams 的问题,它利用了运算符重载,但这不是 C 中的选项。

我可以给出的一条规则是不要打印出像printf(string);. 始终使用printf(%s, string);,因为如果字符串中有百分号,这可以避免难以发现的错误。

于 2009-12-28T15:00:42.987 回答