__FILE__宏显示完整路径

217
标准预定义宏__FILE__在C语言中显示文件的完整路径。有没有办法缩短路径并只获取文件名?我的意思是,不要显示完整路径,只显示文件名。
/full/path/to/file.c

我明白了。
to/file.c

或者

file.c

34
希望找到一个仅使用预处理器的解决方案将非常好。我担心基于字符串操作的建议将在运行时执行。 - cdleonard
9
因为你正在使用gcc,所以我认为你可以通过更改传递给命令行的文件名来改变__FILE__的内容。因此,不要使用gcc /full/path/to/file.c,而是尝试cd /full/path/to; gcc file.c; cd -;。当然,如果你依赖于gcc的当前目录来设置包含路径或输出文件位置,则需要更多的修改。编辑:gcc文档表明它是完整路径,而不是输入文件名参数,但在Cygwin上的gcc 4.5.3中,我看到的并非如此。因此,你可以在Linux上尝试一下。 - Steve Jessop
4
GCC 4.5.1(专为arm-none-eabi构建)在其命令行上使用文件名的确切文本。在我的情况下,这是IDE的问题,因为它以所有文件名都被完全限定的方式调用GCC,而不是将当前目录放在某个合理的位置(例如项目文件的位置?)或可配置并从那里使用相对路径。我怀疑很多IDE都会这样做(尤其是在Windows上),出于某种与解释“当前”目录在GUI应用程序中真正位置有关的不适感。 - RBerteig
3
@SteveJessop - 希望您能看到这条评论。 我遇到了一种情况,其中__FILE__被打印为../../../../../../../../rtems/c/src/lib/libbsp/sparc/leon2/../../shared/bootcard.c,我想知道gcc编译文件的位置,以便该文件相对于所示的位置定位。 - Chan Kim
2
这个问题并不是与链接中的问题重复。首先,链接中的问题是关于C++的,因此答案涉及到了C++宏奥秘。其次,在OP的问题中没有强制要求使用宏解决方案。它只是郑重指出一个问题,并提出了一个开放式问题。 - Prof. Falken
显示剩余4条评论
31个回答

216

试一试

#include <string.h>

#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)

对于 Windows 系统,请使用 '\\' 而不是 '/'.


18
" / " 是 Windows 系统中合法的路径分隔符。 - Hans Passant
11
在传递给CreateFile()等函数的文件名中,“/”是有效的路径分隔符。然而,在Windows中并不总是可以随意使用“/”,因为有一个传统(继承自CPM),即在命令提示符中使用“/”作为参数前导符号。但是,高质量的工具应该小心地在斜杠和反斜杠字符处拆分文件名,以避免对那些确实使用“/”的人造成任何问题。 - RBerteig
6
可以在同一个文件名中混合使用两种分隔符号。 - RBerteig
38
这可以简化为 strrchr("/" __FILE__, '/') + 1。通过在 __FILE__ 前面添加 /,可以确保 strrchr 找到某些内容,因此不再需要条件 ?:。 - ɲeuroburɳ
9
我想知道优化器是否能够优化对 strchrstrrchr 的调用,更不用说对 __FILENAME__ 宏的引用了。如果不能,这个解决方案就是完全臃肿的。 - l33t
显示剩余16条评论

60

如果您正在使用cmake,这里有一个提示。来自于:http://public.kitware.com/pipermail/cmake/2013-January/053117.html

我将这个提示复制到这个页面上:

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__FILENAME__='\"$(subst
  ${CMAKE_SOURCE_DIR}/,,$(abspath $<))\"'")

如果你正在使用GNU make,我认为你是可以将其扩展到你自己的makefile中的。例如,你可能会有这样一行代码:
CXX_FLAGS+=-D__FILENAME__='\"$(subst $(SOURCE_PREFIX)/,,$(abspath $<))\"'"

其中$(SOURCE_PREFIX)是您想要移除的前缀。

然后使用__FILENAME__代替__FILE__


