Стандартный предопределенный MACRO __FILE__, доступный на C, показывает полный путь к файлу. Есть ли способ сократить путь? Я имею в виду вместо
/full/path/to/file.c
Я вижу
to/file.c
или
file.c
Стандартный предопределенный MACRO __FILE__, доступный на C, показывает полный путь к файлу. Есть ли способ сократить путь? Я имею в виду вместо
/full/path/to/file.c
Я вижу
to/file.c
или
file.c
Try
#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, я не вижу причин, по которым вы не могли бы распространять это на свои собственные make файлы. Например, у вас может быть такая строка:
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__
, препроцессор позаботится о том, чтобы указать правильное имя файла, и все это будет относиться к корню вашего проекта CMake.
Если вы заботитесь о производительности, это так же эффективно, как и использование __FILE__
, потому что как __FILE__
, так и SOURCE_PATH_SIZE
являются известными константами времени компиляции, поэтому его можно оптимизировать компилятором.
Единственное место, где это может быть неудачно, - это использовать это в сгенерированных файлах, и они находятся в папке с исходным кодом. Тогда вам, вероятно, придется создать другой макрос, используя переменную CMAKE_BUILD_DIR
вместо CMAKE_SOURCE_DIR
.
По крайней мере для 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, это также повлияет на имя файла, указанное в диагностических сообщениях.
Чисто компилируйте решение времени здесь. Это основано на том, что 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__)
Не стесняйтесь расширять каскад условного оператора до максимального разумного имени файла в проекте. Длина пути не имеет значения, если вы проверяете достаточно далеко от конца строки.
Я посмотрю, смогу ли я получить аналогичный макрос без жестко закодированной длины с рекурсией макросов...
Используйте функцию basename() или, если вы находитесь в Windows, _splitpath().
#include <libgen.h>
#define PRINTFILE() { char buf[] = __FILE__; printf("Filename: %s\n", basename(buf)); }
Также попробуйте man 3 basename
в оболочке.
Нет времени для компиляции. Очевидно, вы можете сделать это во время выполнения с использованием среды выполнения C, как продемонстрировали некоторые из других ответов, но во время компиляции, когда препроцессор запускается, вам не повезло.
в vs, когда с /FC, FILE равно полному пути, без /FC FILE равно имени файла. ref здесь
Если вы используете CMAKE
с компилятором GNU, этот параметр global
отлично работает:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__MY_FILE__='\"$(notdir $(abspath $<))\"'")
Небольшое отклонение от того, что предложил @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) и будет непригоден для файлов заголовков.
Я сделал макрос __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;
}
Немного опоздал на вечеринку, но для 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__
.
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__))]);
}
просто надеюсь немного улучшить макрос FILE:
#define FILE (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__)
это улавливает/и \, как запросил Ч.К.К., и это отлично работает в моей смешанной среде.
Try
#pragma push_macro("__FILE__")
#define __FILE__ "foobar.c"
после операторов include в исходном файле и добавьте
#pragma pop_macro("__FILE__")
в конце исходного файла.
Вот портативная функция, которая работает как для 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:
Если вы оказались на этой странице в поисках способа удалить абсолютный исходный путь, указывающий на некрасивое расположение сборки из бинарного файла, который вы отправляете, приведенный ниже может удовлетворить ваши потребности.
Хотя это не дает точного ответа, о котором автор выразил свое желание, так как предполагает использование 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 3.13 + gcc 4.5
Поскольку вы используете GCC, вы можете воспользоваться
__BASE_FILE__
Этот макрос расширяется до имени основного входного файла в форме константы строки C. Это исходный файл, который был указанный в командной строке препроцессора или компилятора C
а затем контролировать, как вы хотите отобразить имя файла, изменив представление исходного файла (полный путь/относительный путь/базовое имя) во время компиляции.
Здесь решение, которое работает для сред, не имеющих библиотеки строк (ядро 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__
. Да, это все еще работа, но она работает.
Вот решение, которое использует вычисление времени компиляции:
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__);
#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
Я использую одно и то же решение с ответом @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__=...)
.