我的目标是打印文件名而不是文件名的相对路径。我正在使用宏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正在运行:)。但是当它是另一个文件夹中的文件时,我无法“字符串化”计算strrchr(s, '\\')
。为什么?
此外,我没有看到定义中的计算问题,因为一切都是在编译时定义的! (这就是为什么if
案件有效,对吧?)
如果我从TOSTRING()
删除__FILENAME__
,我会遇到大量错误。因为它无法将__FILENAME__
的输出与str[]
连接起来
有办法解决这个问题吗?
请注意,在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 printing和Why 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()
。
警告:显示的代码尚未编译。
我认为理解宏和函数是如何工作存在一些问题。
宏不是“执行”的,它们只是简单的文本替换。是的,这发生在编译时(实际上是预编译),但只是替换。
编译时,宏不会执行和编码或调用任何函数(如strrchr
)。
在您的代码中,您有 -
#define __FILENAME__(x) TOSTRING(strrchr(x, '\\'))
每当使用__FILENAME__(foo)
时,它将被替换为"strrchr(foo, '\\')"
。我确信这不是你想要的。
就个人而言,我认为没有任何理由在这里使用宏。只需将其变为正常功能即可。编译器将为您优化它。