How to extract the source filename without path and suffix at compile time?

Deadly 提交于 2019-12-17 10:35:14

问题


Using both gcc with -std=c11 and g++ with -std=c++14.

E.g. for a file named src/dir/Hello.cxx it should expand to something like e.g.:

const char basename[] = "Hello";

or

const char basename[] = getStaticBasename(__FILE__);

as where getStaticBasename() is a macro (for C sources) or constexpr function (for C++ sources) which results to "Hello".

I have to avoid splitting the string from __FILE__ at runtime, because the path and suffix must not be compiled into the executable in any way.

The solution must be without dependencies to huge libraries such as boost.

As I have no makefiles, solutions like this cannot be used in my case.

Did one have a solution for that?

Edit 2015-07-02:

  • I have no influence on how the compiler and linker was invoked (sometimes via makefile, sometimes from command line, or some IDE (Eclipse CDT managed make, Crossworks, Xcode et cetera. So the solution needs to be in code only.
  • My use case is to provide some kind of "generic region identifier" for a small footprint logging solution. The application code (which uses my logger) should only #include <Joe/Logger.h> and within the later calls to e.g. LOG_DEBUG(...) I'll implicitely take use of the automatically generated "generic region identifier".
  • My current solution is that the application code have to declare a JOE_LOG_FILE_REGION(Hello); (after #include <Joe/Logger.h>) before it could place LOG_DEBUG(...) in its code.

回答1:


  • gcc builtin function can get the file name of a full path at compile time.

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

or

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

  • c++11 constexpr also can do this at compile time.

example:

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

source file name is foo/foo1/foo2/foo3/foo4.cpp

use g++ -o foo.exe foo/foo1/foo2/foo3/foo4.cpp -std=c++11 --save-temps to compile this file.

you can see this.

.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 is the address of file name string without path and suffix




回答2:


If you run gcc from the folder where the source file is located, you will get a different __FILE__ than if you pass an absolute path (i.e. handed to gcc through an IDE).

  • gcc test.c -otest.exe gives me __FILE__ as test.c.
  • gcc c:\tmp\test.c -otest.exe gives me __FILE__ as c:\tmp\test.c.

Perhaps calling gcc from the path where the source is located is sufficient as work-around?


EDIT

Here is a "dirty" but safe hack which removes the file extension in compile-time. Not really something I'd recommend, but it was fun to write :) So take it for what it is worth. It only works in 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;
}

The union allocates one member which is large enough to hold the full path minus extension and null terminator. And it allocates one member which is large enough to hold the full path minus extension, though with a null terminator.

The member which is too small to hold the full __FILE__ is initialized with as much of __FILE__ as can fit. This is ok in C but not allowed in C++. If __FILE__ contains test.c, the union member will now be initialized to contain test with no null terminator.

There will however still be trailing zeroes after that string, because this hack abuses the fact that the other union member has been initialized according to the rules of "aggregate/union" initialization. This rule forces any remaining items in the "aggregate" to be initialized as if they had static storage duration, i.e to zero. Which happens to be the value of the null terminator.




回答3:


extract the base filename at compile time with no preprocessor tricks and no external scripts? c++14? no problem sir.

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



回答4:


It turns out to be very simple, you just need the #line preprocessor directive, example

#line 0 "Hello"

at the top of the file, this as is, if all you want is to hide the file name completely then

#line 0 ""

would work.

If you don't want to use Makefiles, you can use this

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

The -xc gcc flag above means (from gcc's documentation):

-x language:

Specify explicitly the language for the following input files (rather than letting the compiler choose a default based on the file name suffix). This option applies to all following input files until the next -x option. Possible values for language are:

          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

If you don't have any sort of script that helps you building the source then there is no way to do it I think.

Also, you can see from the above quote of the gcc documentation, that you can save the files without any extension at all, and then combine @Lundin's original solution with this and use

gcc -xc -o file filename_without_extension

in this case __FILE__ would expand to "filename_without_extension", and you would achieve what you want, although you need to compile the file in the same directory where it lives, because otherwise it will contain the path to the file.



来源:https://stackoverflow.com/questions/31050113/how-to-extract-the-source-filename-without-path-and-suffix-at-compile-time

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!