Makro __FILE__ pokazuje pełną ścieżkę

164

Standardowe predefiniowane makro __FILE__dostępne w języku C pokazuje pełną ścieżkę do pliku. Czy jest jakiś sposób na skrócenie ścieżki? Mam na myśli zamiast

/full/path/to/file.c

Widzę

to/file.c

lub

file.c
mahmood
źródło
18
Byłoby naprawdę wspaniale znaleźć rozwiązanie tylko dla preprocesora. Obawiam się, że sugestie oparte na operacjach na łańcuchach zostaną wykonane w czasie wykonywania.
cdleonard
9
Ponieważ używasz gcc, myślę, że możesz zmienić to, co __FILE__zawiera, zmieniając nazwę pliku, którą podajesz w linii poleceń. Więc zamiast tego gcc /full/path/to/file.cspróbuj cd /full/path/to; gcc file.c; cd -;. Oczywiście jest to trochę więcej, jeśli polegasz na bieżącym katalogu gcc jako ścieżki dołączania lub lokalizacji pliku wyjściowego. Edycja: dokumentacja gcc sugeruje, że jest to pełna ścieżka, a nie argument nazwy pliku wejściowego, ale to nie jest to, co widzę dla gcc 4.5.3 na Cygwin. Więc równie dobrze możesz wypróbować go w systemie Linux i zobaczyć.
Steve Jessop,
4
GCC 4.5.1 (zbudowany specjalnie dla arm-none-eabi) używa dokładnego tekstu nazwy pliku w swoim wierszu poleceń. W moim przypadku była to wina IDE, która wywołała GCC ze wszystkimi nazwami plików w pełni kwalifikowanymi, zamiast umieszczania bieżącego katalogu w rozsądnym miejscu (być może lokalizacja pliku projektu?) Lub konfigurowalnym i używając ścieżek względnych z tego miejsca. Podejrzewam, że wiele IDE robi to (szczególnie w systemie Windows) z powodu pewnego rodzaju niewygody związanej z wyjaśnianiem, gdzie naprawdę znajduje się „bieżący” katalog aplikacji GUI.
RBerteig
3
@SteveJessop - mam nadzieję, że przeczytałeś ten komentarz. Mam sytuację, w której widzę __FILE__wydrukowany jako ../../../../../../../../rtems/c/src/lib/libbsp/sparc/leon2/../../shared/bootcard.ci chcę wiedzieć, gdzie gcc skompilował plik w taki sposób, że ten plik jest względnie zlokalizowany, tak jak pokazano.
Chan Kim,
1
To pytanie nie jest naśladownictwem pytania połączonego. Po pierwsze, link dotyczy C ++, a odpowiedzi konsekwentnie zagłębiają się w ezoterykę makr C ++. Po drugie, w pytaniu OP nie ma niczego, co wymagałoby rozwiązania makro. Tylko uroczyście wskazuje na problem i zadaje otwarte pytanie.
Prof. Falken

Odpowiedzi:

166

Próbować

#include <string.h>

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

W systemie Windows użyj „\\” zamiast „/”.

red1ynx
źródło
13
/jest prawidłowym separatorem ścieżek w systemie Windows.
Hans Passant
11
/jest prawidłowym separatorem ścieżki w nazwach plików przekazywanych do funkcji CreateFile () i tak dalej. Jednak nie zawsze oznacza to, że można używać /dowolnego miejsca w systemie Windows, ponieważ istnieje tradycja (odziedziczona z CPM) używania /jako argumentu prowadzącego w wierszu polecenia. Ale narzędziem jakość byłby ostrożny w nazwach plików podziału na obu slash i znaki backslash, aby uniknąć wszelkich problemów dla ludzi, że uda się wykorzystać /.
RBerteig
5
@AmigableClarkKant, nie, możesz łączyć oba separatory w tej samej nazwie pliku.
RBerteig
2
Jeśli Twoja platforma to obsługuje, char* fileName = basename(__FILE__); na pewno jest dostępna w systemie Linux i OS X, ale nie wiem o systemie Windows.
JeremyP,
14
Można to skrócić do strrchr("/" __FILE__, '/') + 1. Poprzez poprzedzanie „/” na __FILE__, strrchr ma pewność, że coś znajdzie, a zatem warunek?: Nie jest już potrzebny.
ɲeuroburɳ
54

Oto wskazówka, jeśli używasz cmake. Z: http://public.kitware.com/pipermail/cmake/2013-J January 053117.html

