I want to get the name of the compiled file without extension as a constant in PROGMEM. Partially the name can be cleared like this:
#define __FILENAME__ strrchr(__FILE__, '\\') + 1
Okay, it's easy. Next I need to delete the file extension, I can do this with the code:
char *dot = strrchr(__FILENAME__, '.'); *dot = '\0'
But I can't get all-in-one inside #define because of step-by-step actions sequence and an redundant variable.
Is there a pretty solution? Length of the filename is unknown.
The option of clearing the name in the program body is an extra expense of code and memory.
How to use #define for get filename without file extension
It is not possible to use C preprocessor to get a filename with file extension - C preprocessor has no capability of parsing strings.
#define __FILENAME__
Defining identifiers starting with double __
is invalid. They are reserved, you can't define such your own identifiers.
Your code will fail miserably when the __FILE__
does not contain a slash or a dot.
Next I need to delete the file extension, I can do this with the code:
The code you presented is invalid. You can't modify __FILE__
, it is a constant. If you are using runtime, you have to malloc
the required memory or allocate the memory on stack.
Potentially, you could execute a function on program startup to fix the value, or just call the function right after main
. The happy assumption here is that filename without extension will always be smaller than __FILE__
, so we can overallocate memory.
static char file_we[] = __FILE__;
__attribute__((__constructor__))
void _fix_file_we_() {
// modify file_we
char *const slash = strrchr(__FILE__, '\\');
if (slash) {
strmove(file_we, slash + 1);
}
chat *const dot = strrchr(file_we, '.');
if (dot) {
*dot = '\0';
}
}
With GCC, there are__attribute__((__constructor__))
and __FILE_NAME__
extension you might be interested in.
and store it to PROGMEM
It is impossible to get a string literal of a filename without extension in C programming language. You have to use an external program or an extension.
With the above approach and using GCC, you can just instead compute only the length of file_we
at runtime and store __FILE_NAME__
in a constant static variable. You will lose 2 bytes for .c
, but that is really not much.
#include <stdio.h>
#include <string.h>
static const char file_name[] = __FILE_NAME__;
int main() {
printf("%.*s\n", (int)strcspn(file_name, "."), file_name);
}
In this case, we might precompute the dot position with a constant expression, given the string is not long. The compilation time with longer strings when writing such long ternary expressions might increase.
#include <stdio.h>
#include <limits.h>
#define FORRANGE_0(f, ...) f(0, __VA_ARGS__)
#define FORRANGE_1(f, ...) FORRANGE_0(f, __VA_ARGS__) f(1, __VA_ARGS__)
#define FORRANGE_2(f, ...) FORRANGE_1(f, __VA_ARGS__) f(2, __VA_ARGS__)
#define FORRANGE_3(f, ...) FORRANGE_2(f, __VA_ARGS__) f(3, __VA_ARGS__)
#define FORRANGE_4(f, ...) FORRANGE_3(f, __VA_ARGS__) f(4, __VA_ARGS__)
#define FORRANGE_5(f, ...) FORRANGE_4(f, __VA_ARGS__) f(5, __VA_ARGS__)
#define FORRANGE_6(f, ...) FORRANGE_5(f, __VA_ARGS__) f(6, __VA_ARGS__)
#define FORRANGE_7(f, ...) FORRANGE_6(f, __VA_ARGS__) f(7, __VA_ARGS__)
#define FORRANGE_8(f, ...) FORRANGE_7(f, __VA_ARGS__) f(8, __VA_ARGS__)
#define FORRANGE_9(f, ...) FORRANGE_8(f, __VA_ARGS__) f(9, __VA_ARGS__)
#define FORRANGE(f, n, ...) FORRANGE_##n(f, __VA_ARGS__)
#define DOT_POS_CASE(n, f) f[n]=='.' ? n :
#define DOT_POS(f) ( \
!f[0] ? INT_MAX : \
!f[1] ? FORRANGE(DOT_POS_CASE, 0, f) INT_MAX : \
!f[2] ? FORRANGE(DOT_POS_CASE, 1, f) INT_MAX : \
!f[3] ? FORRANGE(DOT_POS_CASE, 2, f) INT_MAX : \
!f[4] ? FORRANGE(DOT_POS_CASE, 3, f) INT_MAX : \
!f[5] ? FORRANGE(DOT_POS_CASE, 4, f) INT_MAX : \
!f[6] ? FORRANGE(DOT_POS_CASE, 5, f) INT_MAX : \
!f[7] ? FORRANGE(DOT_POS_CASE, 6, f) INT_MAX : \
!f[8] ? FORRANGE(DOT_POS_CASE, 7, f) INT_MAX : \
!f[9] ? FORRANGE(DOT_POS_CASE, 8, f) INT_MAX : \
/* TODO: add more to handle longer strings */ \
INT_MAX)
static const char file_name[] = __FILE_NAME__;
static int file_name_dot_pos = DOT_POS(__FILE_NAME__);
int main() {
printf("%.*s\n", file_name_dot_pos, file_name);
}
In real life, typically, the build system is configured to precompute the value and pass it using compiler options for each file separately, like -DFILE_WE="something"
.
For example, the following CMake script:
cmake_minimum_required(VERSION 3.11)
project(test)
add_executable(tgt main.c)
get_property(srcs TARGET tgt PROPERTY SOURCES)
foreach(src IN LISTS srcs)
get_filename_component(file_we ${src} NAME_WE)
set_property(
SOURCE ${src}
APPEND PROPERTY COMPILE_DEFINITIONS
"FILE_WE=\"${file_we}\""
)
endforeach()
include(CTest)
add_test(NAME tgt COMMAND tgt)
set_tests_properties(tgt PROPERTIES PASS_REGULAR_EXPRESSION main)
Compiles a program:
#include <stdio.h>
int main() { printf("%s\n", FILE_WE); }
With:
/usr/bin/cc -DFILE_WE=\"main\" .....main.c
Or you can use a better preprocessor like m4
or jinja2
or php
.