16
恐怕这个在头文件中引用的__FILE__无法起作用。 - Baiyan Huang
5
同意 @BaiyanHuang 的观点,但不确定评论是否清晰。 __FILE__ 不是一个简单的预处理器符号,它会变成当前文件,并常用于输出当前文件(头文件或源模块)的名称。而 __FILENAME__ 只会有最外层的源文件名。 - nhed
5
这个答案的解决方案不具备可移植性,因为它使用了Bourne shell逃脱。更好的方法是使用CMake以一种干净且可移植的方式来实现它。请参见此处的为源文件定义文件基础名称宏答案 - Colin D Bennett
4
GNU Make的变体为: CFLAGS += -D__FILENAME__="$(notdir $<)" (说明:此指令用于添加编译选项中的宏定义,其中 "$(notdir $<)" 表示从目标文件或依赖文件列表中提取出的第一个文件名,而 "-D__FILENAME__=" 是一个预定义的宏名称。) - ctuffli
4
在嵌入式平台上,代码大小通常比速度更受限制。如果每个文件中都有调试语句,那么这些完整路径字符串很快就会让文件大小膨胀。我目前正在处理这样的平台,并且在各个位置引用 __FILE__ 会占用大量的代码空间。 - mrtumnus
显示剩余12条评论

51

GCC 8 现在具有 -fmacro-prefix-map-ffile-prefix-map 选项:

-fmacro-prefix-map=old=new

当预处理位于目录 old 中的文件时,将 __FILE____BASE_FILE__ 宏扩展为如果这些文件实际上位于目录 new 中一样。通过使用 . 作为 new 来将绝对路径更改为相对路径,从而使生成的构建更可重复且与位置无关。此选项也会影响编译期间的 __builtin_FILE()。另请参见 -ffile-prefix-map

-ffile-prefix-map=old=new

当编译位于目录 old 中的文件时,将它们的任何引用记录到编译结果中,就像这些文件实际上位于目录 new 中一样。指定此选项等效于指定所有单独的 -f*-prefix-map 选项。这可以用于创建更可重复且与位置无关的构建。另请参见 -fmacro-prefix-map-fdebug-prefix-map.

如果您设置-ffile-prefix-map-fdebug-prefix-map)的路径无效,则会破坏调试功能,除非您告诉调试器如何进行映射(对于gdb: set substitue-path, 对于vscode: "sourceFileMap")。

如果您的意图仅仅是清理 __FILE__,则只需使用-fmacro-prefix-map

例如:我的Jenkins构建中,我将添加-ffile-prefix-map=${WORKSPACE}/=/,还有另一个用于删除本地开发软件包安装前缀。

注意:不幸的是,-ffile-prefix-map-fmacro-prefix-map选项仅在GCC 8及更高版本中可用。例如,在GCC 5中,我们只有-fdebug-prefix-map,它不会影响__FILE__


4
选项-ffile-prefix-map确实隐含了-fdebug-prefix-map-fmacro-prefix-map选项。请参见https://reproducible-builds.org/docs/build-path/上的参考文献。跟踪`-fmacro-prefix-map`和`-ffile-prefix-map`的GCC错误是https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70268。 - Lekensteyn
2
如果你的编译器支持这个功能,就像任何一个相当新的GCC或Clang版本一样,你应该使用它而不是其他解决方案。它更加优雅和轻量级。 - ocroquette

47
我刚想到了一个非常好的解决方案,可以同时处理源文件和头文件,非常高效,并且在所有平台上都能够在编译时工作,而且不需要特定于编译器的扩展。这个解决方案还保留了项目的相对目录结构,所以你知道文件在哪个文件夹中,并且仅相对于项目的根目录。
思路是使用构建工具获取源目录的大小,然后将其添加到__FILE__宏中,完全删除目录,并仅显示从您的源目录开始的文件名。
以下示例使用CMake实现,但没有理由它不能与任何其他构建工具一起使用,因为技巧非常简单。
在CMakeLists.txt文件中,定义一个宏,该宏具有CMake项目路径的长度:
# The additional / is important to remove the last character from the path.
# Note that it does not matter if the OS uses / or \, because we are only
# saving the path size.
string(LENGTH "${CMAKE_SOURCE_DIR}/" SOURCE_PATH_SIZE)
add_definitions("-DSOURCE_PATH_SIZE=${SOURCE_PATH_SIZE}")

