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

jucafojl  于 2023-07-01  发布在  其他
关注(0)|答案(6)|浏览(122)

同时使用gcc和-std=c11以及g和-std=c14。
例如,对于一个名为src/dir/Hello.cxx的文件,它应该扩展为如下内容:

const char basename[] = "Hello";

const char basename[] = getStaticBasename(__FILE__);

其中getStaticBasename()是一个宏(对于C源代码)或constexpr函数(对于C++源代码),其结果为“Hello”。
我必须避免在运行时从__FILE__拆分字符串,因为路径和后缀不能以任何方式编译到可执行文件中。
解决方案必须不依赖于boost等大型库。
因为我没有makefile,所以像this这样的解决方案不能在我的情况下使用。
有办法解决吗?
编辑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(...)放入其代码中。
mwecs4sa

mwecs4sa1#

1. gcc内置函数可以在编译时获取全路径的文件名。

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

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

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

c++11 constexpr函数只能使用return-statement。
示例:

#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;
}

源文件名为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

movl $.LC0+19, %edi.LC0 + 19是不带路径和后缀的文件名字符串的地址

3. c++14 constexpr函数可以用一种简单的方法做到这一点

#include <iostream>

constexpr const char* file_name(const char* path) {
    const char* file = path;
    while (*path) {
        if (*path++ == '/') {
            file = path;
        }
    }
    return file;
}

int main() {
    constexpr const char* file = file_name(__FILE__);
    std::cout << file << std::endl;
    return 0;
}

c++14 constexpr函数可以使用循环和局部变量。
file_name函数将在编译时替换为const char *地址。~

7rfyedvj

7rfyedvj2#

在编译时提取基本文件名,而不需要预处理器技巧和外部脚本?#21440;,没有问题。

#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;
}
cgyqldqp

cgyqldqp3#

如果从源文件所在的文件夹中运行gcc,您将得到一个与传递绝对路径不同的__FILE__(即通过IDE交给gcc)。

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

也许从源所在的路径调用gcc就足够了?

编辑

这里有一个“肮脏”但安全的黑客,它在编译时删除了文件扩展名。不是我推荐的东西,但写起来很有趣:)所以就拿它来说吧。它只在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;
}

联合体分配一个足够大的成员,以容纳完整路径减去扩展和空终止符。它分配一个足够大的成员,以容纳完整的路径减去扩展,尽管有一个空终止符。
如果成员太小,无法容纳完整的__FILE__,则使用尽可能多的__FILE__进行初始化。这在C中是可以的,但在C++中是不允许的。如果__FILE__包含test.c,联合成员现在将被初始化为包含test,没有空终止符。
然而,在该字符串之后仍然会有尾随的零,因为这种黑客滥用了另一个联合成员已经根据“聚合/联合”初始化规则初始化的事实。该规则强制“聚合”中的任何剩余项被初始化,就好像它们具有静态存储持续时间,即初始化为零。它恰好是空终止符的值。

xuo3flqw

xuo3flqw4#

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

#line 0 "Hello"

在文件的顶部,这是,如果所有你想要的是完全隐藏文件名,然后

#line 0 ""

会有用的
如果不想使用Makefile s,可以使用以下命令

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文档中可以看出,您可以完全不带任何扩展名地保存文件,然后将@Lundinoriginal 解决方案与此结合使用。

gcc -xc -o file filename_without_extension

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

628mspwn

628mspwn5#

投票最多的解决方案不依赖于回答OP,因为完整的文件路径存储在二进制文件中,并且仅计算和使用指向路径最后部分的指针(从最后一个'/'字符开始)。
请参见@pexeer answer中建议的解决方案的汇编输出:

.LC0:
        .string "/app/example.cpp"
main:
        push    rax
        mov     esi, OFFSET FLAT:.LC0+5
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
        xor     eax, eax
        pop     rdx
        ret
_GLOBAL__sub_I_main:
        push    rax
        mov     edi, OFFSET FLAT:_ZStL8__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:_ZStL8__ioinit
        pop     rcx
        mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
        jmp     __cxa_atexit

为了避免存储完整的文件路径,您需要类似于以下内容:

#include <iostream>
#include <utility>

constexpr const char* file_name(const char* path) {
    const char* file = path;
    while (*path) {
        if (*path++ == '/') {
            file = path;
        }
    }
    return file;
}

constexpr size_t file_length(const char * path) {
    size_t i = 0;
    const char * file = file_name(path);
    while (*file) { i ++; file++; }
    return i;
}

template<std::size_t... I>
const char * print_impl(std::index_sequence<I...>) {
    static const char file[file_length(__FILE__)+1] = { file_name(__FILE__)[I]...};
    return file;
}

inline const char* print_file() {
    return print_impl(std::make_index_sequence<file_length(__FILE__) + 1>());
}

int main() {
    std::cout<<print_file()<<std::endl;
    return 0;
}

你会得到这个程序集输出(其中没有存储完整的文件路径):

main:
        push    rax
        mov     esi, OFFSET FLAT:print_impl<0ul, 1ul, 2ul, 3ul, 4ul, 5ul, 6ul, 7ul, 8ul, 9ul, 10ul, 11ul>(std::integer_sequence<unsigned long, 0ul, 1ul, 2ul, 3ul, 4ul, 5ul, 6ul, 7ul, 8ul, 9ul, 10ul, 11ul>)::file
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
        xor     eax, eax
        pop     rdx
        ret
_GLOBAL__sub_I_main:
        push    rax
        mov     edi, OFFSET FLAT:_ZStL8__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:_ZStL8__ioinit
        pop     rcx
        mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
        jmp     __cxa_atexit
print_impl<0ul, 1ul, 2ul, 3ul, 4ul, 5ul, 6ul, 7ul, 8ul, 9ul, 10ul, 11ul>(std::integer_sequence<unsigned long, 0ul, 1ul, 2ul, 3ul, 4ul, 5ul, 6ul, 7ul, 8ul, 9ul, 10ul, 11ul>)::file:
        .string "example.cpp"

示例here
这里的基本思想是构造一个静态初始化的char数组,其中只包含所需的字符串(而不是指向包含完整文件路径的静态char数组的指针)。推导文件长度是很简单的,但却是必需的,因为我们不能在 constexpr 函数中调用strlen
然后,技巧是使用整数序列作为文件指针数组中的索引(类似于自然声明:const char f[] = {"str"[0], "str"[1], ...})。整数序列可用于可变参数模板示例化,因此必须在此类上下文中调用它。
GCC将print_impl函数作为符号泄漏(因此它可能比文件的完整路径更大),但它可以在链接器步骤(或使用strip --strip-all /path/to/binary)中剥离

bq8i3lrv

bq8i3lrv6#

不幸的是,似乎每个人都在忙碌着通过代码中的各种神奇方法删除路径中不需要的部分(-->大多数都不起作用)。
在我看来,正确的方法是告诉编译器从宏中更改/删除路径,避免所有修补的需要。使用gcc时,参数名为fmacro-prefix-map。你可以这样使用它:

-fmacro-prefix-map=/path/to/source/=

将“/path/to/source/main.cpp”更改为“main.cpp”
顺便说一下:这也适用于std::source_location,当然完整路径(未更改)不会存储在结果二进制文件中。

相关问题