【问题标题】:Treating __func__ as a string literal instead of a predefined identifier将 __func__ 视为字符串文字而不是预定义的标识符
【发布时间】:2013-08-20 08:47:16
【问题描述】:

我正在使用 gcc 编译 C99 代码。我想写一个宏,它会返回一个包含函数名和行号的字符串。

这就是我所拥有的:

#define INFO_MSG  __FILE__ ":"__func__"()"

但是,当我编译尝试使用此字符串的代码时,例如:

char buff[256] = {'\0'}
sprintf(buff, "Something bad happened here: %s, at line: %d", INFO_MSG, __LINE__);
printf("INFO: %s\n", buff);

我收到以下错误消息:

error: expected ‘)’ before ‘__func__’

我已将问题追溯到宏。当我从宏中删除 __func__ 时,代码编译正确。

如何修复宏,以便在字符串中包含预定义的 __func__ 宏?

【问题讨论】:

  • 使用编译器的扩展开关查看吐出的内容。
  • 我不相信__func__ 是一个宏。也许您可以将INFO_MSG 放入格式字符串中? sprintf(buff, "Something bad happened here: %s:%s(), at line: %d", __FILE__, __func__, __LINE__);
  • @leppie:你能澄清一下吗? - 扩展器是什么意思?顺便说一句,我正在使用 gcc。
  • @tangrs: __func__C99 (AFAIK) 中的预定义宏。
  • 似乎不是 gcc 4.2.1 上的宏。 echo "const char* test() { return __func__; }" | gcc -xc -E -std=c99 - 显示 const char* test() { return __func__; }。编辑:显然it isn't a macro.

标签: c macros


【解决方案1】:

这是一个语法错误。我尝试使用你的宏规范,但我没有找到有效的方法,所以也许你可以试试这个:

#define INFO_MSG  __FILE__ , __FUNCTION__   

int main()
{
    char buff[256] = {'\0'};
    sprintf(buff, "Something bad happened here: %s : %s(), at line: %d", INFO_MSG, __LINE__);
    printf("INFO: %s\n", buff);
}

