__FILE__宏显示完整路径

C语言中可用的标准预定义宏__FILE__显示文件的完整路径。有办法缩短这条路吗?我的意思是

/full/path/to/file.c

我看到

to/file.c

file.c
190691 次浏览

使用basename ()函数,或者,如果你在Windows上,_splitpath ()

#include <libgen.h>


#define PRINTFILE() { char buf[] = __FILE__; printf("Filename:  %s\n", basename(buf)); }

也可以在shell中尝试man 3 basename

试一试

#include <string.h>


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

对于Windows,使用“\\”而不是“/”。

编译时没有办法做到这一点。显然,您可以在运行时使用C运行时实现它,正如其他一些答案所演示的那样,但是在编译时,当预处理器启动时,您就不走运了。

对@red1ynx提议的轻微变化将创建以下宏:

#define SET_THIS_FILE_NAME() \
static const char* const THIS_FILE_NAME = \
strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__;

在每一个。c(pp)文件中添加:

SET_THIS_FILE_NAME();

然后你可以引用THIS_FILE_NAME而不是__FILE__:

printf("%s\n", THIS_FILE_NAME);

这意味着每个.c(pp)文件执行一次构造,而不是每次引用宏时都执行。

它仅限于从.c(pp)文件中使用,不能从头文件中使用。

因为你正在使用GCC,你可以利用

这个宏以C字符串常量的形式展开为主输入文件的名称。这是源文件 在预处理器或C编译器的命令行中指定

然后在编译时通过更改源文件表示(完整路径/相对路径/basename)来控制显示文件名的方式。

只是希望改进一下FILE宏:

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

这捕获/和\,像Czarek Tomczak要求的,这在我的混合环境中工作得很好。

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

我在复制提示,所以都在这一页上:

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

如果您正在使用GNU make,我认为没有理由不能将其扩展到您自己的make文件。例如,你可能有这样的一行:

CXX_FLAGS+=-D__FILENAME__='\"$(subst $(SOURCE_PREFIX)/,,$(abspath $<))\"'"

其中$(SOURCE_PREFIX)是要删除的前缀。

然后使用__FILENAME__代替__FILE__

我做了一个宏__FILENAME__,避免每次都切割完整的路径。问题是将结果文件名保存在cppp -local变量中。

这可以通过在. h文件中定义一个静态全局变量来轻松完成。 此定义在每个包含. h. xml文件的. cpp文件中给出独立的变量。 为了成为多线程的证明,值得使变量也线程本地(TLS).

一个变量存储文件名(压缩后)。另一个保存了__FILE__给出的非剪切值。h文件:

static __declspec( thread ) const char* fileAndThreadLocal_strFilePath = NULL;
static __declspec( thread ) const char* fileAndThreadLocal_strFileName = NULL;

宏本身使用所有逻辑调用方法:

#define __FILENAME__ \
GetSourceFileName(__FILE__, fileAndThreadLocal_strFilePath, fileAndThreadLocal_strFileName)

函数是这样实现的:

const char* GetSourceFileName(const char* strFilePath,
const char*& rstrFilePathHolder,
const char*& rstrFileNameHolder)
{
if(strFilePath != rstrFilePathHolder)
{
//
// This if works in 2 cases:
// - when first time called in the cpp (ordinary case) or
// - when the macro __FILENAME__ is used in both h and cpp files
//   and so the method is consequentially called
//     once with strFilePath == "UserPath/HeaderFileThatUsesMyMACRO.h" and
//     once with strFilePath == "UserPath/CPPFileThatUsesMyMACRO.cpp"
//
rstrFileNameHolder = removePath(strFilePath);
rstrFilePathHolder = strFilePath;
}
return rstrFileNameHolder;
}

removePath()可以以不同的方式实现,但最简单快捷的方法似乎是使用strrchr:

const char* removePath(const char* path)
{
const char* pDelimeter = strrchr (path, '\\');
if (pDelimeter)
path = pDelimeter+1;


pDelimeter = strrchr (path, '/');
if (pDelimeter)
path = pDelimeter+1;


return path;
}

至少对于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的第一个参数必须是digit-sequence,所以你不能做类似的事情

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

确保#line指令中的文件名与文件的实际名称相匹配将留作练习。

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

试一试

#pragma push_macro("__FILE__")
#define __FILE__ "foobar.c"

在源文件中的include语句之后,然后添加

#pragma pop_macro("__FILE__")

在源文件的末尾。

我刚刚想到了一个很好的解决方案,它可以同时使用源文件和头文件,非常有效,并且可以在所有平台的编译时工作,没有特定于编译器的扩展。此解决方案还保留了项目的相对目录结构,因此您可以知道文件在哪个文件夹中,并且只相对于项目的根目录。

其思想是用构建工具获取源目录的大小,并将其添加到__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都是已知的编译时常数,所以编译器可以优化掉它。