Kopiuję wskazówkę, więc wszystko jest na tej stronie:

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

Jeśli używasz make GNU, nie widzę powodu, dla którego nie mógłbyś rozszerzyć tego na własne pliki makefile. Na przykład możesz mieć taką linię:

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

gdzie $(SOURCE_PREFIX)jest prefiks, który chcesz usunąć.

Następnie użyj __FILENAME__zamiast __FILE__.

Patrick
źródło
11
Obawiam się, że to nie zadziała dla PLIKU, do którego odwołuje się plik nagłówkowy.
Baiyan Huang
2
Zgadzam się z @BaiyanHuang, ale nie jestem pewien, czy komentarz jest jasny. __FILE__nie jest prostym symbolem preprocesora, zmiany w bieżącym pliku są często używane do emisji nazwy bieżącego pliku (nagłówka lub modułu źródłowego). To __FILENAME__musiałby tylko źródło zewnętrzną
nhed
3
Rozwiązanie tej odpowiedzi nie jest przenośne, ponieważ wykorzystuje ucieczki powłoki Bourne'a. Lepiej użyć CMake, aby zaimplementować go w czysty i przenośny sposób. Zobacz odpowiedź na makro define_file_basename_for_sources tutaj.
Colin D Bennett
3
Wariant GNU Make to CFLAGS + = -D__ NAZWA PLIKU __ = \ "$ (notdir $ <) \"
ctuffli
4
@firegurafiku Na platformach wbudowanych rozmiar kodu jest często bardziej ograniczeniem niż szybkością. Jeśli w każdym pliku znajdują się instrukcje debugowania, mogą one szybko zwiększyć rozmiar pliku za pomocą ciągów pełnych ścieżek. W tej chwili mam do czynienia z taką platformą, a odwoływanie się __FILE__wszędzie wypada z przestrzeni kodu.
mrtumnus
26

Właśnie wymyśliłem świetne rozwiązanie, które działa zarówno z plikami źródłowymi, jak i nagłówkowymi, jest bardzo wydajne i działa w czasie kompilacji na wszystkich platformach bez rozszerzeń specyficznych dla kompilatora. To rozwiązanie zachowuje również względną strukturę katalogów projektu, dzięki czemu wiesz, w którym folderze znajduje się plik, i tylko w odniesieniu do katalogu głównego projektu.

Chodzi o to, aby uzyskać rozmiar katalogu źródłowego za pomocą narzędzia do budowania i po prostu dodać go do __FILE__makra, całkowicie usuwając katalog i wyświetlając tylko nazwę pliku, zaczynając od katalogu źródłowego.

Poniższy przykład jest zaimplementowany przy użyciu CMake, ale nie ma powodu, dla którego nie działałby z innymi narzędziami do kompilacji, ponieważ sztuczka jest bardzo prosta.

W pliku CMakeLists.txt zdefiniuj makro, które ma długość ścieżki do twojego projektu na 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}")

W kodzie źródłowym zdefiniuj __FILENAME__makro, które po prostu dodaje rozmiar ścieżki źródłowej do __FILE__makra:

#define __FILENAME__ (__FILE__ + SOURCE_PATH_SIZE)

Następnie użyj tego nowego makra zamiast __FILE__makra. To działa, ponieważ __FILE__ścieżka zawsze zaczyna się od ścieżki do katalogu źródłowego CMake. Usuwając go z __FILE__łańcucha, preprocesor zajmie się określeniem poprawnej nazwy pliku i wszystko będzie zależne od katalogu głównego projektu CMake.

Jeśli zależy Ci na wydajności, jest to tak samo wydajne, jak użycie __FILE__, ponieważ obie __FILE__i SOURCE_PATH_SIZEsą znanymi stałymi czasu kompilacji, więc kompilator może je zoptymalizować.

Jedynym miejscem, w którym to się nie powiedzie, jest sytuacja, w której używasz tego na wygenerowanych plikach i znajdują się one w folderze kompilacji poza źródłem. Wtedy prawdopodobnie będziesz musiał utworzyć inne makro, używając CMAKE_BUILD_DIRzmiennej zamiast CMAKE_SOURCE_DIR.