【讨论】:

    【解决方案2】:

    经过快速实验,我发现您不能将__func__ 与字符串化一起使用。如果可以的话,那将没有多大意义,因为这意味着该值将位于定义宏的位置而不是应用宏的位置。

    __func__ 的性质,如问题的 cmets 中所述,在 this answer 中进行了描述。

    字符串化是在预处理器时执行的,因此__func__ 不可用,因为它本质上是一个函数本地字符串,稍后在编译过程中定义。

    但是,您可以在宏中使用__func__,只要您不对它使用字符串化。我认为以下执行您所追求的:

    #include <stdio.h>
    
    #define INFO_MSG "Something bad happened here: %s : %s(), at line: %d", \
                     __FILE__, __func__, __LINE__
    
    int main()
    {
        char buff[256] = {'\0'};
        sprintf(buff, INFO_MSG);
        printf("INFO: %s\n", buff);
        return 0;
    }
    

    请注意,在所提出的问题中,没有特别的理由使用字符串缓冲区。以下main 函数将达到相同的效果,而不会出现缓冲区溢出:

    int main()
    {
        printf("INFO: ");
        printf(INFO_MSG);
        printf("\n");
        return 0;
    }
    

    就我个人而言,我会像这样在宏中总结整个过程:

    #include <stdio.h>
    
    #define INFO_MSG(msg) printf("%s: %s : %s(), at line: %d\n", \
                            msg, __FILE__, __func__, __LINE__)
    
    int main()
    {
        INFO_MSG("Something bad happened");
        return 0;
    }
    

    【讨论】:

    • 您可以使用通常的两阶段技巧对__LINE__ 进行字符串化:#define STR(x) #x#define STRINGIFY(x) STR(x),然后是STRINGIFY(__LINE__)__func__ 是一个预定义的标识符(变量),而不是一个常量字符串,你不能做任何事情。
    • 谢谢。我已经修改了答案。
    • 嗯,这并不理想。我正在使用该宏将函数名称动态生成为字符串(使用早期版本的 gcc)。正因为如此,我将不得不重写很多代码,我会调查一下是否可以避免这样做。顺便说一句,它是我需要的字符串,因为我并不总是写入标准输出(即控制台),为了简洁/清晰起见,我只使用了 printf。
    【解决方案3】:

    请注意,“__func__ 不是函数,因此无法调用;实际上,它是一个预定义的标识符,指向一个字符串,该字符串是函数的名称,并且仅在函数的范围。" - Jonathan。

    以下是您要查找的内容:

    #define TO_STR_A( A ) #A
    #define TO_STR( A ) TO_STR_A( A )
    #define INFO_MSG TO_STR( __LINE__ ) ":" __FILE__
    
    char buff[ 256 ] = { 0 };
    
    sprintf( buff, "Something bad happened here, %s in function %s().", INFO_MSG, __func__ );
    printf( "INFO: %s\n", buff );
    

    ...请注意,可以在函数本身内部调用__func__。见this

    【讨论】:

    • 确实需要在函数内部使用__func__,不过可以先预处理一下。
    • __FILE__ 已经是一个字符串;它不需要被字符串化。 __func__ 不是函数,因此无法调用;实际上,它是一个预定义的标识符,指向一个作为函数名的字符串,并且只在函数范围内有效。
    • @JonathanLeffler,固定答案——感谢您提供的信息丰富的后续行动。
    【解决方案4】:

    从您的 cmets 来看,目标是有一个宏,它将文件名和函数名(可能还有行号)组合成一个字符串,可以作为参数传递给函数,例如 printf() 或 @987654323 @ 或syslog()

    很遗憾,我认为这是不可能的。

    C11 标准说:

    ISO/IEC 9899:2011 §6.4.2.2 预定义标识符

    ¶1 标识符__func__ 应由翻译器隐式声明,就好像紧跟在每个函数定义的左大括号之后,声明

    static const char __func__[] = "function-name";
    

    出现,其中 function-name 是词法封闭函数的名称。

    因此,__func__ 不是宏,与 __FILE____LINE__ 不同。

    相关问题What's the difference between __PRETTY_FUNCTION__, __FUNCTION__, __func__? 涵盖了一些替代名称。这些是特定于 GCC 的扩展,而不是标准名称。此外,GCC 4.8.1 文档说:

    这些标识符不是预处理器宏。在 GCC 3.3 和更早版本中,仅在 C 中,__FUNCTION____PRETTY_FUNCTION__ 被视为字符串文字;它们可以被使用 初始化 char 数组,它们可以与其他字符串文字连接。海合会 3.4 及更高版本将它们视为变量,如__func__。在 C++ 中,__FUNCTION____PRETTY_FUNCTION__ 一直是变量。

    这些不能作为预处理器构造有充分的理由。预处理器不知道函数是什么以及它正在处理的文本是否在函数的范围内,或者封闭函数的名称是什么。它是一个简单的文本处理器,而不是编译器。显然,可以在预处理器中建立如此多的理解(仅用于支持这一特性),但标准并没有要求,标准也不应该要求。

    但不幸的是,我认为这意味着尝试将__func__(通过任何拼写)与__FILE____LINE__ 组合在一个宏中以生成单个字符串文字的尝试注定要失败。

    显然,您可以使用标准的两步宏机制将文件名和行号生成为字符串:

    #define STR(x) #x
    #define STRINGIFY(x) STR(x)
    
    #define FILE_LINE __FILE__ ":" STRINGIFY(__LINE__)
    

    但是,您不能将函数名称作为字符串文字的一部分。

    有参数认为文件名和行号足以确定问题所在;函数名几乎没有必要。它更美观多于功能,对程序员有一点帮助,但对其他用户没有帮助。

    【讨论】:

    • 好的,我认为这将不得不这样做 - 至少,文件名和行号应该有助于调试消息。
    • 有一个可怕的替代方案,它不是线程安全的并且使用运行时复制:extern const char *file_line_func(const char *file, int line, const char *func); 从宏调用并将信息格式化为静态字符串(因此它不是线程安全的)并返回一个指向该字符串的指针。在可能的使用环境(错误报告)中工作正常,但价格适中。您可以通过将 FILE_LINE 宏的结果作为单个参数来代替 fileline 参数来进行一些改进。但它仍然很丑。很丑。
    猜你喜欢
    • 2018-05-08
    • 1970-01-01
    • 2012-10-15
    • 1970-01-01
    • 1970-01-01
    • 2023-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多