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

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

/full/path/to/file.c

Я вижу

to/file.c

или

file.c

17 ответов


попробовать

#include <string.h>

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

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


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

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

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

затем использовать __FILENAME__ на месте __FILE__.


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

идея состоит в том, чтобы получить размер исходного каталога с помощью инструмента сборки и просто добавить его в __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__ string препроцессор позаботится о том, чтобы указать правильное имя файла, и все это будет относительно корня вашего проекта CMake.

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

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


чисто решение времени компиляции здесь. Это основано на том, что 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__)

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

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


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

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

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

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

затем __FILE__ будет расширяться, чтобы просто "file.c".

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

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

альтернативой является использование


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


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

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

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


использовать basename ()


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

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

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

SET_THIS_FILE_NAME();

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

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

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

он ограничен для использования только от .C (pp) файлы и будут непригодны для использования из заголовочных файлов.


Я сделал макрос __FILENAME__ это избегает резать полный путь каждый раз. Проблема удержать полученный файл в ЧГК-локальная переменная.

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

один переменная хранит имя файла (сжат). Другой содержит значение non-cut, которое __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;
}

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

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

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


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

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

попробовать

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

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

#pragma pop_macro("__FILE__")

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


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


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

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

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


вот портативная функция, которая работает как для Linux (путь '/'), так и для Windows (сочетание " \ "и"/").
компилируется с GCC, Clang и против

#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: 

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

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

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

    return startPosition;
}

std::cout << getFileName(__FILE__);