Макрос __FILE__ показывает полный путь

Стандартный предопределенный MACRO __FILE__, доступный на C, показывает полный путь к файлу. Есть ли способ сократить путь? Я имею в виду вместо

/full/path/to/file.c

Я вижу

to/file.c

или

file.c

Ответ 1

Try

#include <string.h>

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

Для Windows используйте '\\' вместо '/'.

Ответ 2

Вот совет, если вы используете cmake. Из: http://public.kitware.com/pipermail/cmake/2013-January/053117.html

Я копирую наконечник так, чтобы все это на этой странице:

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__FILENAME__='\"$(subst
  ${CMAKE_SOURCE_DIR}/,,$(abspath $<))\"'")

Если вы используете GNU make, я не вижу причин, по которым вы не могли бы распространять это на свои собственные make файлы. Например, у вас может быть такая строка:

CXX_FLAGS+=-D__FILENAME__='\"$(subst $(SOURCE_PREFIX)/,,$(abspath $<))\"'"

где $(SOURCE_PREFIX) - префикс, который вы хотите удалить.

Затем используйте __FILENAME__ вместо __FILE__.

Ответ 3

Я только что подумал о отличном решении этого вопроса, которое работает как с исходными, так и с заголовочными файлами, очень эффективно и работает во время компиляции на всех платформах без расширения для компилятора. Это решение также сохраняет относительную структуру каталогов вашего проекта, поэтому вы знаете, в какой папке находится файл, и только относительно корня вашего проекта.

Идея состоит в том, чтобы получить размер исходного каталога с помощью инструмента построения и просто добавить его в макрос __FILE__, полностью удалив каталог и указывая только имя файла, начиная с исходного каталога.

Следующий пример реализован с использованием CMake, но нет причин, по которым он не будет работать с другими инструментами сборки, потому что трюк очень прост.

В файле CMakeLists.txt определите макрос, который имеет длину пути к вашему проекту в CMake:

# The additional / is important to remove the last character from the path.
# Note that it does not matter if the OS uses / or \, because we are only
# saving the path size.
string(LENGTH "${CMAKE_SOURCE_DIR}/" SOURCE_PATH_SIZE)
add_definitions("-DSOURCE_PATH_SIZE=${SOURCE_PATH_SIZE}")

В исходном коде укажите макрос __FILENAME__, который просто добавляет размер исходного пути к макросу __FILE__:

#define __FILENAME__ (__FILE__ + SOURCE_PATH_SIZE)

Затем просто используйте этот новый макрос вместо макроса __FILE__. Это работает, потому что путь __FILE__ всегда начинается с пути к вашему исходному директорию CMake. Удалив его из строки __FILE__, препроцессор позаботится о том, чтобы указать правильное имя файла, и все это будет относиться к корню вашего проекта CMake.

Если вы заботитесь о производительности, это так же эффективно, как и использование __FILE__, потому что как __FILE__, так и SOURCE_PATH_SIZE являются известными константами времени компиляции, поэтому его можно оптимизировать компилятором.

Единственное место, где это может быть неудачно, - это использовать это в сгенерированных файлах, и они находятся в папке с исходным кодом. Тогда вам, вероятно, придется создать другой макрос, используя переменную CMAKE_BUILD_DIR вместо CMAKE_SOURCE_DIR.

Ответ 4

По крайней мере для gcc значение __FILE__ - это путь к файлу, указанный в командной строке компилятора. Если вы скомпилируете file.c следующим образом:

gcc -c /full/path/to/file.c

__FILE__ расширится до "/full/path/to/file.c". Если вы вместо этого сделаете это:

cd /full/path/to
gcc -c file.c

тогда __FILE__ расширится до "file.c".

Это может или не может быть практичным.

Стандарт C не требует такого поведения. Все, что он говорит о __FILE__ это то, что он расширяется до "Предполагаемое имя текущего исходного файла (символьная строка литерала)".

Альтернативой является использование директивы #line. Он переопределяет текущий номер строки и, необязательно, имя исходного файла. Если вы хотите переопределить имя файла, но оставить номер строки в покое, используйте макрос __LINE__.

Например, вы можете добавить это в верхней части file.c:

#line __LINE__ "file.c"