RenatoUtsch
źródło
4
Na początku tego nie rozumiałem. Zakodowałem przykłady i porównałem je z gcc i clang i działają. Eksperymentuję też z dodawaniem różnych dosłownych wartości liczbowych i zachowuje się to zgodnie z oczekiwaniami. Wtedy w końcu dotarło do mnie. __FILE__jest wskaźnikiem do tablicy bajtów. Zatem dodanie literału numerycznego jest po prostu dodaniem wskaźnika. Bardzo sprytny @RenatoUtsch
Daniel
Działa tylko wtedy, gdy plik źródłowy znajduje się w katalogu listy cmake. Jeśli plik źródłowy jest na zewnątrz, to się zepsuje, może z dostępem spoza literału. Więc uważaj z tym.
Andry
W rzeczywistości nie zmniejsza to rozmiaru kodu. Myślę, że cała ścieżka jest nadal wkompilowana w plik binarny, tylko wskaźnik jest modyfikowany.
Tarion
Zarówno __FILE__makro, jak i makra SOURCE_PATH_SIZE są stałymi znanymi w czasie kompilacji. Spodziewałbym się, że nowoczesne kompilatory optymalizujące będą w stanie wykryć, że część łańcucha nie jest używana i po prostu usunąć ją z pliku binarnego. W każdym razie nie sądzę, aby te kilka bajtów miało znaczący wpływ na rozmiar binarny, więc nie przejmowałbym się tym.
RenatoUtsch
@RenatoUtsch Projekt, nad którym pracuję, ma zmianę, która po prostu określa nazwę pliku, ale ma tę wadę, że nadaje nazwę pliku C również do nagłówka. Zmiana została wprowadzona w celu uzyskania odtwarzalnych kompilacji. Więc z gcc z -O2, czy łańcuch byłby rzeczywiście zoptymalizowany, a kompilacja odtwarzalna?
Paul Stelian
18

Czysto skompiluj tutaj rozwiązanie czasu. Opiera się na fakcie, że sizeof()literał ciągu znaków zwraca swoją długość + 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__)

Możesz swobodnie rozszerzyć kaskadę operatorów warunkowych do maksymalnej rozsądnej nazwy pliku w projekcie. Długość ścieżki nie ma znaczenia, o ile sprawdzasz wystarczająco daleko od końca łańcucha.

Zobaczę, czy uda mi się uzyskać podobne makro bez zakodowanej długości z rekurencją makra ...

Seva Alekseyev
źródło
3
Fajna odpowiedź. Musisz użyć co najmniej, -O1aby użyć tego do kompilacji.
Cyfrowa trauma,
1
Czy to nie wstecz? Chcesz znaleźć ostatnie wystąpienie znaku „/”, co oznacza, że ​​najpierw powinieneś zacząć od sizeof(s) > 2sprawdzenia. Również to nie zadziałało w czasie kompilacji dla mnie na -Os. W wyjściowym pliku binarnym były obecne ciągi pełnej ścieżki.
mrtumnus
15

Przynajmniej dla gcc wartością __FILE__jest ścieżka do pliku określona w wierszu poleceń kompilatora . Jeśli kompilujesz w file.cten sposób:

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

__FILE__poszerzy się "/full/path/to/file.c". Jeśli zamiast tego zrobisz to:

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

następnie __FILE__rozwinie się do just "file.c".

Może to być praktyczne lub nie.

Standard C nie wymaga takiego zachowania. Mówi tylko o __FILE__tym, że rozwija się do „Domniemana nazwa bieżącego pliku źródłowego (literał ciągu znaków)”.

Alternatywą jest skorzystanie z #linedyrektywy. Zastępuje bieżący numer wiersza i opcjonalnie nazwę pliku źródłowego. Jeśli chcesz nadpisać nazwę pliku, ale pozostawić sam numer linii, użyj __LINE__makra.

Na przykład możesz dodać to w górnej części file.c:

#line __LINE__ "file.c"

Jedynym problemem jest to, że przypisuje określony numer linii do następnej linii, a pierwszy argument #linemusi być sekwencją cyfr, więc nie możesz zrobić czegoś takiego

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

Zapewnienie, że nazwa pliku w #linedyrektywie jest zgodna z rzeczywistą nazwą pliku, pozostaje jako ćwiczenie.

Przynajmniej w przypadku gcc wpłynie to również na nazwę pliku zgłaszaną w komunikatach diagnostycznych.