在您的源代码中,定义一个__FILENAME__宏,该宏只是将源路径大小添加到__FILE__宏中:
#define __FILENAME__ (__FILE__ + SOURCE_PATH_SIZE)

然后只需使用这个新的宏代替__FILE__宏即可。这是有效的,因为__FILE__路径始终以您的CMake源目录路径开头。通过从__FILE__字符串中删除它,预处理器将负责指定正确的文件名,并且所有内容都将相对于您的CMake项目的根目录。
如果您关心性能,则此方法与使用__FILE__一样高效,因为__FILE__SOURCE_PATH_SIZE均为已知在编译时的常量,因此可以被编译器优化掉。
唯一失败的情况是,如果您正在对生成的文件使用它,并且它们位于非源构建目录上,则可能需要创建另一个宏,使用CMAKE_BUILD_DIR变量而不是CMAKE_SOURCE_DIR

14
起初我不理解这个。我编写了一些例子,并对其进行了gcc和clang测试,它们能够正常运行。我还尝试了仅附加各种字面数字值的实验,它的行为与我的期望相同。然后终于有点明白了。__FILE__是指向字节数组的指针。因此,添加数字字面量只是指针加法。非常聪明的@RenatoUtsch。 - Daniel
1
它只在源文件位于cmake列表目录下时才有效。如果源文件在外部,则会出现错误,可能会访问字面字符串之外的内容。因此请小心处理。 - Andry
实际上并没有减少代码大小。我猜整个路径仍然被编译进二进制文件中,只是指针被修改了。 - Tarion
__FILE__宏和SOURCE_PATH_SIZE宏都是在编译时已知的常量。我期望现代优化编译器能够检测到字符串的一部分未被使用,并将其从二进制文件中删除。无论如何,我认为这几个字节不会对二进制文件大小产生显著影响,所以我并不关心它。 - RenatoUtsch
1
@RenatoUtsch 我正在处理的项目有一个变更,只是指定了文件名,但缺点是将C文件名也赋给了头文件。这个变更是为了获得可重复生成的构建。因此,使用带有-O2选项的gcc,该字符串是否确实被优化并使构建可重复生成? - Paul Stelian
显示剩余2条评论

28
  • C++11

  • msvc2015u3,gcc5.4,clang3.8.0

      template <typename T, size_t S>
      inline constexpr size_t get_file_name_offset(const T (& str)[S], size_t i = S - 1)
      {
          return (str[i] == '/' || str[i] == '\\') ? i + 1 : (i > 0 ? get_file_name_offset(str, i - 1) : 0);
      }
    
      template <typename T>
      inline constexpr size_t get_file_name_offset(T (& str)[1])
      {
          return 0;
      }
    

'

    int main()
    {
         printf("%s\n", &__FILE__[get_file_name_offset(__FILE__)]);
    }

当代码生成编译时偏移量时:

  • gcc: at least gcc6.1 + -O1

  • msvc: put result into constexpr variable:

        constexpr auto file = &__FILE__[get_file_name_offset(__FILE__)];
        printf("%s\n", file);
    
  • clang: insists on not compile time evaluation

有一个技巧可以强制所有3个编译器即使在禁用优化的调试配置下也进行编译时评估:

    namespace utility {

        template <typename T, T v>
        struct const_expr_value
        {
            static constexpr const T value = v;
        };

    }

    #define UTILITY_CONST_EXPR_VALUE(exp) ::utility::const_expr_value<decltype(exp), exp>::value

    int main()
    {
         printf("%s\n", &__FILE__[UTILITY_CONST_EXPR_VALUE(get_file_name_offset(__FILE__))]);
    }

https://godbolt.org/z/u6s8j3


1
你可以定义一个辅助宏#define LEAF(FN) (&FN[UTILITY_CONST_EXPR_VALUE(get_file_name_offset(FN))]),并像这样使用它printf("%s\n", LEAF(__FILE__)); - psyched
这应该是答案。 它不需要cmake、修改makefile或运行时计算。并且可以通过新的__FILENAME__宏使用,使其直观:#define __FILENAME__ (__FILE__ + get_file_name_offset(__FILE__)) - automorphic
1
请注意,“utility”也是C++标准库中使用的命名空间,所以您可能需要重命名命名空间,否则MSVC或编译器可能会产生非常奇怪的错误。对我来说,它会产生缺少const的错误。 我使用了LEAF宏来使事情更加清晰。 - undefined