唯一会失败的地方是如果你在生成的文件上使用这个,而且它们在一个off-source build文件夹上。然后你可能不得不使用CMAKE_BUILD_DIR变量而不是CMAKE_SOURCE_DIR创建另一个宏。

纯粹的编译时解决方案。它基于这样一个事实:字符串字面值的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__)

请随意将条件操作符级联扩展到项目中最大的合理文件名。路径长度并不重要,只要检查距离字符串的末尾足够远。

我将看看我是否可以得到一个类似的宏,没有硬编码的长度与宏递归…

在VC中,当使用/FC时,__FILE__展开为完整路径,没有/FC选项__FILE__展开文件名。裁判:在这里

如果你在GNU编译器中使用CMAKE,这个global定义工作正常:

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__MY_FILE__='\"$(notdir $(abspath $<))\"'")

下面是一个解决方案,适用于没有字符串库的环境(Linux内核,嵌入式系统等):

#define FILENAME ({ \
const char* filename_start = __FILE__; \
const char* filename = filename_start; \
while(*filename != '\0') \
filename++; \
while((filename != filename_start) && (*(filename - 1) != '/')) \
filename--; \
filename; })

现在只需使用FILENAME而不是__FILENAME__。是的,它仍然是一个运行时的东西,但它是有效的。

下面是一个适用于Linux(路径'/')和Windows('\'和'/'混合)的可移植函数。
使用gcc, clang和vs编译。

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


const char* GetFileName(const char *path)
{
const char *name = NULL, *tmp = NULL;
if (path && *path) {
name = strrchr(path, '/');
tmp = strrchr(path, '\\');
if (tmp) {
return name && name > tmp ? name + 1 : tmp + 1;
}
}
return name ? name + 1 : path;
}


int main() {
const char *name = NULL, *path = NULL;


path = __FILE__;
name = GetFileName(path);
printf("path: %s, filename: %s\n", path, name);


path ="/tmp/device.log";
name = GetFileName(path);
printf("path: %s, filename: %s\n", path, name);


path = "C:\\Downloads\\crisis.avi";
name = GetFileName(path);
printf("path: %s, filename: %s\n", path, name);


path = "C:\\Downloads/nda.pdf";
name = GetFileName(path);
printf("path: %s, filename: %s\n", path, name);


path = "C:/Downloads\\word.doc";
name = GetFileName(path);
printf("path: %s, filename: %s\n", path, name);


path = NULL;
name = GetFileName(NULL);
printf("path: %s, filename: %s\n", path, name);


path = "";
name = GetFileName("");
printf("path: %s, filename: %s\n", path, name);


return 0;
}

标准输出:

path: test.c, filename: test.c
path: /tmp/device.log, filename: device.log
path: C:\Downloads\crisis.avi, filename: crisis.avi
path: C:\Downloads/nda.pdf, filename: nda.pdf
path: C:/Downloads\word.doc, filename: word.doc
path: (null), filename: (null)
path: , filename:

下面是使用编译时计算的解决方案:

constexpr auto* getFileName(const char* const path)
{
const auto* startPosition = path;
for (const auto* currentCharacter = path;*currentCharacter != '\0'; ++currentCharacter)
{
if (*currentCharacter == '\\' || *currentCharacter == '/')
{
startPosition = currentCharacter;
}
}


if (startPosition != path)
{
++startPosition;
}


return startPosition;
}


std::cout << getFileName(__FILE__);

海湾合作委员会 8现在有-fmacro-prefix-map-ffile-prefix-map选项:

-fmacro-prefix-map = < em > < / em > = < em >新老< / em >

当预处理位于目录中的文件时,展开__FILE____BASE_FILE__宏,就好像这些文件位于目录中一样。这可以用于将绝对路径更改为相对路径,通过为使用.,这可以导致更可重复的构建,这些构建与位置无关。这个选项在编译过程中也会影响__builtin_FILE()。另见-ffile-prefix-map

-ffile-prefix-map = < em > < / em > = < em >新老< / em >

当编译位于目录中的文件时,记录所有引用 对他们来说,编译的结果就好像文件就在里面一样 改为目录。指定此选项相当于 指定所有单独的-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

< p >的例子: 因此,对于我的詹金斯构建,我将添加-ffile-prefix-map=${WORKSPACE}/=/,并删除本地开发包安装前缀

请注意不幸的是,-ffile-prefix-map-fmacro-prefix-map选项仅在GCC 8以后可用。例如,对于GCC 5,我们只有-fdebug-prefix-map,它不会影响__FILE__

  • c++ 11
  • < p > 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__)]);
    }
    

Code generates a compile time offset when:

  • 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: persists on not compile time evaluation

There is a trick to force all 3 compilers does compile time evaluation even in the debug configuration with disabled optimization:

    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

