如何在编译时提取没有路径和后缀的源文件名?

同时使用带有-std = c11的gcc和带有-std = c ++ 14的g ++。

例如,对于名为src/dir/Hello.cxx的文件,它应该扩展为例如:

 const char basename[] = "Hello"; 

要么

 const char basename[] = getStaticBasename(__FILE__); 

其中getStaticBasename()是一个宏(对于C源)或constexpr函数(对于C ++源),其结果为“Hello”。

我必须避免在运行时从__FILE__中拆分字符串,因为路径和后缀不能以任何方式编译到可执行文件中。

解决方案必须不依赖于诸如boost之类的大型库。

因为我没有makefile,所以在我的情况下不能使用这样的解决方案。

有人有解决方案吗?

编辑2015-07-02:

  • 我对如何调用编译器和链接器没有影响(有时通过makefile,有时来自命令行,或某些IDE(Eclipse CDT托管make,Crossworks,Xcode等等)。所以解决方案只需要代码。
  • 我的用例是为小型日志记录解决方案提供某种“通用区域标识符”。 应用程序代码(使用我的记录器)应仅#include 并且在稍后调用例如LOG_DEBUG(...)我将隐含地使用自动生成的“通用区域标识符”。
  • 我目前的解决方案是应用程序代码必须声明一个JOE_LOG_FILE_REGION(Hello); (在#include ),它可以在其代码中放置LOG_DEBUG(...)

  • gcc builtin函数可以在编译时获取完整路径的文件名。

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

  • c ++ 11 constexpr也可以在编译时执行此操作。

例:

 #include  constexpr const char* str_end(const char *str) { return *str ? str_end(str + 1) : str; } constexpr bool str_slant(const char *str) { return *str == '/' ? true : (*str ? str_slant(str + 1) : false); } constexpr const char* r_slant(const char* str) { return *str == '/' ? (str + 1) : r_slant(str - 1); } constexpr const char* file_name(const char* str) { return str_slant(str) ? r_slant(str_end(str)) : str; } int main() { constexpr const char *const_file = file_name(__FILE__); puts(const_file); return 0; } 

源文件名是foo/foo1/foo2/foo3/foo4.cpp

使用g++ -o foo.exe foo/foo1/foo2/foo3/foo4.cpp -std=c++11 --save-temps来编译这个文件。

你可以看到这个。

  .file "foo4.cpp" .section .rodata .LC0: .string "foo/foo1/foo2/foo3/foo4.cpp" .text .globl main .type main, @function main: .LFB4: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $16, %rsp movq $.LC0+19, -8(%rbp) movl $.LC0+19, %edi call puts movl $0, %eax leave .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE4: .size main, .-main .ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4" .section .note.GNU-stack,"",@progbits 

如果从源文件所在的文件夹运行gcc,则会获得与传递绝对路径(即通过IDE传递给gcc)不同的__FILE__

  • gcc test.c -otest.exe给了我__FILE__作为gcc test.c -otest.exe
  • gcc c:\tmp\test.c -otest.exe__FILE__作为c:\tmp\test.c gcc c:\tmp\test.c -otest.exe

也许从源所在的路径调用gcc足以解决问题?


编辑

这是一个“脏”但安全的黑客,它在编译时删除了文件扩展名。 不是我推荐的东西,但写起来很有趣:)所以把它当作值得的东西。 它只适用于C.

 #include  #define EXT_LENGTH (sizeof(".c") - 1) // -1 null term typedef union { char filename_no_nul [sizeof(__FILE__)-EXT_LENGTH-1]; // -1 null term char filename_nul [sizeof(__FILE__)-EXT_LENGTH]; } remove_ext_t; int main (void) { const remove_ext_t file = { __FILE__ }; puts(file.filename_nul); return 0; } 

union分配一个足够大的成员来保存完整路径减去扩展名和null终止符。 并且它分配了一个足够大的成员来保存完整路径减去扩展名,但是使用空终止符。

太小而无法容纳完整的__FILE__的成员初始化为尽可能多的__FILE__ 。 这在C中是可以的,但在C ++中是不允许的。 如果__FILE__包含test.c ,则现在将初始化union成员以包含不带null终止符的test

但是在该字符串之后仍然会有尾随零,因为这个hack滥用了根据“aggregate / union”初始化规则初始化了另一个union成员的事实。 此规则强制初始化“聚合”中的任何剩余项目,就好像它们具有静态存储持续时间,即为零。 这恰好是null终止符的值。

在编译时提取基本文件名,没有预处理器技巧,也没有外部脚本? C ++ 14? 没问题,先生。

 #include  #include  using namespace std; namespace detail { constexpr bool is_path_sep(char c) { return c == '/' || c == '\\'; } constexpr const char* strip_path(const char* path) { auto lastname = path; for (auto p = path ; *p ; ++p) { if (is_path_sep(*p) && *(p+1)) lastname = p+1; } return lastname; } struct basename_impl { constexpr basename_impl(const char* begin, const char* end) : _begin(begin), _end(end) {} void write(std::ostream& os) const { os.write(_begin, _end - _begin); } std::string as_string() const { return std::string(_begin, _end); } const char* const _begin; const char* const _end; }; inline std::ostream& operator<<(std::ostream& os, const basename_impl& bi) { bi.write(os); return os; } inline std::string to_string(const basename_impl& bi) { return bi.as_string(); } constexpr const char* last_dot_of(const char* p) { const char* last_dot = nullptr; for ( ; *p ; ++p) { if (*p == '.') last_dot = p; } return last_dot ? last_dot : p; } } // the filename with extension but no path constexpr auto filename = detail::strip_path(__FILE__); constexpr auto basename = detail::basename_impl(filename, detail::last_dot_of(filename)); auto main() -> int { cout << filename << endl; cout << basename << endl; cout << to_string(basename) << endl; return 0; } 

结果很简单,你只需要#line预处理器指令 ,例如

 #line 0 "Hello" 

在文件的顶部,这就是原样,如果您只想完全隐藏文件名

 #line 0 "" 

会工作。

如果您不想使用Makefile ,可以使用它

 file=cfile; content=$(sed -e "1s/^/#line 0 \"$file\"\n/" example/${file}.c); echo $content | gcc -xc -O3 -o ${file} - 

上面的-xc gcc标志意味着(来自gcc的文档):

-x语言:

明确指定以下输入文件的语言(而不是让编译器根据文件名后缀选择默认值)。 此选项适用于所有后续输入文件,直到下一个-x选项。 语言的可能值是:

  c c-header cpp-output c++ c++-header c++-cpp-output objective-c objective-c-header objective-c-cpp-output objective-c++ objective-c++-header objective-c++-cpp-output assembler assembler-with-cpp ada f77 f77-cpp-input f95 f95-cpp-input go java 

如果你没有任何可以帮助你构建源代码的脚本,那么我认为没有办法做到这一点。

此外,您可以从gcc文档的上述引用中看到,您可以保存文件而不需要任何扩展,然后将@Lundin的原始解决方案与此结合使用

 gcc -xc -o file filename_without_extension 

在这种情况下, __FILE__将扩展为"filename_without_extension" ,你可以实现你想要的,虽然你需要在它所在的同一目录中编译文件,因为否则它将包含文件的路径。