打印编译时保存的文件名

Print filename saved at compile time

我的目标是打印文件名而不是文件名的相对路径。我正在使用宏 TRACE() 对其进行试验。 因为它们都在同一个文件中,所以我将文件名模拟为 TRACE() 的输入。所以在现实生活中,你可以说输入被替换为 __FILE__.

代码:

#include <stdio.h>
#include <string.h>

#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)

#define __FILENAME__(x) TOSTRING(strrchr(x, '\'))

#define TRACE(s, ...) \
    { \
    if (strrchr(s, '\')) { \
        static const char str[] = __FILENAME__(s) "\n\r"; \
        printf(str, ##__VA_ARGS__); \
    } else { \
        static const char str[] = s "\n\r"; \
        printf(str, ##__VA_ARGS__); \
    } \
    }

int main() {
    TRACE("file.c");
    TRACE("parent\file.c");
    return 0;
}

输出:

file.c
strrchr("parent\file.c", '\')

因此,如果它是本地文件,它会打印为 file.c,这很好。这意味着宏中的 ifcase 正在工作:)。但是当它是另一个文件夹中的文件时,我无法"stringify"计算strrchr(s, '\')。为什么?

此外,我没有发现定义中的计算有问题,因为一切都是在编译时定义的!! (这就是 if 案例有效的原因,对吧?)

如果我从 __FILENAME__ 中删除 TOSTRING(),则会出现大量错误。因为它无法将 __FILENAME__ 的输出与 str[]

连接起来

有办法解决吗?

我认为对宏和函数的工作原理的理解存在一些问题。

宏不是 "executed",它们只是简单的文本替换。是的,这发生在编译时(实际上是预编译),但只是替换。

宏在编译时不会执行和编码或调用任何函数(如 strrchr)。

在你的代码中你有 -

#define __FILENAME__(x) TOSTRING(strrchr(x, '\'))

每当使用 __FILENAME__(foo) 时,它都会被替换为 "strrchr(foo, '\')"。我确定这不是您想要的。

就个人而言,我看不出有任何理由在这里使用宏。把它变成一个正常的功能。编译器会为你优化它。

初步观察

请注意,在 C(与 C++ 相对)中,您无法使用函数调用的结果来初始化 static const char str[] 数组。如果 strrchr() 找到一个反斜杠,您可能想要从反斜杠后面的一个开始打印名称。并且字符串化不会对调用 strrchr().

的结果进行字符串化

另请注意,一般情况下,您不应创建以下划线开头的函数或变量名称。 C11 §7.1.3 Reserved identifiers 说(部分):

  • All identifiers that begin with an underscore and either an uppercase letter or another underscore are always reserved for any use.
  • All identifiers that begin with an underscore are always reserved for use as identifiers with file scope in both the ordinary and tag name spaces.

另见 What does double underscore (__const) mean in C?

由于您的 TRACE 宏的第一个参数已经是一个字符串,因此应用字符串化没有太大好处 — 除非您希望在打印名称时出现双引号。

简单改编

为了或多或少地获得您想要的结果,您需要接受每次通过跟踪(或更详细的初始化方案),按照以下行:

#define TRACE(s, ...) \
    do { \
        const char *basename = strrchr(s, '\'); \
        if (basename == 0) \
            basename = s; \
        else \
            basename++; \
        printf(basename, ## __VA_ARGS__); \
    } while (0)

do { … } while (0)成语标准;它允许你写:

if (something)
    TRACE("hocuspocus.c: test passed\n");
else
    TRACE("abracadabra.c: test failed\n");

如果您在问题中使用仅大括号表示法,则第一个 TRACE 之后的分号会使 else 成为语法错误。另见 C #define macro for debug printing and Why use apparently meaningles do { … } while (0) and if … else statements in macros? and do { … } while (0) — what is it good for?

## __VA_ARGS__ 技巧很好,只要您知道它是 GCC(和 Clang,因为它与 GCC 兼容)扩展,而不是标准 C 的一部分。

您还不完全清楚您打算如何使用变量参数。看起来你可以做到:

TRACE("some\kibbitzer.c: value %d is out of the range [%d..%d]\n",
      value, MIN_RANGE, MAX_RANGE);

格式字符串中嵌入了文件名。也许你已经想到了:

TRACE(__FILE__ ": value %d is out of the range [%d..%d]\n",
      value, MIN_RANGE, MAX_RANGE);

那行得通; __FILE__ 是一个字符串文字,与 __func__ 不同,后者是预定义的标识符 (static const char __func__[] = "…function name…";)。

最后(现在),考虑跟踪输出应该转到标准输出还是标准错误。很容易争论它应该进入标准错误;它(可能)不是程序常规输出的一部分。

我建议查看 'debug macro' 问答 — 但我有偏见,因为我写了得分最高的答案。

减少运行时间开销

您可以将 运行 时间开销减少到对每个文件名 strrchr() 的一次调用,只要您不弄乱自动变量等。如果您'正在使用字符串文字。

#define TRACE(s, ...) \
    do { \
        static const char *basename = 0;
        if (basename == 0) \
        {
            if ((basename = strrchr(s, '\')) == 0) \
                basename = s; \
            else \
                basename++; \
        } \
        printf(basename, ## __VA_ARGS__); \
    } while (0)

这会将 basename 初始化为 null;在第一次通过代码时,basename 被设置为字符串中的正确位置;此后,不再调用 strrchr().

警告:显示的代码尚未编译。