27

至少对于gcc编译器而言,__FILE__的值是编译器命令行中指定的文件路径。如果像这样编译file.c:

gcc -c /full/path/to/file.c

__FILE__ 将展开为 "/full/path/to/file.c"。如果您这样做:

cd /full/path/to
gcc -c file.c

然后__FILE__将只扩展为"file.c"

这可能实用,也可能不实用。

C标准不要求此行为。它关于__FILE__的说明仅为“当前源文件的假定名称(字符字符串文字)”。

另一种选择是使用#line指令。它可以覆盖当前行号,并可选地覆盖源文件名。如果您想覆盖文件名但保留行号,请使用__LINE__宏。

例如,您可以在file.c顶部附近添加此代码:

#line __LINE__ "file.c"

唯一的问题是它将指定的行号分配给下一行,#line 的第一个参数必须是 数字序列,因此你不能做像这样的事情

#line (__LINE__-1) "file.c"  // This is invalid

确保#line指令中的文件名与实际文件名匹配是留给读者练习的。

至少对于gcc来说,这也会影响诊断消息中报告的文件名。


2
Keith Thompson提供的解决方案非常好,谢谢您。唯一的问题是,似乎这个宏会将__LINE__的值减1。因此,在第x行写入的__LINE__会被评估为x-1。至少在gcc 5.4中是如此。 - elklepo
2
@Klepak:你说得对,这是标准行为。该指令“使实现的行为好像以下源代码行序列以由数字序列指定的行号开始”。而且它必须是数字序列,所以你不能使用#line __LINE__-1, "file.c"。我会更新我的答案。 - Keith Thompson
1
其他编译器如Clang、MSVC或Intel C Compiler会怎么样呢? - Franklin Yu
4
在文件的第 0 行,我只需加入 #line 2 "name.cpp" 即可。另外需要注意的是,在包含该文件的其他文件中不会被污染(至少在 MSVC 上是这样的)。 - Matt Eding

21

这里是纯粹的编译时解决方案。它基于这样一个事实:字符串字面量的sizeof()返回其长度+1。

#define STRIPPATH(s)\
    (sizeof(s) > 2 && (s)[sizeof(s)-2] == '/' ? (s) + sizeof(s) - 1 : \
    sizeof(s) > 3 && (s)[sizeof(s)-3] == '/' ? (s) + sizeof(s) - 2 : \
    sizeof(s) > 4 && (s)[sizeof(s)-4] == '/' ? (s) + sizeof(s) - 3 : \
    sizeof(s) > 5 && (s)[sizeof(s)-5] == '/' ? (s) + sizeof(s) - 4 : \
    sizeof(s) > 6 && (s)[sizeof(s)-6] == '/' ? (s) + sizeof(s) - 5 : \
    sizeof(s) > 7 && (s)[sizeof(s)-7] == '/' ? (s) + sizeof(s) - 6 : \
    sizeof(s) > 8 && (s)[sizeof(s)-8] == '/' ? (s) + sizeof(s) - 7 : \
    sizeof(s) > 9 && (s)[sizeof(s)-9] == '/' ? (s) + sizeof(s) - 8 : \
    sizeof(s) > 10 && (s)[sizeof(s)-10] == '/' ? (s) + sizeof(s) - 9 : \
    sizeof(s) > 11 && (s)[sizeof(s)-11] == '/' ? (s) + sizeof(s) - 10 : (s))

#define __JUSTFILE__ STRIPPATH(__FILE__)

请随意扩展条件运算符级联到项目中最合理的文件名。路径长度并不重要,只要您从字符串的结尾检查得足够远即可。

我会尝试使用宏递归来获取一个类似的没有硬编码长度的宏...


4
好的回答。要在编译时使用这个,你需要至少使用 -O1 - Digital Trauma
2
这不是反过来了吗?你想要找到'/'的最后出现位置,这意味着你应该首先检查sizeof(s)>2。而且,在我的编译中,它在-Os时无法正常工作。完整路径字符串存在于输出二进制文件中。 - mrtumnus
不,这不是纯编译时解决方案。 - Hadatko
@Hadatko请详细说明。在你看来,是什么使得这个不完全是编译时的呢? - undefined