Единственная проблема с этим состоит в том, что он назначает указанный номер строки следующей строке, а первый аргумент #line должен быть последовательностью цифр, чтобы вы не могли сделать что-то вроде

#line (__LINE__-1) "file.c"  // This is invalid

Проверка того, что имя файла в директиве #line соответствует фактическому имени файла, оставляется в качестве упражнения.

По крайней мере, для gcc, это также повлияет на имя файла, указанное в диагностических сообщениях.

Ответ 5

Чисто компилируйте решение времени здесь. Это основано на том, что sizeof() строкового литерала возвращает его длину + 1.

#define STRIPPATH(s)\
    (sizeof(s) > 2 && (s)[sizeof(s)-2] == '/' ? (s) + sizeof(s) - 1 : \
    sizeof(s) > 3 && (s)[sizeof(s)-3] == '/' ? (s) + sizeof(s) - 2 : \
    sizeof(s) > 4 && (s)[sizeof(s)-4] == '/' ? (s) + sizeof(s) - 3 : \
    sizeof(s) > 5 && (s)[sizeof(s)-5] == '/' ? (s) + sizeof(s) - 4 : \
    sizeof(s) > 6 && (s)[sizeof(s)-6] == '/' ? (s) + sizeof(s) - 5 : \
    sizeof(s) > 7 && (s)[sizeof(s)-7] == '/' ? (s) + sizeof(s) - 6 : \
    sizeof(s) > 8 && (s)[sizeof(s)-8] == '/' ? (s) + sizeof(s) - 7 : \
    sizeof(s) > 9 && (s)[sizeof(s)-9] == '/' ? (s) + sizeof(s) - 8 : \
    sizeof(s) > 10 && (s)[sizeof(s)-10] == '/' ? (s) + sizeof(s) - 9 : \
    sizeof(s) > 11 && (s)[sizeof(s)-11] == '/' ? (s) + sizeof(s) - 10 : (s))

#define __JUSTFILE__ STRIPPATH(__FILE__)

Не стесняйтесь расширять каскад условного оператора до максимального разумного имени файла в проекте. Длина пути не имеет значения, если вы проверяете достаточно далеко от конца строки.

Я посмотрю, смогу ли я получить аналогичный макрос без жестко закодированной длины с рекурсией макросов...

Ответ 6

Используйте функцию basename() или, если вы находитесь в Windows, _splitpath().

#include <libgen.h>

#define PRINTFILE() { char buf[] = __FILE__; printf("Filename:  %s\n", basename(buf)); }

Также попробуйте man 3 basename в оболочке.

Ответ 7

Нет времени для компиляции. Очевидно, вы можете сделать это во время выполнения с использованием среды выполнения C, как продемонстрировали некоторые из других ответов, но во время компиляции, когда препроцессор запускается, вам не повезло.

Ответ 8

в vs, когда с /FC, FILE равно полному пути, без /FC FILE равно имени файла. ref здесь

Ответ 9

Если вы используете CMAKE с компилятором GNU, этот параметр global отлично работает:

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__MY_FILE__='\"$(notdir $(abspath $<))\"'")

Ответ 10

Небольшое отклонение от того, что предложил @red1ynx, создало бы следующий макрос:

#define SET_THIS_FILE_NAME() \
    static const char* const THIS_FILE_NAME = \
        strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__;

В каждом из ваших файлов .c(pp) добавьте:

SET_THIS_FILE_NAME();

Затем вы можете обратиться к THIS_FILE_NAME вместо __FILE__:

printf("%s\n", THIS_FILE_NAME);

Это означает, что конструкция выполняется один раз за файл .c(pp), а не каждый раз, когда ссылается макрос.

Он ограничен только файлами .c(pp) и будет непригоден для файлов заголовков.

Ответ 11

Я сделал макрос __FILENAME__, который позволяет избежать полного перебора пути каждый раз. Проблема заключается в том, чтобы сохранить результирующее имя файла в локальной переменной cpp.

Это легко сделать, задав статическую глобальную переменную в файле .h. Это определение дает отдельные и независимые переменные в каждом файле .cpp, который включает .h. Чтобы быть многопоточным, стоит сделать переменную (и) также локальной (TLS).

