How to Extract the Source Filename Without Path and Suffix at Compile Time

Getting base name of the source file at compile time

If you're using a make program, you should be able to munge the filename beforehand and pass it as a macro to gcc to be used in your program. For example, in your makefile, change the line:

file.o: file.c
gcc -c -o file.o src/file.c

to:

file.o: src/file.c
gcc "-DMYFILE=\"`basename $<`\"" -c -o file.o src/file.c

This will allow you to use MYFILE in your code instead of __FILE__.

The use of basename of the source file $< means you can use it in generalized rules such as .c.o. The following code illustrates how it works. First, a makefile:

mainprog: main.o makefile
gcc -o mainprog main.o

main.o: src/main.c makefile
gcc "-DMYFILE=\"`basename $<`\"" -c -o main.o src/main.c

Then a file in a subdirectory, src/main.c:

#include <stdio.h>

int main (int argc, char *argv[]) {
printf ("file = %s\n", MYFILE);
return 0;
}

Finally, a transcript showing it running:

pax:~$ mainprog
file = main.c

Note the file = line which contains only the base name of the file, not the directory name as well.

__FILE__ macro shows full path

Try

#include <string.h>

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

For Windows use '\\' instead of '/'.

How do I get the filename without the extension from a path in Python?

Getting the name of the file without the extension:

import os
print(os.path.splitext("/path/to/some/file.txt")[0])

Prints:

/path/to/some/file

Documentation for os.path.splitext.

Important Note: If the filename has multiple dots, only the extension after the last one is removed. For example:

import os
print(os.path.splitext("/path/to/some/file.txt.zip.asc")[0])

Prints:

/path/to/some/file.txt.zip

See other answers below if you need to handle that case.

How to get only file name in preprocessor?

There is no known preprocessor macro that provides the functionality. Passing __FILE__ through a function seams like the only sensible option.

Print filename saved at compile time

Preliminary observations

Note that in C (as opposed to C++), you can't initialize a static const char str[] array with the result of a function call. If the strrchr() found a backslash, you probably want to print the name from one after the backslash. And the stringification isn't going to stringify the result of invoking strrchr().

Also note that you should not create function or variable names that start with an underscore, in general. C11 §7.1.3 Reserved identifiers says (in part):

  • All identifiers that begin with an underscore and either an uppercase letter or another underscore are always reserved for any use.
  • All identifiers that begin with an underscore are always reserved for use as identifiers with file scope in both the ordinary and tag name spaces.

See also What does double underscore (__const) mean in C?

Since the first argument to your TRACE macro is already a string, there's not much benefit to applying the stringification — unless you want the double quotes to appear when the name is printed.

Simple adaptation

To get more or less the result you want, you would need to accept that there'll be run-time overhead invoking strrchr() each time you pass the trace (or a more elaborate scheme for initialization), along the lines of:

