snprintf:相同的代码 - 不同的g ++编译器上的不同错误/警告

时间:2017-01-27 09:35:45

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

我知道此问题之前可能已被提出,但尚未回答我在下面发布的查询。我试图找出为什么下面的代码确切地给出了错误(以及其他版本的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 ++的兼容性。

谢谢!

2 个答案:

答案 0 :(得分:1)

我找到了这种含糊不清的答案。谢谢@PaulMcKenzie指出。这是我看到的解决方案,使用任何编译器标志,如“-Wno-format-security”。在Ubuntu g ++上没有错误的Pi或错误。

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

catch正在执行linkName.c_str()(C-Style字符串),而不是将std :: string作为参数传递。这太棘手了!

答案 1 :(得分:1)

我不知道是什么导致了您的问题,但我不知道您使用模板功能会带来什么好处。你可以为此使用古老的varargs。

我要说的是,写一个varargs函数的黄金法则是编写一个va_list。你的编辑很清楚 - 你通常最终会从另一个varargs函数调用varags函数!

#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的常量引用(但这只是因为传值总是让我抽搐)。

最后,当你似乎在使用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 );

(如果格式错误,也会抱怨。