Одна переменная хранит имя файла (сокращение). Другой имеет значение без выреза, которое дало __FILE__. Файл h:

static __declspec( thread ) const char* fileAndThreadLocal_strFilePath = NULL;
static __declspec( thread ) const char* fileAndThreadLocal_strFileName = NULL;

Сам макрос вызывает метод со всей логикой:

#define __FILENAME__ \
    GetSourceFileName(__FILE__, fileAndThreadLocal_strFilePath, fileAndThreadLocal_strFileName)

И функция реализована следующим образом:

const char* GetSourceFileName(const char* strFilePath, 
                              const char*& rstrFilePathHolder, 
                              const char*& rstrFileNameHolder)
{
    if(strFilePath != rstrFilePathHolder)
    {
        // 
        // This if works in 2 cases: 
        // - when first time called in the cpp (ordinary case) or
        // - when the macro __FILENAME__ is used in both h and cpp files 
        //   and so the method is consequentially called 
        //     once with strFilePath == "UserPath/HeaderFileThatUsesMyMACRO.h" and 
        //     once with strFilePath == "UserPath/CPPFileThatUsesMyMACRO.cpp"
        //
        rstrFileNameHolder = removePath(strFilePath);
        rstrFilePathHolder = strFilePath;
    }
    return rstrFileNameHolder;
}

RemovePath() может быть реализован по-разному, но быстрый и простой, по-видимому, связан с strrchr:

const char* removePath(const char* path)
{
    const char* pDelimeter = strrchr (path, '\\');
    if (pDelimeter)
        path = pDelimeter+1;

    pDelimeter = strrchr (path, '/');
    if (pDelimeter)
        path = pDelimeter+1;

    return path;
}

Ответ 12

Немного опоздал на вечеринку, но для GCC взглянем на -ffile-prefix-map=old=new:

При компиляции файлов, находящихся в старом каталоге, запишите любые ссылки на них в результате компиляции, как если бы файлы находились в новом каталоге. Указание этой опции эквивалентно указанию всех отдельных -f*-prefix-map. Это может использоваться для создания воспроизводимых сборок, которые не зависят от местоположения. Смотрите также -fmacro-prefix-map и -fdebug-prefix-map.

Поэтому для моих сборок Jenkins я добавлю -ffile-prefix-map=${WORKSPACE}/=/ и еще один, чтобы удалить локальный префикс установки пакета dev.

ПРИМЕЧАНИЕ. К сожалению, -ffile-prefix-map доступна только в GCC 8 и далее, как и -fmacro-prefix-map который, как мне кажется, выполняет часть __FILE__. Например, для GCC 5 у нас есть только -fdebug-prefix-map которая (кажется, не влияет) на __FILE__.

Ответ 13

  • С++ 11
  • msvc2015u3, gcc5.4, clang3.8.0

    template <size_t S>
    inline constexpr size_t get_file_name_offset(const char (& str)[S], size_t i = S - 1)
    {
        return (str[i] == '/' || str[i] == '\\') ? i + 1 : (i > 0 ? get_file_name_offset(str, i - 1) : 0);
    }
    
    template <size_t S>
    inline constexpr size_t get_file_name_offset(const wchar_t (& str)[S], size_t i = S - 1)
    {
        return (str[i] == L'/' || str[i] == L'\\') ? i + 1 : (i > 0 ? get_file_name_offset(str, i - 1) : 0);
    }
    
    template <size_t S>
    inline constexpr size_t get_file_name_offset(const char16_t (& str)[S], size_t i = S - 1)
    {
        return (str[i] == u'/' || str[i] == u'\\') ? i + 1 : (i > 0 ? get_file_name_offset(str, i - 1) : 0);
    }
    
    template <size_t S>
    inline constexpr size_t get_file_name_offset(const char32_t (& str)[S], size_t i = S - 1)
    {
        return (str[i] == U'/' || str[i] == U'\\') ? i + 1 : (i > 0 ? get_file_name_offset(str, i - 1) : 0);
    }
    
    template <typename T>
    inline constexpr size_t get_file_name_offset(T (& str)[1])
    {
        return 0;
    }
    

    '

    int main()
    {
         printf("%s\n", &__FILE__[get_file_name_offset(__FILE__)]);
    }
    

