如何帮助程序员在C中编写安全正确的printf调用?

时间:2009-12-28 14:52:51

标签: c printf variadic

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

真实问题

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

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

背景问题与解答

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

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

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

问题3:我们为什么关心?
A3:充其量,会导致错误的日志记录信息并阻碍调试。最糟糕的是,崩溃软件。

问题4:有没有人试图对这个问题采取任何措施? 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问题的产品,这是因为它们是事后解决方案。他们可以避免崩溃,并且可以提供错误的警告解释,以及 出错了什么,但他们当然无法猜出程序员的意图是什么(参见上面的问题与答案A#2) )。

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

5 个答案:

答案 0 :(得分:7)

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;
}

答案 1 :(得分:3)

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

#pragma GCC poison printf

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

请参阅GCC pragmas

答案 2 :(得分:2)

使用可以检查printf()的类型和格式的编译器。大多数现代编译器都应该能够做到。对于GCC,-Wall是您的朋友(或-Wformat,如果您只想进行格式检查)。有关详细信息,请参阅warning options

除此之外,您唯一的选择是将#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中达到一定程度的安全性,但需要付出代价:您必须禁止在代码中的任何位置使用varargs。指针和数组必须包含在检查大小等的代码中。所以是的,它可能但不再是C语言。

面对它,C是从1972年开始的。它是古老而且它表明了。近35年来,没有人设法找到一种让C安全的聪明方法(参见C ++的尝试和你可以期待的成功量)。

答案 3 :(得分:1)

我最好的运气是使用像lintsplint这样的工具,当东西没有通过100%的时候,它就是绝对合适的。结合某种代码检查(处理警告被正确抑制的情况),这足以完成工作,虽然它绝对不是一个理想的解决方案。

答案 4 :(得分:0)

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

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

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

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