错误:'format' 属性的第三个参数的值为 '7',并非指向一个可变参数列表

编程语言 2026-07-11

有一个类似的 帖子,但并没有帮助。那个帖子中的解决方案是说这是一个gcc的 bug,已经被修复。gcc 15.2会不会又有这个bug?

就下面的代码快照,我收到了这些错误信息,覆盖了这3 个重载:

  • 错误:‘format’ 属性参数3 的值 ‘7’ 未引用到一个可变参数列表
  • 错误:‘format’ 属性参数3 的值 ‘6’ 未引用到一个可变参数列表
  • 错误:‘format’ 属性参数3 的值 ‘4’ 未引用到一个可变参数列表

gnu文档 中我没有发现任何提示为什么会失败。第一个参数是 this,因此计数从2 开始。

我到底哪里做错了,是不是漏看了关于在模板中使用 __attribute__ 的GCC文档?

#include <vector>
#include <source_location>
#include <stdio.h>

typedef unsigned int uint;
constexpr std::size_t MAX_LOG_LINE_LENGTH = { 2'500 };

enum LogControllerMode { Default = 0, };
enum class LogControllerType { Normal, };

template <typename... Ts>
struct FAEWE_LOG
{
    __attribute__ ((format(printf, 5, 6)))
        FAEWE_LOG( const uint instrumentsId,
               [[maybe_unused]] const LogControllerMode logMode,
               [[maybe_unused]] const std::vector<LogControllerType> & logControllerType,
               const char * formatString, Ts &&... LogArguments,
               const std::source_location & sourceLocation = std::source_location::current() )
        {
            char resolvedParameters [MAX_LOG_LINE_LENGTH];
            snprintf( resolvedParameters, sizeof(resolvedParameters), formatString, LogArguments...);
            printf( "%s:%d - [%d] %s\n", sourceLocation.file_name(), sourceLocation.line(),
            instrumentsId, resolvedParameters );
        }

};

template <typename... Ts>
FAEWE_LOG( const uint, const LogControllerMode, const std::vector<LogControllerType>, const char *, Ts &&... )
    -> FAEWE_LOG<Ts...>;

auto main () -> int
{
    FAEWE_LOG( 5, LogControllerMode::Default, { LogControllerType::Normal },
    " This is not a correct printf %s string %d", "formatted", 73U );
    return 0;
}

更新:原来是忘记在 godbolt 放上工作演示的链接。

解决方案

我把它提交到了 GCC Bugzilla。简短的回答是:

format不适用于带有变量模板类型。它只适用于可变参数列表。

文档应该把这一点讲清楚。

我为我自己找到了这个“合适”的解决方案:

  1. 在主包含文件中我添加了 #define CHECK_PRINT_PARAMETERS false|true
  2. 在定义 FAEWE_LOG* 模板/函数的包含文件中我有 #if CHECK_PRINT_PARAMETERS == false
  3. 在这种情况下,现有的模板定义会被处理——没有对 printf 参数进行检查。
  4. #if CHECK_PRINT_PARAMETERS == true "opens" 对于gcc来说是一段完全不同的代码,在这段代码里我只声明所有日志函数——作为占位。在我的情况下,我必须写出以下代码:
__attribute__((format(printf, 1, 2)))
auto FAEWE_LOG_SERVER( const char *, ... ) -> void;

__attribute__((format(printf, 2, 3)))
auto FAEWE_LOG_SERVER( const LogControllerMode, const char *, ... ) -> void;

__attribute__((format(printf, 1, 2)))
auto FAEWE_LOG_THREAD( const char *, ... ) -> void;

__attribute__((format(printf, 2, 3)))
auto FAEWE_LOG_THREAD( const char, const char *, ... ) -> void;

__attribute__((format(printf, 2, 3)))
auto FAEWE_LOG_THREAD( const LogControllerMode, const char *, ... ) -> void;

__attribute__((format(printf, 4, 5)))
auto FAEWE_LOG( const uint, const LogControllerMode, const std::vector<LogControllerType> &,
                const char *, ... ) -> void;

__attribute__((format(printf, 2, 3)))
auto FAEWE_LOG( const uint , const char *, ... ) -> void;

template<class BaseClass>  // Extends each class with the log functionality
class LoggingClass
{

};

有了这个,我就不需要去改动成百上千个 FAEWE_LOG* 函数, 一切都像以前一样工作。

因为这些只是声明,链接会失败——在我的情况下,这是 "理想的" 行为,因为那样占位函数永远不会生成可执行文件。

这套方法“完美地”工作,当我第一次运行时,gcc指出有几十个并非100% 正确的格式说明符。是的,我必须手动进行检查,但这总比某处出现一个bug要好。我是一个会犯错的人。

在我看来,C++的“退化”体现在 std::cout 和类似的函数 printf 上。某种程度上,他们想要“强制”参数管道的美感,但我感觉没有人真的写过一个能为人类格式化大量不同参数的日志函数。只要没有类似的C++函数存在,我就坚持使用 printf

已经很好奇为什么要关闭这个问题……但在它被关闭之前,我已经给出了答案。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章