Код генерирует смещение времени компиляции, когда:

  • gcc: не менее gcc6.1 + -O1
  • msvc: поместить результат в переменную constexpr:

      constexpr auto file = &__FILE__[get_file_name_offset(__FILE__)];
      printf("%s\n", file);
    
  • clang: сохраняется при некомпилированной оценке времени

Есть хитрость, заставляющая все 3 компилятора выполнять оценку времени компиляции даже в конфигурации отладки с отключенной оптимизацией:

    namespace utility {

        template <typename T, T v>
        struct const_expr_value
        {
            static constexpr const T value = v;
        };

    }

    #define UTILITY_CONST_EXPR_VALUE(exp) ::utility::const_expr_value<decltype(exp), exp>::value

    int main()
    {
         printf("%s\n", &__FILE__[UTILITY_CONST_EXPR_VALUE(get_file_name_offset(__FILE__))]);
    }

https://godbolt.org/z/P1y3ZI

Ответ 14

просто надеюсь немного улучшить макрос FILE:

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

это улавливает/и \, как запросил Ч.К.К., и это отлично работает в моей смешанной среде.

Ответ 15

Try

#pragma push_macro("__FILE__")
#define __FILE__ "foobar.c"

после операторов include в исходном файле и добавьте

#pragma pop_macro("__FILE__")

в конце исходного файла.

Ответ 16

Вот портативная функция, которая работает как для Linux (путь "/" ), так и для Windows (сочетание "\" и "/" ).
Компилирует с gcc, clang и vs.

#include <string.h>
#include <stdio.h>

const char* GetFileName(const char *path)
{
    const char *name = NULL, *tmp = NULL;
    if (path && *path) {
        name = strrchr(path, '/');
        tmp = strrchr(path, '\\');
        if (tmp) {
             return name && name > tmp ? name + 1 : tmp + 1;
        }
    }
    return name ? name + 1 : path;
}

int main() {
    const char *name = NULL, *path = NULL;

    path = __FILE__;
    name = GetFileName(path);
    printf("path: %s, filename: %s\n", path, name);

    path ="/tmp/device.log";
    name = GetFileName(path);
    printf("path: %s, filename: %s\n", path, name);

    path = "C:\\Downloads\\crisis.avi";
    name = GetFileName(path);
    printf("path: %s, filename: %s\n", path, name);

    path = "C:\\Downloads/nda.pdf";
    name = GetFileName(path);
    printf("path: %s, filename: %s\n", path, name);

    path = "C:/Downloads\\word.doc";
    name = GetFileName(path);
    printf("path: %s, filename: %s\n", path, name);

    path = NULL;
    name = GetFileName(NULL);
    printf("path: %s, filename: %s\n", path, name);

    path = "";
    name = GetFileName("");
    printf("path: %s, filename: %s\n", path, name);

    return 0;
}

Стандартный вывод:

path: test.c, filename: test.c
path: /tmp/device.log, filename: device.log
path: C:\Downloads\crisis.avi, filename: crisis.avi
path: C:\Downloads/nda.pdf, filename: nda.pdf
path: C:/Downloads\word.doc, filename: word.doc
path: (null), filename: (null)
path: , filename: 

Ответ 17

Если вы оказались на этой странице в поисках способа удалить абсолютный исходный путь, указывающий на некрасивое расположение сборки из бинарного файла, который вы отправляете, приведенный ниже может удовлетворить ваши потребности.

Хотя это не дает точного ответа, о котором автор выразил свое желание, так как предполагает использование CMake, он довольно близок. Жаль, что никто не упоминал об этом раньше, так как это сэкономило бы мне кучу времени.

OPTION(CMAKE_USE_RELATIVE_PATHS "If true, cmake will use relative paths" ON)

Установка вышеуказанной переменной в ON приведет к генерации команды сборки в формате:

cd /ugly/absolute/path/to/project/build/src && 
    gcc <.. other flags ..> -c ../../src/path/to/source.c

В результате __FILE__ макрос будет решать ../../src/path/to/source.c

CMake документация

Остерегайтесь предупреждения на странице документации, хотя:

Используйте относительные пути (может не работать!).

Не гарантируется работа во всех случаях, но работал в моем - CMake 3.13 + gcc 4.5

Ответ 18

Поскольку вы используете GCC, вы можете воспользоваться

__BASE_FILE__ Этот макрос расширяется до имени основного входного файла в форме константы строки C. Это исходный файл, который был указанный в командной строке препроцессора или компилятора C

а затем контролировать, как вы хотите отобразить имя файла, изменив представление исходного файла (полный путь/относительный путь/базовое имя) во время компиляции.

Ответ 19

Здесь решение, которое работает для сред, не имеющих библиотеки строк (ядро Linux, встроенные системы и т.д.):

#define FILENAME ({ \
    const char* filename_start = __FILE__; \
    const char* filename = filename_start; \
    while(*filename != '\0') \
        filename++; \
    while((filename != filename_start) && (*(filename - 1) != '/')) \
        filename--; \
    filename; })

