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


164

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

/full/path/to/file.c

я вижу

to/file.c

или

file.c

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

9
Поскольку вы используете gcc, я думаю, вы можете изменить __FILE__содержимое, изменив имя файла, которое вы передаете в командной строке. Так что вместо того gcc /full/path/to/file.c, чтобы попробовать cd /full/path/to; gcc file.c; cd -;. Конечно, это немного больше, чем если вы полагаетесь на текущий каталог gcc для пути включения или расположения выходного файла. Редактировать: документы gcc предполагают, что это полный путь, а не аргумент имени входного файла, но это не то, что я вижу для gcc 4.5.3 на Cygwin. Так что вы можете попробовать это на Linux и посмотреть.
Стив Джессоп

4
GCC 4.5.1 (специально для arm-none-eabi) использует точный текст имени файла в командной строке. В моем случае это была ошибка среды IDE, вызвавшая GCC со всеми полными именами файлов, вместо того, чтобы помещать текущий каталог где-нибудь разумным (местоположение файла проекта, возможно?) Или настраиваемым и используя относительные пути оттуда. Я подозреваю, что многие IDE делают это (особенно в Windows) из-за некоторого дискомфорта, связанного с объяснением, где на самом деле находится «текущий» каталог для приложения с графическим интерфейсом.
RBerteig

3
@SteveJessop - надеюсь, что вы прочитали этот комментарий. У меня есть ситуация, когда я вижу __FILE__напечатанный как, ../../../../../../../../rtems/c/src/lib/libbsp/sparc/leon2/../../shared/bootcard.cи я хочу знать, где gcc скомпилировал файл так, что этот файл расположен относительно, как показано.
Чан Ким

1
Этот вопрос не является дураком связанного. Во-первых, связанный относится к C ++, и ответы, следовательно, углубляются в C ++ macro esoterica. Во-вторых, в вопросе OP нет ничего, что требовало бы макрорешения. Это только торжественно указывает на проблему и задает открытый вопрос.
Профессор Фалькен,

Ответы:


167

Пытаться

#include <string.h>

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

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


13
/является допустимым разделителем пути в Windows.
Ганс Пассант

11
/является допустимым разделителем пути в именах файлов, передаваемых в CreateFile () и т. д. Однако это не всегда означает, что вы можете использовать его /где угодно в Windows, поскольку существует традиция (унаследованная от CPM) использовать /в качестве аргумента в командной строке. Но качественный инструмент был бы осторожен с именами файлов раскол в обоих слэш и обратной косой черты , чтобы избежать каких - либо проблем для людей , которые умудряются использовать /.
RBerteig

5
@AmigableClarkKant, нет, вы можете смешивать оба разделителя в одном и том же имени файла.
RBerteig

2
Если ваша платформа поддерживает это, char* fileName = basename(__FILE__); это определенно есть в Linux и OS X, но не знаю о Windows.
JeremyP

14
Это может быть сокращено до strrchr("/" __FILE__, '/') + 1. __FILE__Заранее добавив "/" к , strrchr гарантированно найдет что-то, и, следовательно, условный?: Больше не нужен.
uroeuroburɳ

54

Вот совет, если вы используете 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__.


11
Боюсь, тогда это не работает для ФАЙЛА, указанного в заголовочном файле.
Байян Хуан,

2
Согласен с @BaiyanHuang, но не уверен, что комментарий понятен. __FILE__не является простым символом препроцессора, это изменение текущего файла, часто используется для выдачи имени текущего файла (заголовок или исходный модуль). Это __FILENAME__будет иметь только самый внешний источник
nhed

3
Решение этого ответа не является переносимым, поскольку оно использует экранирование оболочки Bourne. Лучше использовать CMake, чтобы реализовать его чистым и переносимым способом. Видеть здесь макро-ответ define_file_basename_for_sources .
Колин Д. Беннетт

3
Вариант GNU Make: CFLAGS + = -D__FILENAME __ = \ "$ (notdir $ <) \"
ctuffli,

4
@firegurafiku На встроенных платформах размер кода часто является скорее ограничением, чем скоростью. Если в каждом файле есть операторы отладки, это может быстро увеличить размер файла со строками полного пути. В данный момент я имею дело с такой платформой, и ссылки __FILE__везде выдуваются из пространства кода.
mrtumnus

26

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

Идея состоит в том, чтобы получить размер исходного каталога с помощью инструмента сборки и просто добавить его в __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 и clang, и они работают. Я также экспериментирую с простым добавлением различных буквенных числовых значений, и это ведет себя так, как я ожидал. Затем меня наконец осенило. __FILE__указатель на массив байтов Таким образом, добавление числового литерала является просто добавлением указателя. Очень умный @RenatoUtsch
Даниэль