Keith Thompson
źródło
1
Keith Thompson, to świetne rozwiązanie, dziękuję. Jedynym problemem jest to, że wydaje się, że to makro obniża __LINE__wartość o jeden. Tak __LINE__napisane w kolejce xdo oceny x-1. Przynajmniej z gcc 5.4.
elklepo
1
@Klepak: Masz rację i to jest standardowe zachowanie. Dyrektywa „powoduje, że implementacja zachowuje się tak, jakby następująca sekwencja wierszy źródłowych zaczynała się od wiersza źródłowego, który ma numer wiersza określony przez sekwencję cyfr”. Musi to być ciąg cyfr , więc nie możesz go użyć #line __LINE__-1, "file.c". Zaktualizuję odpowiedź.
Keith Thompson
1
Jak by to wyglądało w przypadku innych kompilatorów, takich jak Clang, MSVC lub Intel C Compiler?
Franklin Yu
8

Trochę za późno na imprezę, ale dla GCC spójrz na -ffile-prefix-map=old=newopcję:

Podczas kompilowania plików znajdujących się w katalogu old , zapisz wszelkie odniesienia do nich w wyniku kompilacji, tak jakby pliki znajdowały się w katalogu new . Określenie tej opcji jest równoważne określeniu wszystkich indywidualnych -f*-prefix-mapopcji. Można to wykorzystać do tworzenia odtwarzalnych kompilacji, które są niezależne od lokalizacji. Zobacz także -fmacro-prefix-mapi -fdebug-prefix-map.

Więc dodam do moich kompilacji Jenkinsa-ffile-prefix-map=${WORKSPACE}/=/ , a drugą, aby usunąć prefiks instalacji lokalnego pakietu deweloperskiego.

UWAGA Niestety -ffile-prefix-mapopcja jest avalable tylko w GCC 8 roku, jak to -fmacro-prefix-map, które myślę , robi __FILE__część. Bo, powiedzmy, GCC 5, mamy tylko to, -fdebug-prefix-mapco nie (wydaje się) mieć wpływ __FILE__.

simon.watts
źródło
1
Opcja -ffile-prefix-maprzeczywiście oznacza obie opcje -fdebug-prefix-mapi -fmacro-prefix-map. Zobacz także odniesienia na reproducible-builds.org/docs/build-path Błąd GCC, który śledzi -fmacro-prefix-mapi -ffile-prefix-mapjest 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__)]);
    }

Kod generuje przesunięcie czasu kompilacji, gdy:

  • gcc: co najmniej gcc6.1 + -O1
  • msvc: wstaw wynik do zmiennej constexpr:

      constexpr auto file = &__FILE__[get_file_name_offset(__FILE__)];
      printf("%s\n", file);
  • clang: utrzymuje się przy braku oceny czasu kompilacji

Istnieje sztuczka polegająca na zmuszeniu wszystkich 3 kompilatorów do oceny czasu kompilacji, nawet w konfiguracji debugowania z wyłączoną optymalizacją:

    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

Andry
źródło
Człowieku, to jest piękne i działa, dziękuję! Nie mam pojęcia, dlaczego ta odpowiedź jest tak niedoceniana.
Roman
5

W VC, podczas używania /FC, __FILE__rozwija do pełnej ścieżki, bez /FCopcji __FILE__rozwija nazwę pliku. ref: tutaj

user7382523
źródło
4

Nie ma sposobu, aby to zrobić. Oczywiście możesz to zrobić w czasie wykonywania, używając środowiska wykonawczego C, jak pokazały niektóre inne odpowiedzi, ale w czasie kompilacji, kiedy uruchamia się pre-procesor, nie masz szczęścia.

