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

Joe*_*Joe 23 c c++ c++11 c11 c++14

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

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

const char basename[] = "Hello";
Run Code Online (Sandbox Code Playgroud)

要么

const char basename[] = getStaticBasename(__FILE__);
Run Code Online (Sandbox Code Playgroud)

如何getStaticBasename()是一个宏(对于C源)或constexpr函数(对于C++源),其结果为"Hello".

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

解决方案必须不依赖于大型库,例如boost.

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

有人有解决方案吗?

编辑2015-07-02:

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

pex*_*eer 22

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

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

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

例:

#include <stdio.h>

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;
}
Run Code Online (Sandbox Code Playgroud)

源文件名是 #define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)

用于foo/foo1/foo2/foo3/foo4.cpp编译此文件.

你可以看到这个.

.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
Run Code Online (Sandbox Code Playgroud)

  • 我已经使用简单的* strrchr *和* strstr *尝试了您的示例,并且在两种情况下都进行了优化,因此似乎没有必要使用__builtin_strrchr。编译器:“ GCC:(Debian 6.3.0-18 + deb9u1)6.3.0 20170516”。 (2认同)
  • 这些解决方案保留了后缀,因此不能满足问题。或者我错过了什么? (2认同)

Lun*_*din 8

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

  • gcc test.c -otest.exe给我__FILE__test.c.
  • gcc c:\tmp\test.c -otest.exe给我__FILE__c:\tmp\test.c.

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


编辑

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

#include <stdio.h>

#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;
}
Run Code Online (Sandbox Code Playgroud)

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

太小而不能装满的成员__FILE__用尽可能多的东西进行初始化__FILE__.这在C中是可以的,但在C++中是不允许的.如果__FILE__包含test.c,则联合成员现在将被初始化为包含test无null终止符.

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


Ric*_*ges 7

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

#include <iostream>
#include <string>

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;
}
Run Code Online (Sandbox Code Playgroud)

  • @RichardHodges:不仅如此,您的解决方案是唯一真正删除后缀的 C++ 解决方案。 (4认同)
  • 可爱,constexpr 这个东西真的想成为它自己的编程语言,比如 PHP。 (2认同)

Iha*_*imi 5

事实证明非常简单,你只需要#line预处理器指令,例如

#line 0 "Hello"
Run Code Online (Sandbox Code Playgroud)

在文件的顶部,按原样,如果您想要的只是完全隐藏文件名,那么

#line 0 ""
Run Code Online (Sandbox Code Playgroud)

会工作。

如果你不想使用Makefiles,你可以使用这个

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

上面的 gcc标志-xc意味着(来自 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
Run Code Online (Sandbox Code Playgroud)

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

另外,您可以从 gcc 文档的上述引用中看到,您可以保存完全不带任何扩展名的文件,然后将 @ Lundin原始解决方案与此结合起来并使用

gcc -xc -o file filename_without_extension
Run Code Online (Sandbox Code Playgroud)

在这种情况下__FILE__将扩展为"filename_without_extension",并且您将实现您想要的效果,尽管您需要在文件所在的同一目录中编译该文件,因为否则它将包含该文件的路径。

  • 这看起来不太有帮助。如果您必须将感兴趣的字符串放入源中,无论如何,您也可以直接使用它来初始化变量。 (3认同)