Как извлечь исходное имя файла без пути и суффикса во время компиляции?
Использование gcc с -std = c11 и g++ с -std = С++ 14.
например. для файла с именем src/dir/Hello.cxx
он должен расширяться до значения, например:
const char basename[] = "Hello";
или
const char basename[] = getStaticBasename(__FILE__);
где getStaticBasename()
- макрос (для источников C) или функция constexpr (для источников С++), что приводит к "Hello".
Мне нужно избегать разделения строки из __FILE__
во время выполнения, потому что путь и суффикс не должны компилироваться в исполняемый файл каким-либо образом.
Решение должно быть без зависимостей от огромных библиотек, таких как boost.
Поскольку у меня нет файлов makefile, такие решения, как this, не могут быть использованы в моем случае.
У кого-то есть решение для этого?
Изменить 2015-07-02:
- Я не влияю на то, как был вызван компилятор и компоновщик (иногда через make файл, иногда из командной строки или некоторую IDE (Eclipse CDT managed make, Crossworks, Xcode et cetera). Поэтому решение должно быть только в коде.
- Моим вариантом использования является предоставление какого-то "идентификатора общего региона" для небольшого решения регистрации каротажа. Код приложения (который использует мой регистратор) должен только
#include <Joe/Logger.h>
и в последующих вызовах, например. LOG_DEBUG(...)
Я буду неявно использовать автоматически сгенерированный "идентификатор общего региона".
- Мое текущее решение состоит в том, что код приложения должен объявить
JOE_LOG_FILE_REGION(Hello);
(после #include <Joe/Logger.h>
), прежде чем он сможет разместить LOG_DEBUG(...)
в своем коде.
Ответы
Ответ 1
- Функция gcc встроенная может получить имя файла полного пути во время компиляции.
#define __FILENAME__ (__builtin_strrchr(__FILE__, '/') ? __builtin_strrchr(__FILE__, '/') + 1 : __FILE__)
- С++ 11 constexpr также может сделать это во время компиляции.
Пример:
#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
Ответ 2
Если вы запустите gcc из папки, где находится исходный файл, вы получите другой __FILE__
, чем если бы вы передали абсолютный путь (т.е. передали gcc через среду IDE).
-
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, но не разрешено в С++. Если __FILE__
содержит test.c
, член объединения теперь будет инициализирован, чтобы содержать test
без нулевого терминатора.
Тем не менее, после этой строки все еще остаются конечные нули, поскольку этот хак злоупотребляет тем, что другой член союза был инициализирован в соответствии с правилами инициализации "агрегация/объединение". Это правило заставляет все оставшиеся элементы в "агрегате" инициализироваться так, как если бы они имели статическую продолжительность хранения, то есть до нуля. Который является значением нулевого терминатора.
Ответ 3
извлечь базовое имя файла во время компиляции без препроцессорных трюков и внешних скриптов? С++ 14? нет проблем сэр.
#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
Это очень просто, вам просто нужна директива #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
Если у вас нет каких-либо script, которые помогут вам создать источник, тогда нет никакого способа сделать это, я думаю.
Кроме того, вы можете видеть из приведенной выше цитаты документации gcc, что вы можете сохранить файлы без какого-либо расширения вообще, а затем объединить @оригинальное решение Lundin с этим и используйте
gcc -xc -o file filename_without_extension
в этом случае __FILE__
будет расширяться до "filename_without_extension"
, и вы достигнете того, чего хотите, хотя вам нужно скомпилировать файл в том же каталоге, где он живет, потому что в противном случае он будет содержать путь к файлу.