Sean
źródło
1
strrchrodpowiedź mogłaby przekonująco być obliczane w czasie kompilacji, choć oczywiście nadal nie przez preprocesor. Nie wiem, czy gcc faktycznie to robi, nie sprawdzałem, ale jestem prawie pewien, że oblicza strlenliterały łańcuchowe w czasie kompilacji.
Steve Jessop,
@Steve - może, ale to duża zależność od zachowania konkretnego kompilatora.
Sean,
Nie sądzę, że jest to duża zależność, ponieważ bardzo wątpię, czy ten kod ma krytyczne znaczenie dla wydajności. A jeśli tak, usuń go z pętli. W przypadkach, w których jest to ogromna sprawa, ponieważ absolutnie potrzebujesz literału ciągu zawierającego tylko nazwę basename, być może możesz obliczyć właściwy ciąg w czasie kompilacji , uruchamiając skrypt na źródle.
Steve Jessop,
5
Może nie być krytyczne dla wydajności, ale można je łatwo uznać za krytyczne dla prywatności. Nie ma prawdziwego powodu, aby ujawniać moje praktyki organizacyjne dla poszczególnych klientów w ciągach znaków zamrożonych w opublikowanym pliku EXE. Co gorsza, w przypadku aplikacji utworzonych w imieniu klienta te ciągi mogą ujawniać rzeczy, których mój klient wolałby nie robić, na przykład nie być autorem własnego produktu. Ponieważ __FILE__jest wywoływany niejawnie przez assert(), ten wyciek może wystąpić bez żadnego innego jawnego działania.
RBerteig
@RBerteig, sama nazwa podstawowa __FILE__, może również ujawniać rzeczy, których klient wolałby nie robić , więc używanie __FILE__dowolnego miejsca - niezależnie od tego, czy zawiera pełną absolutną nazwę ścieżki, czy tylko nazwa podstawowa - ma te same problemy, które wskazałeś. W tej sytuacji wszystkie dane wyjściowe będą musiały zostać przeanalizowane i należy wprowadzić specjalny interfejs API, który będzie udostępniony klientom. Reszta wyjścia powinna zostać zrzucona do / dev / NULL lub stdout, a stderr powinno zostać zamknięte. :-)
tchen
4

Użyj funkcji basename () lub, jeśli pracujesz w systemie Windows, _splitpath () .

#include <libgen.h>

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

Spróbuj także man 3 basenamew muszli.

Prof. Falken
źródło
2
@mahmood: char file_copy[] = __FILE__; const char *filename = basename(__FILE__);. Powodem kopiowania jest to, że basename może modyfikować ciąg wejściowy. Musisz także uważać, aby wskaźnik wyniku był dobry tylko do momentu basenameponownego wywołania. Oznacza to, że nie jest bezpieczny dla wątków.
Steve Jessop,
@SteveJessop, ah zapomniałem. Prawdziwe.
Prof. Falken,
1
@Amigable: aby być uczciwym, podejrzewam, że basenamew rzeczywistości nie zmodyfikuje ciągu wejściowego, który wynika z __FILE__, ponieważ ciąg wejściowy nie ma /na końcu, więc nie ma potrzeby modyfikacji. Więc może ci się to basenameujrzeć na sucho, ale myślę, że gdy ktoś zobaczy to po raz pierwszy , powinien zobaczyć to ze wszystkimi ograniczeniami.
Steve Jessop,
@SteveJessop Strona podręcznika BSd dla basename () wspomina, że ​​starsza wersja basename () pobiera znak const * i nie modyfikuje ciągu. Strona podręcznika systemu Linux nie wspomina nic o const, ale wspomina, że ​​może zwrócić część łańcucha argumentu. Dlatego najlepiej ostrożnie radzić sobie z basename ().
Prof. Falken,
@SteveJessop, hehe, dopiero teraz, po uważnym przyjrzeniu się twojemu komentarzowi, po czterech latach, zdaję sobie sprawę, że /na końcu napisu basename może mieć dobry powód, aby zmodyfikować swój argument.
Prof. Falken
4

Jeśli używasz CMAKEkompilatora GNU, to globalzdefiniowanie działa dobrze:

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__MY_FILE__='\"$(notdir $(abspath $<))\"'")
GerneralAmerica
źródło
4

Muszę użyć tego samego rozwiązania z @Patrick jest odpowiedzią na lata.

Ma mały problem, gdy pełna ścieżka zawiera łącze symboliczne.

Lepsze rozwiązanie.

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 $<))\"'")

Dlaczego warto tego używać?

  • -Wno-builtin-macro-redefinedwyciszyć ostrzeżenia kompilatora dotyczące przedefiniowania __FILE__makra.

    Te kompilatory nie obsługują tego, zapoznaj się z poniższym sposobem Robust .

  • Usunięcie ścieżki projektu ze ścieżki do pliku jest Twoim prawdziwym wymaganiem. Nie chcesz tracić czasu, aby dowiedzieć się, gdzie jest header.hplik src/foo/header.hlub src/bar/header.h.

  • Powinniśmy usunąć __FILE__makro w cmakepliku konfiguracyjnym.

    To makro jest używane w większości istniejących kodów. Po prostu przedefiniuj to, aby cię uwolnić.

    Kompilatory, takie jak, gccwstępnie definiują to makro na podstawie argumentów wiersza poleceń. Pełna ścieżka jest zapisywana w makefiles wygenerowanych przez cmake.

  • CMAKE_*_FLAGSWymagany jest twardy kod .

    Istnieje kilka poleceń dodających opcje lub definicje kompilatora w niektórych nowszych wersjach, takich jak add_definitions()i add_compile_definitions(). Te polecenia przeanalizują funkcje make, takie jak substprzed zastosowaniem do plików źródłowych. Tego nie chcemy.

