【问题标题】:snprintf: Same code - different errors/warnings on different g++ compilerssnprintf:相同的代码 - 不同 g++ 编译器上的不同错误/警告
【发布时间】:2026-01-31 12:10:01
【问题描述】:

我知道这个问题之前可能已经被问过,但没有回答我在下面发布的查询。我试图弄清楚为什么下面的代码给出了错误(在其他版本的 GCC 中,一个警告)和 esp。如何解决这个问题?我是一位经验丰富的 C++ 程序员,但有时您确实需要寻求帮助。任何帮助表示赞赏。

[错误]:无法通过非平凡可复制类型“class std::basic_string”的对象

/// USE THE MACRO BELOW TO LOG A FORMATTED STRING (usage is exactly like printf(...) [ SEE HEADER FILE - TEMPLATE FUNCTION user_log_fmt(...) ]
#define LOG_TRACE_FMT(...)  Logger::getInstance()->user_log_fmt(LOG_LEVEL_TRACE,   __PRETTY_FUNCTION__, __FUNCTION__, __VA_ARGS__)

void main()
{   
    std::string linkName = getLinkName();

    // THIS IS THE CALL THAT LEADS TO THE ERROR AT COMPILE-TIME
    LOG_TRACE_FMT("\nLink-name: %s, Sent packet: SYS: %d, COMP: %d, LEN: %d, MSG ID: %d\n", linkName, msg->sysid, msg->compid, msg->len, msg->msgid);
}
///
/// Part of logger class (combines strings with argument for formatting) 
///
template <typename ... Args>
void user_log_fmt(LOG_LEVEL level,  std::string pretty_func, std::string func_name, std::string str_format, Args ... args)
{
    if (m_LogLevel < level)
        return;

    std::string formatted_data = utils::Utils::string_format(str_format, args...);
    // Do something with the formatted string.. 
}

///
/// Part of Utils library. This method does a printf type formatting.
///
template<typename ... Args>
static std::string string_format( const std::string& format, Args ... args )
{
    size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0'
    std::unique_ptr<char[]> buf( new char[ size ] );
    snprintf( buf.get(), size, format.c_str(), args ... );
    return std::string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
}

错误在于上面的 snprintf 语句。