如果您最终在本页上寻找一种方法,以从您正在交付的二进制文件中删除指向丑陋构建位置的绝对源路径,那么下面可能适合您的需要。

虽然这并没有产生作者对因为它假设使用CMake所表达的愿望的确切答案,但它非常接近。很遗憾之前没有人提到这一点,因为这可以节省我很多时间。

OPTION(CMAKE_USE_RELATIVE_PATHS "If true, cmake will use relative paths" ON)

将上述变量设置为ON将生成如下格式的构建命令:

cd /ugly/absolute/path/to/project/build/src &&
gcc <.. other flags ..> -c ../../src/path/to/source.c

因此,__FILE__宏将解析为../../src/path/to/source.c

CMake文档 .

但是要注意文档页上的警告:

使用相对路径(可能不起作用!)

它不能保证在所有情况下都能工作,但在我的CMake 3.13 + gcc 4.5中工作

#include <algorithm>
#include <string>
using namespace std;
string f( __FILE__ );
f = string( (find(f.rbegin(), f.rend(), '/')+1).base() + 1, f.end() );


// searches for the '/' from the back, transfers the reverse iterator
// into a forward iterator and constructs a new sting with both

我已经使用相同的解决方案与@帕特里克的回答多年。

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

更好的解决方案。

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__宏。

    对于那些编译器不支持这一点,请参考下面的健壮的方式

    李< /引用> < / >
  • 从文件路径中剥离项目路径是您的real要求。你不会想浪费时间去找出header.h文件,src/foo/header.hsrc/bar/header.h在哪里。

  • 我们应该剥离cmake配置文件中的__FILE__宏。

    此宏在大多数现有代码中使用。只要重新定义它就能让你自由。

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

  • < p > 需要在CMAKE_*_FLAGS中硬代码。

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

-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__=...)行中删除这个编译器选项。

一个简短的,适用于Windows和*nix的答案:

#define __FILENAME__ std::max<const char*>(__FILE__,\
std::max(strrchr(__FILE__, '\\')+1, strrchr(__FILE__, '/')+1))

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

一个扭曲的,甚至更“膨胀”的;red1ynx回答的版本:

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

如果我们发现反斜杠,我们就分反斜杠。否则,拆分为正斜杠。很简单。

几乎任何替代方案都会更简洁(在我看来,c++ constexpr确实是这里的黄金标准)。然而,如果你使用的编译器中__BASE_FILE__不可用,这可能会很有帮助。

我认为这比使用strrchr函数更好。 strfnchr将搜索最后一个删除表'/',并从__FILE__获取文件名 你可以使用__FILE__NAME__代替__FILE__来获取文件名,而不需要完整的文件路径。 STRRCHR解决方案搜索文件名两次每次使用。但这段代码只是1次搜索。 即使在__FILE__中没有分隔符'/',它也能有效工作。 您可以根据需要用\替换它。 strfnchr的源代码通过下面strrchr的源代码进行了改进。我认为它会比strrchr更有效。 https://code.woboq.org/userspace/glibc/string/strrchr.c.html < / p >
inline const char* strfnchr(const char* s, int c) {
const char* found = s;
while (*(s++)) {
if (*s == c)
found = s;
}
if (found != s)
return found + 1;
return s;
}


#define __FILE_NAME__ strfnchr(__FILE__, '/')

GCC 12将提供GNU C扩展宏__FILE_NAME__来获取编译文件的基名。

请参阅已经包含此宏的GCC文档:gcc-common_predefined_macros

GCC线程:Bug 42579 - [PATCH]支持获取文件basename

这个解决方案是基于@RenatoUtsch的答案:

CMake列表:

string(LENGTH "${PROJECT_SOURCE_DIR}/" SOURCE_PATH_SIZE)
add_definitions("-DSOURCE_PATH_SIZE=${SOURCE_PATH_SIZE}")
add_definitions("-DSOURCE_PATH=\"${PROJECT_SOURCE_DIR}\"")

C / c++头文件

#define __FILENAME__ strstr(__FILE__, SOURCE_PATH) + SOURCE_PATH_SIZE

对于Visual Studio,你可以使用/d1trimfile选项。

你可能想把它设置为/d1trimfile:"$(SolutionDir)\":

magic

在MSVC中,将文件名宏作为文件名=%(FileName)%(扩展名)添加到c++项目的预处理器定义中。恐怕这完全是一个编译器杀手。它在某种程度上打破了并行构建。

上面的答案不够好,因为它不是一个编译时const表达式 这里有一个非常简单的解决方案:

 #define FILESTEM(x)                                                        \
std::string_view(x).substr(std::string_view(x).rfind(OS_PATH_SLASH) + 1, \
std::string_view(x).rfind('.') -              \
std::string_view(x).rfind(OS_PATH_SLASH) - 1)

它是一个constexpr,可以在头文件中使用。