Solidny sposób na -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)

Pamiętaj, aby usunąć tę opcję kompilatora z set(*_FLAGS ... -D__FILE__=...)linii.

Levi.G
źródło
Nie działa to w przypadku treści pochodzących z plików dołączanych.
chqrlie
czy możesz wysłać kod? Typowym przypadkiem jest ustawienie zmiennych w zakresie lokalnym i użycie ich w innym.
Levi.G
Na przykład, jeśli używasz __FILE__z definicją do tworzenia diagnostyki w funkcji wbudowanej zdefiniowanej w pliku nagłówkowym, diagnostyka środowiska wykonawczego zgłosi nazwę pliku przekazanego do kompilatora zamiast nazwy pliku dołączanego, podczas gdy numer wiersza będzie odwołaj się do pliku dołączanego.
chqrlie
tak, tak to zostało zaprojektowane, do najczęstszego zastosowania #define LOG(fmt, args...) printf("%s " fmt, __FILE__, ##args). kiedy używasz LOG()makra, tak naprawdę nie chcesz widzieć log.hw wiadomościach. w końcu __FILE__makro jest rozwijane w każdym pliku C / Cpp (jednostce kompilacji) zamiast w dołączonych plikach.
Levi.G
3

Niewielką odmianą tego, co zaproponował @ red1ynx, byłoby utworzenie następującego makra:

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

W każdym z plików .c (pp) dodaj:

SET_THIS_FILE_NAME();

Następnie możesz odwołać się THIS_FILE_NAMEzamiast __FILE__:

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

Oznacza to, że konstrukcja jest wykonywana raz na plik .c (pp), a nie za każdym razem, gdy odwołuje się do makra.

Jest ograniczone do używania tylko z plików .c (pp) i byłoby bezużyteczne z plików nagłówkowych.

hmjd
źródło
3

Zrobiłem makro, __FILENAME__które za każdym razem unika cięcia pełnej ścieżki. Problemem jest przechowywanie wynikowej nazwy pliku w zmiennej cpp-local.

Można to łatwo zrobić, definiując statyczną zmienną globalną w pliku .h . Ta definicja zapewnia oddzielne i niezależne zmienne w każdym pliku .cpp , który zawiera rozszerzenie .h . Aby być odpornym na wielowątkowość, warto sprawić, aby zmienna (e) była również lokalna (TLS) w wątku.

Jedna zmienna przechowuje nazwę pliku (skurczona). Inny ma wartość, która __FILE__dawała niecięta. Plik h:

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

Samo makro wywołuje metodę z całą logiką:

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

A funkcja jest realizowana w ten sposób:

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 () można zaimplementować na różne sposoby, ale szybkie i proste wydaje się być dzięki 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;
}
Kunis
źródło
3

Najnowszy kompilator Clang ma __FILE_NAME__makro (patrz tutaj ).

dziwny róg
źródło
2

mam tylko nadzieję, że poprawię trochę makro FILE:

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

to łapie / i \, jak zażądał Czarek Tomczak, a to działa świetnie w moim mieszanym środowisku.

alexander golks
źródło
6
Definiowanie makra o nazwie FILE to naprawdę zły pomysł, jeśli włączysz <stdio.h>.
Keith Thompson
dobrze wiedzieć. Chciałem tylko pokazać Czarkowi moje \\ / rozwiązanie, więc nie przejmowałem się schematami nazewnictwa.
alexander golks
2

Próbować

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

po instrukcjach include w pliku źródłowym i dodaj

#pragma pop_macro("__FILE__")

na końcu pliku źródłowego.

Alain Totouom
źródło
2
push_macroi pop_macrosą niestandardowe. (gcc obsługuje je w celu zapewnienia zgodności z kompilatorami Microsoft Windows). W każdym razie nie ma sensu wypychać i wstawiać definicji __FILE__; przywrócona wartość i tak nie zostanie użyta po zakończeniu pliku źródłowego. Czystszym sposobem zmiany wartości __FILE__jest#line __LINE__ "foobar.c"
Keith Thompson,
1
A to powoduje wewnętrzny błąd w preprocesorze gcc. gcc.gnu.org/bugzilla/show_bug.cgi?id=69665
Keith Thompson
1