Это работает, только если исходный файл находится в каталоге cmake list. Если исходный файл находится за пределами, он сломается, возможно, с доступом вне литеральной строки. Так что будьте осторожны с этим.
Андри

Это на самом деле не уменьшает размер кода. Я предполагаю, что весь путь все еще скомпилирован в двоичный файл, только указатель изменен.
Тарион

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

@RenatoUtsch В проекте, над которым я работаю, есть изменение, которое просто указывает имя файла, но имеет недостаток, заключающийся в том, что имя C-файла также указывается в заголовке. Изменение было сделано для того, чтобы получить воспроизводимые сборки. Так что с gcc с -O2 будет ли строка действительно оптимизирована и сборка сделана воспроизводимой?
Пол Стелиан

19

Чисто скомпилируйте временное решение здесь. Он основан на том факте, что 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__)

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

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


3
Классный ответ. Вы должны использовать по крайней мере, -O1чтобы использовать это во время компиляции.
Цифровая травма

1
Разве это не задом наперед? Вы хотите найти последнее вхождение '/', что означает, что вы должны начать с sizeof(s) > 2проверки в первую очередь. Кроме того, это не сработало во время компиляции для меня, в -Os. Строки полного пути присутствовали в выходном двоичном файле.
mrtumnus

15

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


1
Кит Томпсон, это отличное решение, спасибо. Единственная проблема состоит в том, что кажется, что этот макрос сокращает __LINE__значение на единицу. Так __LINE__написано в строке xжест оценивается в x-1. По крайней мере, с gcc 5.4.
elklepo

1
@Klepak: Вы правы, и это стандартное поведение. Директива «заставляет реализацию вести себя так, как если бы следующая последовательность строк источника начиналась с строки источника, которая имеет номер строки, указанный в последовательности цифр». И это должна быть последовательность цифр , чтобы вы не могли ее использовать #line __LINE__-1, "file.c". Я обновлю свой ответ.
Кит Томпсон

1
Как это будет для других компиляторов, таких как Clang, MSVC или Intel C Compiler?
Франклин Ю.

8

Немного опоздал на вечеринку, но для 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__.


1
Вариант -ffile-prefix-mapдействительно подразумевает как -fdebug-prefix-mapи -fmacro-prefix-mapварианты. См. Также ссылки на reproducible-builds.org/docs/build-path . Ошибка GCC, которая отслеживает -fmacro-prefix-mapи -ffile-prefix-mapявляется gcc.gnu.org/bugzilla/show_bug.cgi?id=70268
Лекенштейн

6
  • C ++ 11
  • msvc2015u3, gcc5.4, clang3.8.0

    template <typename T, size_t S>
    inline constexpr size_t get_file_name_offset(const T (& 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 <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/u6s8j3


Человек, это красиво, и это работает, спасибо! Не знаю, почему этот ответ так недооценен.
Роман

5

В VC, при использовании /FC, __FILE__расширяется до полного пути, без /FCопции __FILE__расширения имени файла. ссылка: здесь


4

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


1
strrchrответ мог правдоподобно быть вычислено во время компиляции, хотя, конечно , до сих пор не препроцессором. Я не знаю, действительно ли это делает gcc, я не проверял, но я уверен, что он вычисляет strlenстроковые литералы во время компиляции.
Стив Джессоп

@ Steve - может быть, но это большая зависимость от конкретного поведения компилятора.
Шон

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

5
Возможно, это не критично для производительности, но это легко можно рассматривать как критичное для конфиденциальности. Нет никаких веских причин для раскрытия моей организационной практики для каждого клиента в строках, помещенных в выпущенный EXE-файл. Хуже того, для приложений, созданных от имени клиента, эти строки могут показывать вещи, которые мой клиент может предпочесть не делать, например не быть автором своего собственного продукта. Поскольку __FILE__неявно вызывается assert(), эта утечка может произойти без каких-либо других явных действий.
RBerteig

@RBerteig само по __FILE__себе базовое имя может также указывать на то, что клиент может предпочесть не делать, поэтому при __FILE__любом его использовании - будь то полный абсолютный путь или просто базовое имя - возникают те же проблемы, на которые вы указали. В этой ситуации все результаты должны быть тщательно изучены, и для вывода клиентам необходимо ввести специальный API. Остальные выходные данные должны быть выгружены в / dev / NULL или stdout, а stderr должен быть закрыт. :-)
tchen

4

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

#include <libgen.h>

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

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


2
@mahmood: char file_copy[] = __FILE__; const char *filename = basename(__FILE__);. Причина для копирования заключается в том, что basename может изменить входную строку. Вы также должны следить за тем, чтобы указатель результата был действителен только до basenameповторного вызова. Это означает, что это не потокобезопасно.
Стив Джессоп

@ SteveJessop, ах, я забыл. Правда.
Профессор Фалькен

1
@Amigable: честно говоря, я подозреваю, что basenameна самом деле не изменит входную строку, которая получается из-за того __FILE__, что у входной строки нет /конца в конце, и поэтому нет необходимости изменять . Так что вам это может сойти с рук, но я думаю, что в первый раз, когда кто-то увидит basename, он должен увидеть это со всеми ограничениями.
Стив Джессоп

@SteveJessop В справочной странице BSd для basename () упоминается, что устаревшая версия basename () принимает const char * и не изменяет строку. В справочной странице linux ничего не говорится о const, но упоминается, что она может вернуть часть строки аргумента. Так что лучше всего быть консервативным в отношении basename ().
Профессор Фалькен

@SteveJessop, хе-хе, я только сейчас, внимательно изучив ваш комментарий, через четыре года осознаю, что /в конце строки означает, что у basename может быть веская причина изменить свой аргумент.
Профессор Фалькен,

4

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

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

4

У меня есть использовать один и тот же раствор с @Patrick «s ответа в течение многих лет.

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

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

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Требуется .

    Есть некоторые команды для добавления опций или определений компилятора в более позднюю версию, например, 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__=...)строки.


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