Теперь просто используйте FILENAME вместо __FILENAME__. Да, это все еще работа, но она работает.

Ответ 20

Вот решение, которое использует вычисление времени компиляции:

constexpr auto* getFileName(const char* const path)
{
    const auto* startPosition = path;
    for (const auto* currentCharacter = path;*currentCharacter != '\0'; ++currentCharacter)
    {
        if (*currentCharacter == '\\' || *currentCharacter == '/')
        {
            startPosition = currentCharacter;
        }
    }

    if (startPosition != path)
    {
        ++startPosition;
    }

    return startPosition;
}

std::cout << getFileName(__FILE__);

Ответ 21

#include <algorithm>
#include <string>
using namespace std;
string f( __FILE__ );
f = string( (find(f.rbegin(), f.rend(), '/')+1).base() + 1, f.end() );

// searches for the '/' from the back, transfers the reverse iterator 
// into a forward iterator and constructs a new sting with both

Ответ 22

Я использую одно и то же решение с ответом @Patrick в течение многих лет.

Есть небольшая проблема, когда полный путь содержит символьную ссылку.

Лучшее решение.

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-builtin-macro-redefined -D'__FILE__=\"$(subst $(realpath ${CMAKE_SOURCE_DIR})/,,$(abspath $<))\"'")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-builtin-macro-redefined -D'__FILE__=\"$(subst $(realpath ${CMAKE_SOURCE_DIR})/,,$(abspath $<))\"'")

Зачем использовать это?

  • -Wno-builtin-macro-redefined для отключения предупреждений компилятора о переопределении макроса __FILE__.

    Для тех компиляторов, которые не поддерживают это, обратитесь к Robust способ ниже.

  • Уберите путь проекта из пути к файлу - это ваше реальное требование. Вам не понравится тратить время, чтобы узнать, где находится файл header.h, src/foo/header.h или src/bar/header.h.

  • Мы должны удалить макрос __FILE__ в конфигурационном файле cmake.

    Этот макрос используется в большинстве существующих кодов. Просто переопределите это может освободить вас.

    Компиляторы, такие как gcc предопределяют этот макрос из аргументов командной строки. И полный путь записывается в makefile сгенерированный cmake.

  • CMAKE_*_FLAGS жесткий код в CMAKE_*_FLAGS.

    Есть некоторые команды для добавления опций или определений компилятора в более add_definitions() версии, например add_definitions() и add_compile_definitions(). Эти команды будут анализировать функции make, такие как subst перед их применением к исходным файлам. Этого мы не хотим.

Надежный способ для -Wno-builtin-macro-redefined.

include(CheckCCompilerFlag)
check_c_compiler_flag(-Wno-builtin-macro-redefined SUPPORT_C_WNO_BUILTIN_MACRO_REDEFINED)
if (SUPPORT_C_WNO_BUILTIN_MACRO_REDEFINED)
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-builtin-macro-redefined")
endif (SUPPORT_C_WNO_BUILTIN_MACRO_REDEFINED)
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag(-Wno-builtin-macro-redefined SUPPORT_CXX_WNO_BUILTIN_MACRO_REDEFINED)
if (SUPPORT_CXX_WNO_BUILTIN_MACRO_REDEFINED)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-builtin-macro-redefined")
endif (SUPPORT_CXX_WNO_BUILTIN_MACRO_REDEFINED)

Не забудьте удалить эту опцию компилятора из строки set(*_FLAGS... -D__FILE__=...).