Oto przenośna funkcja, która działa zarówno w systemie Linux (ścieżka „/”), jak i Windows (połączenie „\” i „/”).
Kompiluje się z gcc, clang i 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;
}

Wyjście standardowe:

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: 
Sasha Zezulinsky
źródło
1

Oto rozwiązanie, które wykorzystuje obliczenia czasu kompilacji:

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__);
Dmitry Gordon
źródło
1

Jeśli znalazłeś się na tej stronie, szukając sposobu na usunięcie bezwzględnej ścieżki źródłowej, która wskazuje brzydką lokalizację kompilacji z pliku binarnego, który wysyłasz, poniżej może odpowiadać Twoim potrzebom.

Chociaż nie daje to dokładnej odpowiedzi, której wyraził autor, ponieważ zakłada użycie CMake , jest dość blisko. Szkoda, że ​​nikt o tym wcześniej nie wspominał, bo zaoszczędziłoby mi to mnóstwo czasu.

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

Ustawienie powyższej zmiennej na ONwygeneruje polecenie budowania w formacie:

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

W rezultacie __FILE__makro rozwiąże problem../../src/path/to/source.c

Dokumentacja CMake

Uważaj jednak na ostrzeżenie na stronie dokumentacji:

Użyj ścieżek względnych (może nie działać!).

Nie ma gwarancji, że zadziała we wszystkich przypadkach, ale zadziałał w moim - CMake 3.13 + gcc 4.5

IvanR
źródło
Dokumentacja CMake 3.4+ stwierdza, że ​​„Ta zmienna nie ma wpływu. Częściowo zaimplementowany efekt, jaki miała w poprzednich wersjach, został usunięty w CMake 3.4”. Jeśli zadziałało z 3.13, jest inny powód.
mike.dld
0

Ponieważ używasz GCC, możesz skorzystać z

__BASE_FILE__To makro jest interpretowane jako nazwa głównego pliku wejściowego w postaci stałej w języku C. To jest plik źródłowy określony w wierszu poleceń preprocesora lub kompilatora C.

a następnie kontroluj sposób wyświetlania nazwy pliku, zmieniając reprezentację pliku źródłowego (pełna ścieżka / ścieżka względna / nazwa podstawowa) w czasie kompilacji.

ziu
źródło
6
nie robi różnicy. Użyłem __FILE__i __BASE_FILE__jednak oba pokazują pełną ścieżkę do pliku
mahmood
jak wywołać kompilator?
ziu
2
Wtedy założę się, że SCONS dzwoni do gcc w ten sposób gcc /absolute/path/to/file.c. Jeśli znajdziesz sposób na zmianę tego zachowania (otwierając kolejne pytanie na SO, lol?), Nie musisz modyfikować ciągu w czasie wykonywania
ziu
17
Ta odpowiedź jest w 100% błędna. __BASE_FILE__(jak mówią dokumenty, choć niewyraźnie) tworzy nazwę pliku podanego w linii poleceń , np. test.club w /tmp/test.czależności od tego, jak wywołałeś kompilator. To jest dokładnie to samo co __FILE__, z wyjątkiem sytuacji, gdy jesteś w pliku nagłówkowym, w którym to przypadku __FILE__tworzy nazwę bieżącego pliku (np. foo.h), Podczas gdy __BASE_FILE__kontynuuje tworzenie test.c.
Quuxplusone
0

Oto rozwiązanie, które działa w środowiskach, które nie mają biblioteki ciągów (jądro Linuksa, systemy wbudowane itp.):

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

Teraz po prostu użyj FILENAMEzamiast __FILENAME__. Tak, nadal jest to element środowiska uruchomieniowego, ale działa.

beavis9k
źródło
Może chcieć zaznaczyć „/” i „\”, w zależności od platformy.
Technophile
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
Przygaszony
źródło
0

Krótka, działająca odpowiedź zarówno dla systemu Windows, jak i * nix:

#define __FILENAME__ std::max<const char*>(__FILE__,\
    std::max(strrchr(__FILE__, '\\')+1, strrchr(__FILE__, '/')+1))
Luc Bloom
źródło
Dlaczego potrzebujesz std::max<const char*>zamiast tylko std::max?
chqrlie