打印编译时保存的文件名
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
,这很好。这意味着宏中的 if
case 正在工作:)。但是当它是另一个文件夹中的文件时,我无法"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()
.
警告:显示的代码尚未编译。
我的目标是打印文件名而不是文件名的相对路径。我正在使用宏 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
,这很好。这意味着宏中的 if
case 正在工作:)。但是当它是另一个文件夹中的文件时,我无法"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()
.
警告:显示的代码尚未编译。