Reputation: 3160
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:
#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".JOE_LOG_FILE_REGION(Hello);
(after #include <Joe/Logger.h>
) before it could place LOG_DEBUG(...)
in its code.Upvotes: 36
Views: 34898
Reputation: 138
I simplified @Lundin's solution to use an anonymous union:
#include <stdio.h>
#define BASE_FILE(ext) (((union { char const f[sizeof(__FILE__) - (sizeof(ext))]; }){__FILE__}).f)
#define BASE_C_FILE BASE_FILE(".c")
int main()
{
puts(BASE_C_FILE);
return 0;
}
If the extension is not .c
, you can just call BASE_FILE("<extension>")
.
The main problem with this solution is that BASE_FILE(ext)
expands to a char const[]
instead of a string literal like __FILE__
.
Also it causes a warning such as:
warning: initializer-string for array of ‘char’ is too long
depending on your compiler.
I also removed the non-null terminated string, as it's basically a segfault generator.
Upvotes: 0
Reputation: 962
Unfortunately it seems everyone is busy removing the unwanted part of the path via various magical ways in code (--> most of them not working).
In my opinion the correct way is to tell the compiler to change/remove the path from the macro avoiding all need for tinkering. With gcc the parameter is called fmacro-prefix-map. You would use it like this:
-fmacro-prefix-map=/path/to/source/=
to change "/path/to/source/main.cpp" to just "main.cpp"
By the way: this also works for std::source_location and of course the full path (unaltered) is not stored in the resulting binary.
Upvotes: 4
Reputation: 3584
The most voted solution does not rely answer the OP, because the complete file path is stored in the binary and only a pointer to the last part of the path is computed (from the last '/' character) and used.
See assembly output of the proposed solution in @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
To avoid storing the complete file path, you'll need something like this:
#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;
}
and you'll get this assembly output (where the complete file path isn't stored):
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"
Example here
The basic idea here is to construct a statically initialized char array containing only the string you want (and not a pointer to a static char array containing the full file path). Deducing the file length is trivial but required since we can't call strlen
in a constexpr function.
Then the trick is to use a integer sequence as indices in the file's pointed array (like natural declaration: const char f[] = {"str"[0], "str"[1], ...}
). The integer sequence can be used in variadic template instantiation, so it must be called in such context.
GCC leaks the print_impl
function as a symbol (so it's likely larger that the file's full path), but it can be stripped later on at linker step (or with strip --strip-all /path/to/binary
)
Upvotes: 3
Reputation: 695
#define __FILENAME__ (__builtin_strrchr(__FILE__, '/') ? __builtin_strrchr(__FILE__, '/') + 1 : __FILE__)
or
#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
c++11 constexpr function can only use a return-statement.
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
#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 function can use loop and local variable.
the file_name
function will replace with a address of const char *
at compiler time.
~
Upvotes: 40
Reputation: 69854
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;
}
Upvotes: 12
Reputation: 213276
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.
Upvotes: 9
Reputation: 53006
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 Makefile
s, 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.
Upvotes: 5