Как извлечь исходное имя файла без пути и суффикса во время компиляции?

Использование 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", и вы достигнете того, чего хотите, хотя вам нужно скомпилировать файл в том же каталоге, где он живет, потому что в противном случае он будет содержать путь к файлу.