错误地将字符串传递给printf样式的日志函数时缺少错误



用自定义日志函数替换printf对于任何中大型项目来说都是非常常见的。下面是一个最小的C++示例及其用法:

#include <stdio.h>
#include <stdarg.h>
#include <string>
void log_printf(const char* fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    vprintf(fmt, ap); // real code obviously does something fancier
    va_end(ap);
}
int main() {
    std::string x = "Hello";
    // correct code
    printf("String is %sn", x.c_str());
    log_printf("String is %sn", x.c_str());
    // incorrect code
    printf("String is %sn", x); // bad line 1
    log_printf("String is %sn", x); // bad line 2
}

简单记录器接收可变数量的参数,并调用vprintf将它们输出到标准输出。"正确代码"下的行展示了此记录器的正确使用。我的问题涉及"坏"行,其中错误地传递了字符串对象,而不是指向字符缓冲区的指针。

在GCC 4.6(在Linux下测试)下,两个坏行都不能编译,这是一件好事,因为我想捕捉这种不正确的用法。错误为:

error: cannot pass objects of non-trivially-copyable type ‘std::string {aka struct std::basic_string<char>}’ through ‘...’

然而,在GCC 5.1中,显然可以传递不太普通的可复制对象,并且编译成功了。如果我使用-Wall,那么只有"坏行1"会引发关于意外参数类型的警告,但带有log_printf的"坏行2"在任何情况下都不会出现问题。不用说,这两行都会产生垃圾输出。

我可以用-Wall -Werror捕捉"坏线路1",但"坏线路2"呢?如何使它也生成编译错误?

对于您自己的函数,您需要使用一个公共函数属性format:

void log_printf(const char* fmt, ...) __attribute__((format (printf, 1, 2)));
void log_printf(const char* fmt, ...) {
    ...
}

请注意,必须在函数声明上设置属性,而不是在定义上

format属性的第一个参数是样式,在这种情况下是printf(对于像scanf这样工作的函数,scanf也是可能的),第二个参数是格式字符串,第三个参数是省略号...所在的位置。这将有助于GCC像检查标准printf函数那样检查格式字符串。

这是一个GCC扩展,尽管其他一些编译器已经采用它来实现GCC兼容,最著名的是英特尔C编译器ICC和Clang(OSX和一些BSD变体上使用的标准编译器)。Visual Studio编译器没有这个扩展,我也不知道Visual C++有类似的东西。

最新更新