【问题标题】:Print filename saved at compile time打印编译时保存的文件名
【发布时间】:2018-05-15 10:55:10
【问题描述】:

我的目标是打印文件名而不是文件名的相对路径。我正在使用宏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 正在工作:)。但是当它是另一个文件夹中的文件时,我无法“字符串化”计算strrchr(s, '\\')。为什么?

此外,我没有看到定义中的计算存在问题,因为所有内容都是在编译时定义的! (这就是if 案例有效的原因,对吧?)

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

有没有办法解决这个问题?

【问题讨论】:

  • 与您的问题无关,但所有以两个前导下划线(或一个前导下划线后跟一个大写字母)开头的符号在所有范围内都保留用于“实现”(编译器和标准库) .你不应该在任何地方自己使用这些符号。
  • @Someprogrammerdude 检查!
  • 我没有看到定义中的计算有问题,因为一切都是在编译时定义的 -- 不,在运行时选择了正确的分支。跨度>
  • TOSTRING(strrchr(s, '\\')) 被转换为"strchr(s, '\\')" 。这是你想要的吗?
  • 请注意,在行尾使用\r\n 比使用\n\r 更传统。

标签: c string macros filenames


【解决方案1】:

初步观察

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

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

  • 以下划线和大写字母或另一个下划线开头的所有标识符始终保留供任何使用。
  • 以下划线开头的所有标识符始终保留用作普通和标记名称空间中具有文件范围的标识符。

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

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

简单适配

要或多或少地获得您想要的结果,您需要接受每次通过跟踪(或更精细的初始化方案)时都会产生调用strrchr() 的运行时开销,如下所示的:

#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 printingWhy use apparently meaningles do { … } while (0) and if … else statements in macros?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…";)。

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

我建议查看“调试宏”问题和答案——但由于我写了得分最高的答案,所以我有偏见。

减少运行时开销

您可以将运行时开销减少到每个文件名对 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()

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

【讨论】:

  • 优秀的答案!很棒的链接。作品!但这就像您所说的“运行时开销”。无论如何,接受,因为这回答了问题,而不是“你不应该那样做” - 答案。
  • 查看附录,了解如何减少运行时开销。
  • 我会的,tnx。但最好我想整体跳过运行时并在编译时使用它,就像使用__FILE__ 时一样。但是随后您将获得整个相对路径。令我惊讶的是__FILENAME__ 不是在 gcc 中构建的
  • 看来% 不是reserved 作为文件名字符。肯定会对 OP 的TRACE() 造成严重破坏。
  • 没有足够的人觉得需要它。如果他们愿意做出并记录改变,“足够的人”可能只需要一个人。但是,由于您使用的是 GCC,因此您应该阅读常用宏的 [手册](gcc.gnu.org/onlinedocs/gcc-8.1.0/cpp/…)。有一个 __BASE_FILE__ 宏。 [……时间流逝……] 哦;这是正在编译的主源文件的__FILE__,即使您当时正在处理头文件或其他文件。没有帮助。
【解决方案2】:

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

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

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

在您的代码中 -

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

每当使用__FILENAME__(foo) 时,都会将其替换为"strrchr(foo, '\\')"。我确信这不是你想要的。

就我个人而言,我认为这里没有使用宏的任何理由。把它变成一个正常的功能。编译器将为您优化它。

【讨论】:

  • 感谢您的意见。但我不能接受这个作为答案。
  • 您可能会注意到您引用的宏的参数是x,但扩展引用了s。自从您编写答案以来,该问题已在问题中得到修复 - 您可能希望将其更新为该问题(因为,正如所写,__FILENAME__(foo) 被转换为 strrchr(s, '\\'),无论作为参数提供什么。
  • @JonathanLeffler 谢谢,我会解决的。我已经从问题中复制了这一行。不知道它是怎么让我眼前一亮的。
猜你喜欢
  • 1970-01-01
  • 2020-05-28
  • 1970-01-01
  • 2018-12-10
  • 2014-01-24
  • 1970-01-01
  • 1970-01-01
  • 2021-08-03
  • 1970-01-01
相关资源
最近更新 更多