#define TRACE(s, ...) \
do { \
const char *basename = strrchr(s, '\\'); \
if (basename == 0) \
basename = s; \
else \
basename++; \
printf(basename, ## __VA_ARGS__); \
} while (0)

The do { … } while (0) idiom is standard; it allows you to write:

if (something)
TRACE("hocuspocus.c: test passed\n");
else
TRACE("abracadabra.c: test failed\n");

If you use the braces-only notation in the question, the semicolon after the first TRACE makes the else into a syntax error. See also C #define macro for debug printing and Why use apparently meaningles do { … } while (0) and if … else statements in macros? and do { … } while (0) — what is it good for?

The ## __VA_ARGS__ trick is fine as long as you know that it is a GCC (and Clang because it is compatible with GCC) extension, and not a part of standard C.

It also isn't entirely clear how you plan to use the variable arguments. It looks as though you'd be able to do:

TRACE("some\\kibbitzer.c: value %d is out of the range [%d..%d]\n",
value, MIN_RANGE, MAX_RANGE);

where the file name is embedded in the format string. Maybe you have in mind:

TRACE(__FILE__ ": value %d is out of the range [%d..%d]\n",
value, MIN_RANGE, MAX_RANGE);

That can work; __FILE__ is a string literal, unlike __func__ which is a predefined identifier (static const char __func__[] = "…function name…";).

Finally (for now), consider whether trace output should go to standard output or to standard error. It is easily arguable it should go to standard error; it (probably) isn't part of the regular output of the program.

I recommend looking at the 'debug macro' question and answer — but I am biassed since I wrote the top-scoring answer.

Reducing runtime overhead

You can reduce the runtime overhead to a single call to strrchr() per file name, as long as you aren't messing with automatic variables etc. You'll be OK if you're using string literals.

#define TRACE(s, ...) \
do { \
static const char *basename = 0;
if (basename == 0) \
{
if ((basename = strrchr(s, '\\')) == 0) \
basename = s; \
else \
basename++; \
} \
printf(basename, ## __VA_ARGS__); \
} while (0)

This initializes the basename to null; on the first pass through the code, basename is set to the correct position in the string; thereafter, there is no further call to strrchr().

Warning: the code shown has not been compiled.

C++ compile-time substring

The idea is to create truncated array of characters, but it needs to use only compile time features. Generating data array through variadic template with pack of char forces compiler to generate data without direct relation to passed string literal. This way compiler cannot use input string literal, especially when this string is long.

Godbolt with clang: https://godbolt.org/z/WdKNjB.

Godbolt with msvc: https://godbolt.org/z/auMEIH.

The only problem is with template depth compiler settings.

First we define int variadic template to store sequence of indexes:

template <int... I>
struct Seq {};

Pushing int to Seq:

template <int V, typename T>
struct Push;

template <int V, int... I>
struct Push<V, Seq<I...>>
{
using type = Seq<V, I...>;
};

Creating sequence:

template <int From, int To>
struct MakeSeqImpl;

template <int To>
struct MakeSeqImpl<To, To>
{
using type = Seq<To>;
};

template <int From, int To>
using MakeSeq = typename MakeSeqImpl<From, To>::type;

template <int From, int To>
struct MakeSeqImpl : Push<From, MakeSeq<From + 1, To>> {};

Now we can make sequence of compile time ints, meaning that MakeSeq<3,7> == Seq<3,4,5,6,7>. Still we need something to store selected characters in array, but using compile time representation, which is variadic template parameter with characters:

template<char... CHARS>
struct Chars {
static constexpr const char value[] = {CHARS...};
};
template<char... CHARS>
constexpr const char Chars<CHARS...>::value[];

Next we something to extract selected characters into Chars type:

template<typename WRAPPER, typename IDXS>
struct LiteralToVariadicCharsImpl;

template<typename WRAPPER, int... IDXS>
struct LiteralToVariadicCharsImpl<WRAPPER, Seq<IDXS...> > {
using type = Chars<WRAPPER::get()[IDXS]...>;
};

template<typename WRAPPER, typename SEQ>
struct LiteralToVariadicChars {
using type = typename LiteralToVariadicCharsImpl<WRAPPER, SEQ> :: type;
};

WRAPPER is a type that contain our string literal.

Almost done. The missing part is to find last slash. We can use modified version of the code found in the question, but this time it returns offset instead of pointer:

static constexpr int PastLastOffset(int last_offset, int cur, const char * const str)
{
if (*str == '\0') return last_offset;
if (*str == '/') return PastLastOffset(cur + 1, cur + 1, str + 1);
return PastLastOffset(last_offset, cur + 1, str + 1);
}

Last util to get string size:

constexpr int StrLen(const char * str) {
if (*str == '\0') return 0;
return StrLen(str + 1) + 1;
}

Combining everything together using define:

#define COMPILE_TIME_PAST_LAST_SLASH(STR)                                   \
[](){ \
struct Wrapper { \
constexpr static const char * get() { return STR; } \
}; \
using Seq = MakeSeq<PastLastOffset(0, 0, Wrapper::get()), StrLen(Wrapper::get())>; \
return LiteralToVariadicChars<Wrapper, Seq>::type::value; \
}()

Lambda function is to have nice, value-like feeling when using this macro. It also creates a scope for defining Wrapper structure. Generating this structure with inserted string literal using macro, leads to situation when the string literal is bounded to type.

Honestly I would not use this kind of code in production. It is killing compilers.

Both, in case of security reasons and memory usage, I would recommend using docker with custom, short paths for building.

How I strip path of source file while writing a compilation rule in makefile?

OK, took me some time, but finally I found the solution (using some threads on this site by the way):

# Defining compilation rules in a way that object files will be produced in current directory, and not in the directory of source files:
all: <List of my targets>

define my_c_rule
$(subst .c,.o,$(notdir $(1))): $(1)
$(CC) $(CFLAGS) $(CDEFINES) $$^ -o $$@
endef
$(foreach f, $(CSRC), $(eval $(call my_c_rule, $(f))))

$(CSRC) contains list of source files with their paths.

Just need to take into account that if earlier I had something like this:

.c.o:
$(CC) $(CFLAGS) $(CDEFINES) $^ -o $@

all: <List of my targets>

...now I have to put all sentence above the rules which I described in my_c_rule procedure. If I don't do this, make stops after compiling first source file. This is because old "wildcard" rules like .c.o or %.o: %.c do not replace all as a default target (even being written earlier), but non-wildcard rules like boot.o: ../../lib1/boot.c (result of the above macros) do replace the default target in case they are written earlier.

display filename, line number and function in C without modifying the source code

If you're on linux, gdb might come handy.

You can compile the code using -g or -g3 option with gcc, then run the binary using gdb ./<executable_name>, set a breakpoint on desired function in any of the source files and check the call.

While stepping through the application, it will show the filename and line number of the executing instruction.

Note: Please check this and this for a detailed understanding.



Related Topics



Leave a reply



Submit