2017-01-27 10 views
0

Я знаю, что этот вопрос, возможно, был задан раньше, но не ответил на запрос, который я публикую ниже. Я пытаюсь понять, почему именно код ниже дает ошибку (и в другой версии GCC, предупреждение) и esp. как это решить? Я опытный программист на C++, но иногда вам нужно искать помощи. Любая помощь приветствуется.snprintf: тот же код - разные ошибки/предупреждения для разных компиляторов g ++

[ERROR]: не может передать объекты нетривиально-Copyable типа «класс станд :: 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 заявлении выше.

WARNING on g++ (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609:

предупреждение: формат не строковые и никаких аргументов в формате [-Wformat-безопасности] size_t размера = snprintf (nullptr, 0, format.c_str(), арг ...) + 1;» ^

ERROR on g++ (Raspbian 4.9.2-10) 4.9.2

ошибка: не может передать объекты нетривиально-Copyable типа 'класс станд :: basic_string' через '...' size_t размер = snprintf (nullptr, 0, format.c_str(), args ...) + 1;

Note: I wish to understand and resolve this but without providing a compiler flag setting. I understand this may have something to do with C compatibility with C++.

Спасибо!

+0

@SouravGhosh Спасибо за указание. Но snprintf относится к C, поэтому я отметил его как C. Содержимое хранится как c-строка. – Sammy

+0

Кажется, что работает с онлайн-компилятором C++ 14: http://ideone.com/HfuHIx. Как вы называете эту функцию? – mch

+2

@Sammy Это хорошо, но IMHO этот вопрос ищет ответы на основе языка C++, а C и C++ - не то же самое. –

ответ

0

Я нашел ответ на эту двусмысленность. Спасибо @PaulMcKenzie за указание. Вот решение, которое я видел, работал с любым флагом компилятора, таким как «-wno-format-security». Больше нет ошибок в Pi или Error на 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); 
} 

The catch was doing linkName.c_str() (C-Style string) instead of passing the std::string as argument. This was tricky!

+1

Я удивлен, что компилятор выпустил ошибки, а не просто позволить вам застрелить себя в ноге во время выполнения, когда вы передали 'std :: string'. Соответствующий раздел в стандартном документе ANSI (вы можете получить его проект), который относится к функциям varargs, - это «18.10» в разделе 3: * Если параметр parmN объявлен с помощью функции, массива или ссылочного типа или с помощью тип, который несовместим с типом, который возникает при передаче аргумента, для которого нет параметра, поведение не определено * – PaulMcKenzie

+0

ПРЕДУПРЕЖДЕНИЕ о g ++ (Ubuntu 5.4.0-6ubuntu1 ~ 16.04.4) 5.4.0 20160609, но ОШИБКА на g ++ (Raspbian 4.9.2-10) 4.9.2. Я согласен, что поведение может быть неопределенным, как указано выше, с передачей массива char. Я рад, что нашел это во время компиляции. Причина, по которой я часто воздерживаюсь от использования флага безопасности формата Wno. Это может быть довольно опасно в самые неожиданные моменты для критически важных приложений. Спасибо, btw! – Sammy

1

Я не знаю, что является причиной ваших проблем, но я не вижу, какую пользу вы получите от использования функции шаблона. Для этого вы можете просто использовать старомодные варгары.

Я собирался сказать, что золотое правило для написания функции varargs состоит в том, чтобы написать тот, который принимает va_list. Ваше изменение делает причину очень ясного - вы обычно вызываете функцию varags из другой функции 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 как const, ссылаясь на 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) 

Затем G CC будет выдавать вам с хорошей ошибкой, если вы делаете что-то вроде:

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

(Он также будет жаловаться, если формат неправильного типа.

+0

Спасибо @Martin Bonner. Я пытался понять, что вы предложили, и компиляция же дает ошибку. Вы уверены, что имеете в виду: va_start (format, args); , потому что формат должен быть недействительным va_start (va_list ap, last_arg); Я использую g ++ – Sammy

+0

Нет, я не уверен, по крайней мере. Этот код нигде не был рядом с компилятором –