关于 g++ (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609 的警告:

警告:格式不是字符串文字,也没有格式参数 [-Wformat-security] size_t size = snprintf(nullptr, 0, format.c_str(), args ...) + 1;' ^

g++ (Raspbian 4.9.2-10) 4.9.2 上的错误

错误:不能通过“...”传递非平凡可复制类型“class std::basic_string”的对象 size_t size = snprintf(nullptr, 0, format.c_str(), args ...) + 1;

注意:我希望了解并解决此问题,但不提供编译器标志设置。我知道这可能与C有关 与 C++ 的兼容性。

谢谢!

【问题讨论】:

  • @SouravGhosh 感谢您的指出。但是 snprintf 确实与 C 有关,我将其标记为 C 的原因。内容存储为 c-string。
  • @Sammy 很好,但是恕我直言,这个问题是基于 C++ 语言寻求答案,C 和 C++ 并不相同。
  • 这与 C++ 与 C 的兼容性有关,但与 C 语言无关 - 因此标记不合适。
  • @Sammy :这些错误是来自模板的声明,还是来自使用?如果是后者,您能否显示调用(以及所有相关的声明)。 Ubuntu 错误看起来像您正在拨打string_format( "Hello World" ); 之类的电话。 Pi 错误看起来像 std::string foo="foo"; string_format("Hello %s",foo);
  • @Sammy -- snprintf 是一个 C 函数,但您传递的内容只有 C++ 知道。这肯定不会可靠地工作。此外,如果我正确阅读了 C++ 标准的18.10.3,则传递任何不符合参数列表的内容都是未定义的行为。换句话说,如果您使用varargs 函数(例如snprintfprintf 等),您最好做纯粹的CC 兼容工作。

标签: c++ c++11 g++ printf


【解决方案1】:

我找到了这个模棱两可的答案。感谢@PaulMcKenzie 指出。这是我看到的适用于任何编译器标志的解决方案,例如“-Wno-format-security”。在 Pi 上不再出现错误,在 Ubuntu g++ 上不再出现错误。

void main()
{   
    std::string linkName = getLinkName();

    // THIS IS THE CALL THAT LEADS TO THE ERROR AT COMPILE-TIME
    LOG_TRACE_FMT("\nLink-name: %s, Sent packet: SYS: %d, COMP: %d, LEN: %d, MSG ID: %d\n", linkName.c_str(), msg->sysid, msg->compid, msg->len, msg->msgid);
}

问题是在做 linkName.c_str() (C 风格的字符串),而不是传递 std::string 作为参数。这很棘手!

【讨论】:

  • 令我惊讶的是编译器发出了一个错误,而不是让你在运行时通过std::string 时让你在脚上开枪。 ANSI 标准文档(您可以得到它的草稿)中与可变参数函数有关的相关部分是第 3 部分中的18.10如果参数 parmN 是用函数、数组或引用类型声明的,或者与传递没有参数的参数时产生的类型不兼容的类型,行为未定义
  • 警告 g++ (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609 但 g++ (Raspbian 4.9.2-10) 4.9.2 出现错误。我同意通过传递 char 数组可以如上所述未定义行为。我很高兴我在编译时发现了这个。我经常避免使用 Wno-format 安全格式标志的原因。对于关键应用程序来说,在大多数意想不到的时候,这可能是非常危险的。谢谢顺便说一句!
【解决方案2】:

我不知道是什么导致了您的问题,但我看不出您从使用模板函数中获得了什么好处。您可以为此使用老式的可变参数。

我要说的是,编写可变参数函数的黄金法则是编写一个接受va_list 的函数。您的编辑说明了这样做的原因 - 您通常最终会从另一个 varargs 函数调用 varargs 函数!

#include <stdarg.h>  // varargs support

static std::string string_vformat( const std::string& format, va_list args)
{
    va_list args_copy;
    va_copy(args,args_copy);  // Copy args before we use it.
                              // +1 is extra space for '\0'
    const size_t size = vsnprintf( nullptr, 0, format.c_str(), args_copy ) + 1; 
    va_end(args_copy);        // Release args_copy.
    std::unique_ptr<char[]> buf( new char[ size ] );
    vsnprintf( buf.get(), size, format.c_str(), args );
    // Caller must va_end(args);
           // We don't want the '\0' inside
    return std::string( buf.get(), buf.get() + size - 1 );
}

static std::string string_format( const std::string& format, ... )
{
    va_list args;
    va_start(format, args);
    const auto result = string_vformat(format, args);
    va_end(args);
    return result;
}

void user_log_fmt(LOG_LEVEL level,  std::string pretty_func, std::string func_name,
                                    std::string str_format, ... )
{
    if (m_LogLevel < level)
        return;

    va_list args;
    va_start(format, args);
    std::string formatted_data = utils::Utils::string_vformat(str_format, args);
    // Do something with the formatted string.. 
}

几个旁白:

  • 我会为'\0' 分配一个有空间的可写字符串并为此写入缓冲区,然后缩小缓冲区以丢失终止符。字符串是连续的,因此这是安全的,并且保存动态分配使得最终结果中额外字节的成本非常值得。
  • 我会将 user_log_fmt 的所有参数作为对 std::string 的 const 引用(但这只是因为传递值总是让我抽搐)。

最后,你似乎在使用 GCC,你可以这样写:

#ifdef __GNUC__
    #define PRINTFLIKE(n)  __attribute__(format,printf,(n),(n+1))
#else
    #define PRINTFLIKE(n)
#endif

然后你可以声明:

 void user_log_fmt(LOG_LEVEL level, std::string pretty_func, std::string func_name,
                  std::string str_format, ... ) PRINTFLIKE(4)

 std::string string_format( const std::string& format, ... ) PRINTFLIKE(1)

如果您执行以下操作,GCC 会向您发出一个很好的错误:

string_format( "Two things %d %d", only_one_thing );

(格式错误也会报错。

【讨论】:

  • 谢谢@Martin Bonner。我试图理解你的建议并编译相同的错误。你确定你的意思是:va_start(format, args);因为格式必须是 void va_start(va_list ap, last_arg);我正在使用 g++
  • 不,我至少不确定。这段代码离编译器还差得很远
最近更新 更多