Вы можете опубликовать код? Распространенным случаем является установка переменных в локальной области и использование их в другой.
Levi.G

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

да, он предназначен для этого, для наиболее распространенного использования #define LOG(fmt, args...) printf("%s " fmt, __FILE__, ##args). при использовании LOG()макроса вы не очень хотите видеть log.hв сообщениях. в конце концов, __FILE__макрос раскрывается в каждом файле C / Cpp (модуле компиляции) вместо включаемых файлов.
Levi.G

3

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


3

Я сделал макрос __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;
}


2

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

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

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


6
Определение названного макроса FILE- очень плохая идея, если вы включите его <stdio.h>.
Кит Томпсон

хорошо знать. я просто хотел показать Czarek мое решение \\ /, поэтому меня не беспокоят схемы именования.
Александр Голкс

2

Пытаться

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

после включения операторов в ваш исходный файл и добавить

#pragma pop_macro("__FILE__")

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


2
push_macroи pop_macroнестандартны. (gcc поддерживает их для совместимости с компиляторами Microsoft Windows.) В любом случае нет смысла толкать и вставлять определение __FILE__; восстановленное значение не будет использовано после окончания исходного файла в любом случае. Уборщик способ изменить значение __FILE__является#line __LINE__ "foobar.c"
Кит Томпсон

1
И это вызывает внутреннюю ошибку в препроцессоре gcc. gcc.gnu.org/bugzilla/show_bug.cgi?id=69665
Кит Томпсон

1

Вот переносимая функция, которая работает как для 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: 

1

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

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__);

1

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

Хотя это не дает точного ответа, о котором автор выразил свое желание, так как предполагает использование 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


В документации CMake 3.4+ говорится, что «эта переменная не имеет никакого эффекта. Частично реализованный эффект, который она имела в предыдущих выпусках, был удален в CMake 3.4». Если у вас работает 3.13, есть еще одна причина.
mike.dld

0

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

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

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


6
не имеет значения. Я использовал __FILE__и __BASE_FILE__однако они оба показывают полный путь к файлу
mahmood

как вы вызываете компилятор?
Зиу

2
Тогда держу пари, что SCONS вызывает gcc вот так gcc /absolute/path/to/file.c. Если вы найдете способ изменить это поведение (открыв еще один вопрос о SO, смеется?), Вам не нужно изменять строку во время выполнения
ziu

17
Этот ответ на 100% неверен. __BASE_FILE__(как говорят документы, хотя и неясно) создает имя файла, указанного в командной строке , например, test.cили в /tmp/test.cзависимости от того, как вы вызывали компилятор. Это точно так же, как __FILE__, за исключением случаев, когда вы находитесь внутри файла заголовка, в этом случае __FILE__выдает имя текущего файла (например foo.h), тогда как __BASE_FILE__продолжает создавать test.c.
Quuxplusone

0

Вот решение, которое работает для сред, в которых нет строковой библиотеки (ядро 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__. Да, это все еще во время выполнения, но это работает.


Возможно, вы захотите проверить и «/», и «\», в зависимости от платформы.
Технофил

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

0

Краткий рабочий ответ для Windows и * nix:

#define __FILENAME__ std::max<const char*>(__FILE__,\
    std::max(strrchr(__FILE__, '\\')+1, strrchr(__FILE__, '/')+1))

Зачем тебе нужно, std::max<const char*>а не просто std::max?
Chqrlie
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.