10

最近的Clang编译器有一个__FILE_NAME__宏(请参见这里)。


1
似乎gcc也已经添加了它(https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=1a9b3f04c11eb467a8dc504a37dad57a371a0d4c)。 - rtrrtr

9
在 VC 中,使用 /FC 选项时,__FILE__ 将展开为完整路径;而不使用 /FC 选项时,__FILE__ 将展开为文件名。参考:此处

在VS2019中,如果使用Edit & Continue/ZI d / b,则强制使用/FC - Laurie Stearn
没有使用/FC选项,__FILE__会使用相对路径而不是绝对路径。虽然它更短,但并不是一个“无路径文件名”的解决方案。 - Cem Polat
至少在VS2019和VS2022中,项目文件中底层设置的默认值(UseFullPaths)默认为true,除非在更本地的级别(例如在项目文件中)被覆盖。请参阅Microsoft.Cl.Common.props... - 0xC0000022L

9

我使用了与 @Patrick 的答案相同的解决方案多年。

当完整路径包含符号链接时,它会有一个小问题。

更好的解决方案。

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-builtin-macro-redefined -D'__FILE__=\"$(subst $(realpath ${CMAKE_SOURCE_DIR})/,,$(abspath $<))\"'")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-builtin-macro-redefined -D'__FILE__=\"$(subst $(realpath ${CMAKE_SOURCE_DIR})/,,$(abspath $<))\"'")

为什么要使用它?

  • -Wno-builtin-macro-redefined可以抑制编译器对重新定义__FILE__宏的警告。

    对于不支持此选项的编译器,请参考下面的鲁棒方法

  • 从文件路径中去除项目路径是你的真实需求。你不希望浪费时间去查找header.h文件,src/foo/header.h还是src/bar/header.h

  • 我们应该在cmake配置文件中取消__FILE__宏。

    这个宏在大多数现有的代码中都有使用。简单地重新定义它可以让您自由操作。

    gcc这样的编译器从命令行参数中预定义了这个宏。而完整路径写在cmake生成的makefile中。

  • 需要在CMAKE_*_FLAGS中硬编码。

    有一些命令可以在一些较新版本中添加编译器选项或定义,如add_definitions()add_compile_definitions()。这些命令将在应用于源文件之前解析subst等制作函数。这不是我们想要的。

-Wno-builtin-macro-redefined的鲁棒方法。

include(CheckCCompilerFlag)
check_c_compiler_flag(-Wno-builtin-macro-redefined SUPPORT_C_WNO_BUILTIN_MACRO_REDEFINED)
if (SUPPORT_C_WNO_BUILTIN_MACRO_REDEFINED)
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-builtin-macro-redefined")
endif (SUPPORT_C_WNO_BUILTIN_MACRO_REDEFINED)
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag(-Wno-builtin-macro-redefined SUPPORT_CXX_WNO_BUILTIN_MACRO_REDEFINED)
if (SUPPORT_CXX_WNO_BUILTIN_MACRO_REDEFINED)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-builtin-macro-redefined")
endif (SUPPORT_CXX_WNO_BUILTIN_MACRO_REDEFINED)

记得从 set(*_FLAGS ... -D__FILE__=...) 这行中删除该编译器选项。

1
这对于来自包含文件的内容不起作用。 - chqrlie
你能发一些代码吗?一个常见的情况是在本地范围内设置变量并在另一个范围内使用它。 - Levi.G
1
例如,如果您在定义中使用__FILE__来生成头文件中定义的内联函数的诊断信息,则运行时诊断将报告传递给编译器的文件名,而不是包含文件的名称,而行号将指向包含文件。 - chqrlie
1
是的,它被设计成这样,最常见的用法是 #define LOG(fmt, args...) printf("%s " fmt, __FILE__, ##args)。当使用 LOG() 宏时,您不希望在消息中看到 log.h。毕竟,__FILE__ 宏在每个 C/Cpp 文件(编译单元)中都会被扩展,而不是包含的文件。 - Levi.G

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接