__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) через якийсь дискомфорт, пов’язаний з поясненням, де "поточний" каталог дійсно для GUI-програми.
RBerteig

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

1
Це питання не є дурою пов'язаного. Для одного, пов'язаний один стосується C ++, і відповіді, отже, поглинаються в C ++ макроезотерику. По-друге, у запитанні ОП немає нічого, що передбачає макророзв'язок. Він лише урочисто вказує на проблему і задає відкрите запитання.
Проф. Фолкен

Відповіді:


166

Спробуйте

#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 гарантовано знайде щось, і, отже, умовне?: Більше не потрібне.
ɲeuroburɳ

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, я не бачу причин, щоб ви не могли поширити це на свої власні файли. Наприклад, у вас може бути такий рядок:

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

де $(SOURCE_PREFIX)є префікс, який потрібно видалити.

Потім використовуйте __FILENAME__замість __FILE__.


11
Я боюся, що це не працює для ФАЙЛу, на який посилається у файлі заголовка.
Baiyan Huang

2
Погодьтеся з @BaiyanHuang, але не впевнений, що коментар зрозумілий. __FILE__не є простим символом препроцесора, він змінюється на поточний файл, часто використовується для видачі імені поточного файлу (заголовок або вихідний модуль). Це __FILENAME__буде мати тільки самий зовнішній джерело
nhed

3
Рішення цієї відповіді не є портативним, оскільки він використовує втечі оболонки Bourne. Краще використовувати CMake, щоб реалізувати його чистим та портативним способом. Дивіться макровідповідь defini_file_basename_for_sources тут.
Колін Д Беннетт

3
GNU Зробити варіант цього CFLAGS + = -D__FILENAME __ = \ "$ (notdir $ <) \"
ctuffli

4
@firegurafiku На вбудованих платформах розмір коду часто швидше обмежує швидкість. Якщо у кожному файлі є заяви про налагодження, вони можуть швидко збільшити розмір файлу з надісланими рядками. Я зараз маю справу з такою платформою, і посилання на __FILE__всюди видуває з кодового простору.
мертвенус

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

Якщо ви дбаєте про продуктивність, це настільки ж ефективно, як і використання __FILE__, оскільки обидва __FILE__і SOURCE_PATH_SIZEвідомі константи часу компіляції, тому компілятор може бути оптимізований.

Не вдалося це зробити, якщо ви використовуєте це для створених файлів, і вони знаходяться в папці збірки без вихідного коду. Тоді вам, мабуть, доведеться створити інший макрос, використовуючи CMAKE_BUILD_DIRзмінну замість CMAKE_SOURCE_DIR.


4
Я спочатку цього не розумів. Я зашифровував приклади і зіставив їх проти gcc та clang, і вони працюють. Я також експериментую з просто додаванням різних буквальних числових значень, і це поводиться так, як я очікував. Тоді воно нарешті зійшло на мені. __FILE__- вказівник на масив байтів. Тож додавання числового літералу є лише додаванням покажчика. Дуже розумний @RenatoUtsch
Даніель

Він працює лише в тому випадку, якщо вихідний файл знаходиться в каталозі списку cmake. Якщо вихідний файл знаходиться поза, то він порушиться, можливо, з доступом поза буквальним рядком. Тож будьте обережні з цим.
Андрі

Це фактично не зменшує розмір коду. Я думаю, весь шлях все ще збирається у двійковий, просто вказівник модифікований.
Таріон

І __FILE__макрос, і макроси SOURCE_PATH_SIZE є константами, відомими під час компіляції. Я очікую, що сучасні оптимізуючі компілятори зможуть виявити, що частина рядка не використовується, і просто видалити її з двійкового. У всякому разі, я не думаю, що ці декілька байтів мали б істотну різницю у двійковому розмірі, тому я б не переймався цим.
RenatoUtsch

@RenatoUtsch У проекті, над яким я працюю, є зміна, яка просто вказує ім'я файлу, але має недолік, якщо також вказати ім'я файлу C в заголовок. Зміни були внесені з метою отримання відтворюваних складових. Так що з gcc з -O2, чи дійсно буде оптимізовано рядок, і збірка зробиться відтворюваною?
Пол

18

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

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".

Це може бути, а може і не бути практичним.

Стандарт С не вимагає такої поведінки. Все, про що йдеться, __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}/=/ще й ще один для видалення локального пакета розробників.

ПРИМІТКА На жаль, цей -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
Lekensteyn

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__розширення імені файлу. ref: тут


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буде викликаний знову. Це означає, що це не безпечно для потоків.
Стів Джессоп

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

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

@SteveJessop Сторінка Man BSd для basename () згадує, що застаріла версія basename () має const char * і не змінює рядок. Сторінка man-linux нічого не згадує про const, але згадує, що вона може повернути частину аргументу. Тож, краще будьте консервативно мати справу з базовим іменем ().
Проф. Фолкен

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

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__макросу.

    Для тих компіляторів це не підтримує, див. Надійний спосіб нижче.

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

  • Нам слід зняти __FILE__макросcmake конфігураційному файлі.

    Цей макрос використовується в більшості існуючих кодів. Просто перегляньте, що може звільнити вас.

    Компілятори, як і gccзаздалегідь, визначають цей макрос з аргументів командного рядка. І повний шлях записаний у makefiles, породжений 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 (компілюючий блок) замість включених файлів.
Леві.Г

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

це ловить / та \, як просив Czarek Tomczak, і це чудово працює в моєму змішаному середовищі.


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

як викликати компілятор?
ziu

2
Тоді я думаю, що SCONS називає gcc так gcc /absolute/path/to/file.c. Якщо ви знайдете спосіб змінити цю поведінку (відкриєте інше запитання щодо SO, lol?), Вам не потрібно змінювати рядок під час виконання
ziu

17
Ця відповідь на 100% помилкова. __BASE_FILE__(як кажуть документи, хоча і незрозуміло) створює ім'я файлу, вказаного в командному рядку , наприклад, test.cабо /tmp/test.cзалежно від того, як ви викликали компілятор. Це точно те саме, що __FILE__, за винятком випадків, коли ви знаходитесь у файлі заголовка, у цьому випадку видається __FILE__ім'я поточного файлу (наприклад foo.h), тоді як __BASE_FILE__продовжує видаватися test.c.
Квомплусон

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.