Das Makro __FILE__ zeigt den vollständigen Pfad an


164

Das __FILE__in C verfügbare vordefinierte Standardmakro zeigt den vollständigen Pfad zur Datei an. Gibt es eine Möglichkeit, den Weg zu verkürzen? Ich meine statt

/full/path/to/file.c

Aha

to/file.c

oder

file.c

18
Es wäre wirklich großartig, eine reine Präprozessorlösung zu finden. Ich befürchte, dass die auf Zeichenfolgenoperationen basierenden Vorschläge zur Laufzeit ausgeführt werden.
cdleonard

9
Da Sie gcc verwenden, können Sie den Inhalt ändern, __FILE__indem Sie den Dateinamen ändern, den Sie in der Befehlszeile übergeben. Also statt gcc /full/path/to/file.cversuchen cd /full/path/to; gcc file.c; cd -;. Natürlich steckt noch ein bisschen mehr dahinter, wenn Sie sich für den Include-Pfad oder den Speicherort der Ausgabedatei auf das aktuelle Verzeichnis von gcc verlassen. Bearbeiten: Die gcc-Dokumente legen nahe, dass es sich um den vollständigen Pfad handelt, nicht um das Argument für den Namen der Eingabedatei, aber das ist nicht das, was ich für gcc 4.5.3 unter Cygwin sehe. Sie können es also auch unter Linux ausprobieren und sehen.
Steve Jessop

4
GCC 4.5.1 (speziell für arm-none-eabi entwickelt) verwendet den genauen Text des Dateinamens in der Befehlszeile. In meinem Fall war es die Schuld der IDE, GCC mit allen vollständig qualifizierten Dateinamen aufzurufen, anstatt das aktuelle Verzeichnis an einem sinnvollen (Speicherort der Projektdatei, vielleicht?) Oder konfigurierbar zu platzieren und relative Pfade von dort zu verwenden. Ich vermute, dass viele IDEs dies tun (insbesondere unter Windows), weil sie sich unwohl fühlen, wenn sie erklären, wo sich das "aktuelle" Verzeichnis wirklich für eine GUI-App befindet.
RBerteig

3
@SteveJessop - hoffe du hast diesen Kommentar gelesen. Ich habe eine Situation, in der ich __FILE__gedruckt sehe ../../../../../../../../rtems/c/src/lib/libbsp/sparc/leon2/../../shared/bootcard.cund ich möchte wissen, wo gcc die Datei so kompiliert hat, dass sich diese Datei relativ befindet, wie sie gezeigt wird.
Chan Kim

1
Diese Frage ist kein Betrug der verknüpften. Zum einen handelt es sich bei dem verknüpften um C ++, und die Antworten befassen sich folglich mit C ++ - Makro-Esoterik. Zweitens gibt es in der Frage von OP nichts, was eine Makrolösung vorschreibt. Es weist nur feierlich auf ein Problem hin und stellt eine offene Frage.
Prof. Falken

Antworten:


166

Versuchen

#include <string.h>

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

Verwenden Sie für Windows '\\' anstelle von '/'.


13
/ist ein gültiges Pfadtrennzeichen in Windows.
Hans Passant

11
/ist ein gültiges Pfadtrennzeichen in Dateinamen, die an CreateFile () usw. übergeben werden. Dies bedeutet jedoch nicht immer, dass Sie /nur irgendwo in Windows verwenden können, da es eine Tradition (von CPM geerbt) gibt, die /als Argument an der Eingabeaufforderung verwendet wird. Ein Qualitätswerkzeug würde jedoch darauf achten, Dateinamen sowohl in Schrägstriche als auch in Schrägstriche zu unterteilen, um Probleme für Benutzer zu vermeiden, die diese verwenden können /.
RBerteig

5
@AmigableClarkKant, nein, Sie können beide Trennzeichen im selben Dateinamen mischen.
RBerteig

2
Wenn Ihre Plattform dies unterstützt, ist es char* fileName = basename(__FILE__); definitiv unter Linux und OS X vorhanden. Sie wissen jedoch nichts über Windows.
JeremyP

14
Dies könnte auf verkürzt werden strrchr("/" __FILE__, '/') + 1. Durch das Voranstellen von "/" __FILE__wird sichergestellt, dass strrchr etwas findet, und somit wird die Bedingung ?: Nicht mehr benötigt.
ɲeuroburɳ

54

Hier ist ein Tipp, wenn Sie cmake verwenden. Von: http://public.kitware.com/pipermail/cmake/2013-January/053117.html

Ich kopiere den Tipp, damit alles auf dieser Seite steht:

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

Wenn Sie GNU make verwenden, sehe ich keinen Grund, warum Sie dies nicht auf Ihre eigenen Makefiles ausweiten können. Zum Beispiel könnten Sie eine Zeile wie diese haben:

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

Wo $(SOURCE_PREFIX)ist das Präfix, das Sie entfernen möchten?

Dann __FILENAME__anstelle von verwenden __FILE__.


11
Ich fürchte, dann funktioniert dies nicht für die DATEI, auf die in der Header-Datei verwiesen wird.
Baiyan Huang

2
Stimmen Sie @BaiyanHuang zu, aber nicht sicher, ob der Kommentar klar ist. __FILE__ist kein einfaches Präprozessorsymbol, es ändert sich in die aktuelle Datei und wird häufig zum Ausgeben des Namens der aktuellen Datei (Header oder Quellmodul) verwendet. Dies __FILENAME__würde nur die äußerste Quelle haben
nhed

3
Die Lösung dieser Antwort ist nicht portierbar, da sie Bourne-Shell-Escapezeichen verwendet. Verwenden Sie CMake besser, um es sauber und tragbar zu implementieren. Siehe die Makroantwort define_file_basename_for_sources hier.
Colin D Bennett

3
Die GNU Make-Variante davon ist CFLAGS + = -D__FILENAME __ = "$ (notdir $ <)"
ctuffli

4
@firegurafiku Auf eingebetteten Plattformen ist die Codegröße häufig eher eine Einschränkung als die Geschwindigkeit. Wenn Sie Debug-Anweisungen in jeder Datei haben, kann dies die Dateigröße mit Zeichenfolgen mit vollem Pfad schnell erhöhen. Ich habe es im Moment mit einer solchen Plattform zu tun, und __FILE__überall zu referenzieren , sprengt den Code-Raum.
Mrtumnus

26

Ich habe gerade an eine großartige Lösung gedacht, die sowohl mit Quell- als auch mit Header-Dateien funktioniert, sehr effizient ist und die Kompilierungszeit auf allen Plattformen ohne compilerspezifische Erweiterungen unterstützt. Diese Lösung behält auch die relative Verzeichnisstruktur Ihres Projekts bei, sodass Sie wissen, in welchem ​​Ordner sich die Datei befindet und nur relativ zum Stammverzeichnis Ihres Projekts.

Die Idee ist, die Größe des Quellverzeichnisses mit Ihrem Build-Tool zu ermitteln und es einfach dem __FILE__Makro hinzuzufügen , das Verzeichnis vollständig zu entfernen und nur den Dateinamen anzuzeigen, der in Ihrem Quellverzeichnis beginnt.

Das folgende Beispiel wird mit CMake implementiert, aber es gibt keinen Grund, warum es mit anderen Build-Tools nicht funktionieren würde, da der Trick sehr einfach ist.

Definieren Sie in der Datei CMakeLists.txt ein Makro mit der Länge des Pfads zu Ihrem Projekt in 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}")

Definieren Sie in Ihrem Quellcode ein __FILENAME__Makro, das dem Makro nur die Quellpfadgröße hinzufügt __FILE__:

#define __FILENAME__ (__FILE__ + SOURCE_PATH_SIZE)

Verwenden Sie dann einfach dieses neue Makro anstelle des __FILE__Makros. Dies funktioniert, da der __FILE__Pfad immer mit dem Pfad zu Ihrem CMake-Quellverzeichnis beginnt. Durch Entfernen aus der __FILE__Zeichenfolge übernimmt der Präprozessor die Angabe des richtigen Dateinamens und alles relativ zum Stammverzeichnis Ihres CMake-Projekts.

Wenn Sie sich für die Leistung interessieren, ist dies genauso effizient wie die Verwendung __FILE__, da beide __FILE__und SOURCE_PATH_SIZEbekannte Kompilierungszeitkonstanten bekannt sind, sodass sie vom Compiler optimiert werden können.

Der einzige Ort, an dem dies fehlschlagen würde, ist, wenn Sie dies für generierte Dateien verwenden und diese sich in einem Off-Source-Build-Ordner befinden. Dann müssen Sie wahrscheinlich ein anderes Makro mit der CMAKE_BUILD_DIRVariablen anstelle von erstellen CMAKE_SOURCE_DIR.


4
Ich habe das zuerst nicht verstanden. Ich habe Beispiele codiert und sie gegen gcc und clang ausgeführt, und sie funktionieren. Ich experimentiere auch damit, nur verschiedene numerische Literalwerte anzuhängen, und das verhält sich wie erwartet. Dann dämmerte es mir endlich. __FILE__ist ein Zeiger auf ein Array von Bytes. Das Hinzufügen eines numerischen Literals ist also nur das Hinzufügen von Zeigern. Sehr klug @RenatoUtsch
Daniel

Dies funktioniert nur, wenn sich eine Quelldatei im Verzeichnis cmake list befindet. Wenn sich eine Quelldatei außerhalb befindet, wird sie beschädigt, möglicherweise mit Zugriff außerhalb einer Literalzeichenfolge. Also sei vorsichtig damit.
Andry

Die Codegröße wird dadurch nicht reduziert. Ich denke, der gesamte Pfad ist immer noch in die Binärdatei kompiliert, nur der Zeiger wird geändert.
Tarion

Sowohl das __FILE__Makro als auch das Makro SOURCE_PATH_SIZE sind Konstanten, die zur Kompilierungszeit bekannt sind. Ich würde erwarten, dass moderne Optimierungs-Compiler erkennen können, dass ein Teil der Zeichenfolge nicht verwendet wird, und ihn einfach aus der Binärdatei entfernen. Wie auch immer, ich glaube nicht, dass diese wenigen Bytes einen signifikanten Unterschied in der Binärgröße bewirken würden, also würde mich das nicht wirklich interessieren.
RenatoUtsch

@RenatoUtsch Das Projekt, an dem ich arbeite, hat eine Änderung, die nur den Dateinamen angibt, aber den Nachteil hat, dass der C-Dateiname auch dem Header zugewiesen wird. Die Änderung wurde vorgenommen, um reproduzierbare Builds zu erhalten. Würde also mit gcc mit -O2 der String tatsächlich optimiert und der Build reproduzierbar gemacht?
Paul Stelian

18

Zeitlösung hier rein kompilieren. Es basiert auf der Tatsache, dass sizeof()ein String-Literal seine Länge + 1 zurückgibt.

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

Sie können die bedingte Operatorkaskade auch auf den maximal sinnvollen Dateinamen im Projekt erweitern. Die Pfadlänge spielt keine Rolle, solange Sie weit genug vom Ende der Zeichenfolge entfernt prüfen.

Ich werde sehen, ob ich ein ähnliches Makro ohne fest codierte Länge mit Makrorekursion erhalten kann ...


3
Coole Antwort. Sie müssen mindestens -O1verwenden, um die Kompilierungszeit zu nutzen.
Digitales Trauma

1
Ist das nicht rückwärts? Sie möchten das letzte Vorkommen von '/' finden, was bedeutet, dass Sie zuerst mit der sizeof(s) > 2Prüfung beginnen sollten . Außerdem funktionierte dies zur Kompilierungszeit für mich bei -Os nicht. Die vollständigen Pfadzeichenfolgen waren in der Ausgabebinärdatei vorhanden.
Mrtumnus

15

Zumindest für gcc ist der Wert von __FILE__der Dateipfad, wie in der Befehlszeile des Compilers angegeben . Wenn Sie so kompilieren file.c:

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

das __FILE__wird sich erweitern auf "/full/path/to/file.c". Wenn Sie stattdessen Folgendes tun:

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

dann __FILE__wird auf nur erweitert "file.c".

Dies kann praktisch sein oder auch nicht.

Der C-Standard erfordert dieses Verhalten nicht. Alles, worüber es sagt, __FILE__ist, dass es auf "Der vermutete Name der aktuellen Quelldatei (ein Zeichenfolgenliteral)" erweitert wird.

Eine Alternative ist die Verwendung der #lineRichtlinie. Es überschreibt die aktuelle Zeilennummer und optional den Namen der Quelldatei. Wenn Sie den Dateinamen überschreiben möchten, aber die Zeilennummer in Ruhe lassen möchten, verwenden Sie das __LINE__Makro.

Zum Beispiel können Sie dies oben hinzufügen file.c:

#line __LINE__ "file.c"

Das einzige Problem dabei ist, dass die angegebene Zeilennummer der folgenden Zeile zugewiesen wird und das erste Argument #lineeine Ziffernfolge sein muss, damit Sie so etwas nicht tun können

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

Es #linewird als Übung belassen, sicherzustellen, dass der Dateiname in der Direktive mit dem tatsächlichen Namen der Datei übereinstimmt.

Zumindest für gcc wirkt sich dies auch auf den in Diagnosemeldungen angegebenen Dateinamen aus.


1
Keith Thompson, es ist eine großartige Lösung, danke. Das einzige Problem ist, dass dieses Makro den __LINE__Wert anscheinend um eins senkt . So __LINE__geschrieben in Zeile xgest ausgewertet x-1. Zumindest mit gcc 5.4.
Elklepo

1
@Klepak: Du hast recht, und das ist Standardverhalten. Die Anweisung "bewirkt, dass sich die Implementierung so verhält, als ob die folgende Folge von Quellzeilen mit einer Quellzeile beginnt, deren Zeilennummer durch die Ziffernfolge angegeben ist". Und es muss eine Ziffernfolge sein , damit Sie sie nicht verwenden können #line __LINE__-1, "file.c". Ich werde meine Antwort aktualisieren.
Keith Thompson

1
Wie wäre es für andere Compiler wie Clang, MSVC oder Intel C Compiler?
Franklin Yu

7

Ein bisschen spät zur Party, aber für GCC schauen Sie sich die -ffile-prefix-map=old=newOption an:

Wenn Sie Dateien kompilieren, die sich im alten Verzeichnis befinden , notieren Sie im Ergebnis der Kompilierung alle Verweise darauf, als ob sich die Dateien stattdessen im neuen Verzeichnis befinden würden . Die Angabe dieser Option entspricht der Angabe aller einzelnen -f*-prefix-mapOptionen. Dies kann verwendet werden, um reproduzierbare Builds zu erstellen, die ortsunabhängig sind. Siehe auch -fmacro-prefix-mapund -fdebug-prefix-map.

Also werde ich für meine Jenkins- Builds ein -ffile-prefix-map=${WORKSPACE}/=/weiteres hinzufügen , um das lokale Entwicklungspaket-Installationspräfix zu entfernen.

HINWEIS Leider ist die -ffile-prefix-mapOption nur ab GCC 8 verfügbar, ebenso wie -fmacro-prefix-mapdas, was meiner Meinung nach den __FILE__Teil ausmacht . Zum Beispiel für GCC 5 haben wir nur das, -fdebug-prefix-mapwas nicht (anscheinend) beeinflusst __FILE__.


1
Option -ffile-prefix-mapimpliziert in der Tat beides -fdebug-prefix-mapund -fmacro-prefix-mapOptionen. Siehe auch die Verweise auf reproducible-builds.org/docs/build-path Die GCC Fehler , dass Titel -fmacro-prefix-mapund -ffile-prefix-mapist 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__)]);
    }

Code generiert einen Kompilierungszeitversatz, wenn:

  • gcc: mindestens gcc6.1 + -O1
  • msvc: Ergebnis in constexpr Variable setzen:

      constexpr auto file = &__FILE__[get_file_name_offset(__FILE__)];
      printf("%s\n", file);
  • clang: bleibt bei der Auswertung der Kompilierungszeit bestehen

Es gibt einen Trick, um zu erzwingen, dass alle 3 Compiler die Kompilierungszeitbewertung auch in der Debug-Konfiguration mit deaktivierter Optimierung durchführen:

    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


Mann, das ist wunderschön und es funktioniert, danke! Keine Ahnung, warum diese Antwort so unterschätzt wird.
Roman

5

In VC, bei der Verwendung /FC, __FILE__erweitert auf den vollständigen Pfad, ohne die /FCOption __FILE__Dateinamen erweitert. ref: hier


4

Es gibt keine Möglichkeit zur Kompilierung, um dies zu tun. Natürlich können Sie dies zur Laufzeit mit der C-Laufzeit tun, wie einige der anderen Antworten gezeigt haben, aber zur Kompilierungszeit, wenn der Präprozessor einschaltet, haben Sie kein Glück.


1
Die strrchrAntwort könnte plausibel zur Kompilierungszeit berechnet werden, obwohl dies natürlich immer noch nicht vom Präprozessor geschieht. Ich weiß nicht, ob gcc es tatsächlich tut, ich habe es nicht überprüft, aber ich bin mir ziemlich sicher, dass es strlenzur Kompilierungszeit aus String-Literalen berechnet .
Steve Jessop

@Steve - vielleicht, aber das ist eine große Abhängigkeit vom compilerspezifischen Verhalten.
Sean

Ich denke nicht, dass es eine große Abhängigkeit ist, da ich sehr bezweifle, dass dieser Code leistungskritisch ist. Und wenn ja, verschieben Sie es aus der Schleife. In Fällen, in denen dies eine große Sache ist, weil Sie unbedingt ein Zeichenfolgenliteral benötigen, das nur den Basisnamen enthält, können Sie möglicherweise die richtige Zeichenfolge zur Erstellungszeit berechnen , indem Sie ein Skript über die Quelle ausführen.
Steve Jessop

5
Es ist möglicherweise nicht leistungskritisch, kann jedoch leicht als datenschutzkritisch angesehen werden. Es gibt keinen wirklich guten Grund, meine organisatorischen Praktiken pro Kunde in Zeichenfolgen zu enthüllen, die in einer freigegebenen EXE-Datei eingefroren sind. Schlimmer noch, für Anwendungen, die im Auftrag eines Kunden erstellt wurden, können diese Zeichenfolgen Dinge enthüllen, die mein Kunde möglicherweise lieber nicht möchte, z. B. nicht der Autor seines eigenen Produkts zu sein. Da dies __FILE__implizit durch aufgerufen wird assert(), kann dieses Leck ohne jede andere offensichtliche Handlung auftreten.
RBerteig

@RBerteig Der Basisname __FILE__selbst kann auch Dinge enthüllen, die der Kunde möglicherweise lieber nicht möchte. Die Verwendung von __FILE__irgendwo überhaupt - unabhängig davon, ob er den vollständigen absoluten Pfadnamen oder nur den Basisnamen enthält - hat dieselben Probleme, auf die Sie hingewiesen haben. In dieser Situation muss die gesamte Ausgabe überprüft und eine spezielle API für die Ausgabe an Kunden eingeführt werden. Der Rest der Ausgabe sollte in / dev / NULL oder stdout ausgegeben und stderr sollte geschlossen werden. :-)
tchen

4

Verwenden Sie die Funktion basename () oder unter Windows _splitpath () .

#include <libgen.h>

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

Versuchen Sie es auch man 3 basenamein einer Shell.


2
@ Mahmood : char file_copy[] = __FILE__; const char *filename = basename(__FILE__);. Der Grund für die Kopie ist, dass der Basisname die Eingabezeichenfolge ändern kann. Sie müssen auch darauf achten, dass der Ergebniszeiger nur gut ist, bis basenameer erneut aufgerufen wird. Dies bedeutet, dass es nicht threadsicher ist.
Steve Jessop

@SteveJessop, ah ich habe es vergessen. Wahr.
Prof. Falken

1
@Amigable: Um fair zu sein, ich vermute, basenamedass die daraus resultierende Eingabezeichenfolge tatsächlich nicht geändert wird __FILE__, da die Eingabezeichenfolge /am Ende keine hat und daher keine Änderungen erforderlich sind. Sie könnten also damit durchkommen, aber ich denke, wenn jemand das erste Mal sieht basename, sollte er es mit allen Einschränkungen sehen.
Steve Jessop

@SteveJessop In der BSd-Manpage für basename () wird erwähnt, dass die ältere Version von basename () ein const char * verwendet und die Zeichenfolge nicht ändert. Die Linux-Manpage erwähnt nichts über const, erwähnt aber, dass es einen Teil der Argumentzeichenfolge zurückgeben kann. Seien Sie also am besten konservativ im Umgang mit basename ().
Prof. Falken

@SteveJessop, hehe, ich habe nur jetzt nach sorgfältig auf Ihren Kommentar suchen, nach vier Jahren, realize , dass /ihr Argument am Ende des Strings Mittel Basisnamen kann einen guten Grund haben , zu ändern.
Prof. Falken

4

Wenn Sie CMAKEmit dem GNU-Compiler arbeiten global, funktioniert diese Definition einwandfrei:

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

4

Ich verwende seit Jahren dieselbe Lösung mit @Patricks Antwort .

Es gibt ein kleines Problem, wenn der vollständige Pfad eine Symbolverknüpfung enthält.

Bessere Lösung.

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

Warum sollte man das benutzen?

  • -Wno-builtin-macro-redefinedum die Compiler-Warnungen für die Neudefinition des __FILE__Makros stummzuschalten.

    Informationen zu Compilern, die dies nicht unterstützen, finden Sie im Abschnitt Robust .

  • Das Entfernen des Projektpfads vom Dateipfad ist Ihre eigentliche Anforderung. Sie möchten nicht die Zeit verschwenden, um herauszufinden, wo sich eine header.hDatei befindet, src/foo/header.hoder src/bar/header.h.

  • Wir sollten das __FILE__Makro in der cmakeKonfigurationsdatei entfernen.

    Dieses Makro wird in den meisten vorhandenen Codes verwendet. Definieren Sie es einfach neu, um frei zu werden.

    Compiler wie gccdefinieren dieses Makro anhand der Befehlszeilenargumente vor. Und der vollständige Pfad wird in makefiles geschrieben, das von generiert wird cmake.

  • Hardcode in CMAKE_*_FLAGSist erforderlich.

    Es gibt einige Befehle zum Hinzufügen von Compileroptionen oder -definitionen in einer neueren Version, wie z . B. add_definitions()und add_compile_definitions(). Diese Befehle analysieren die make-Funktionen wie substzuvor für Quelldateien. Das wollen wir nicht.

Robuster Weg für -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)

Denken Sie daran, diese Compileroption aus der set(*_FLAGS ... -D__FILE__=...)Zeile zu entfernen .


Dies funktioniert nicht für Inhalte, die aus Include-Dateien stammen.
Chqrlie

Kannst du einen Code posten? Ein häufiger Fall besteht darin, Variablen im lokalen Bereich festzulegen und in einem anderen zu verwenden.
Levi.G

Wenn Sie beispielsweise __FILE__mit Ihrer Definition eine Diagnose in einer Inline-Funktion erstellen, die in einer Header-Datei definiert ist, meldet die Laufzeitdiagnose den Namen der an den Compiler übergebenen Datei anstelle des Namens der Include-Datei, während die Zeilennummer dies tun würde Siehe die Include-Datei.
Chqrlie

Ja, es wurde so konzipiert, dass es für die häufigste Verwendung ist #define LOG(fmt, args...) printf("%s " fmt, __FILE__, ##args). Wenn Sie das LOG()Makro verwenden, möchten Sie es nicht wirklich log.hin Nachrichten sehen. Schließlich wird das __FILE__Makro in jeder C / Cpp-Datei (Kompilierungseinheit) anstelle der enthaltenen Dateien erweitert.
Levi.G

3

Eine geringfügige Abweichung von dem, was @ red1ynx vorgeschlagen hat, wäre das Erstellen des folgenden Makros:

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

Fügen Sie in jeder Ihrer .c (pp) -Dateien Folgendes hinzu:

SET_THIS_FILE_NAME();

Dann können Sie sich beziehen THIS_FILE_NAMEstatt __FILE__:

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

Dies bedeutet, dass die Konstruktion einmal pro .c (pp) -Datei ausgeführt wird, anstatt jedes Mal, wenn auf das Makro verwiesen wird.

Es ist auf die Verwendung nur aus .c (pp) -Dateien beschränkt und kann aus Header-Dateien nicht verwendet werden.


3

Ich habe ein Makro erstellt __FILENAME__, das es vermeidet, jedes Mal den vollen Pfad zu schneiden. Das Problem besteht darin, den resultierenden Dateinamen in einer cpp-lokalen Variablen zu speichern.

Dies kann einfach durch Definieren einer statischen globalen Variablen in der .h- Datei erfolgen. Diese Definition enthält separate und unabhängige Variablen in jeder CPP- Datei, die die .h- Datei enthält . Um ein Multithreading-Proof zu sein, lohnt es sich, die Variablen auch threadlokal (TLS) zu machen.

Eine Variable speichert den Dateinamen (verkleinert). Ein anderer enthält den nicht geschnittenen Wert, __FILE__der angegeben wurde. Die h-Datei:

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

Das Makro selbst ruft eine Methode mit der gesamten Logik auf:

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

Und die Funktion wird folgendermaßen implementiert:

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;
}

Das removePath () kann auf verschiedene Arten implementiert werden, aber das schnelle und einfache scheint mit strrchr zu sein:

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

Ich hoffe nur, das FILE-Makro ein wenig zu verbessern:

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

Dies fängt / und \, wie Czarek Tomczak es verlangt hat, und dies funktioniert großartig in meiner gemischten Umgebung.


6
Das Definieren eines Makros mit dem Namen FILEist eine wirklich schlechte Idee, wenn Sie einschließen <stdio.h>.
Keith Thompson

gut zu wissen. Ich wollte Czarek nur meine \\ / Lösung zeigen, damit ich mich nicht um Namensschemata kümmere.
Alexander Golks

2

Versuchen

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

nach den include-Anweisungen in Ihre Quelldatei und fügen Sie hinzu

#pragma pop_macro("__FILE__")

am Ende Ihrer Quelldatei.


2
push_macround pop_macrosind nicht Standard. (gcc unterstützt sie aus Gründen der Kompatibilität mit Microsoft Windows-Compilern.) In jedem Fall macht es keinen Sinn, die Definition von zu verschieben und zu löschen __FILE__. Der wiederhergestellte Wert wird ohnehin nach dem Ende der Quelldatei nicht mehr verwendet. Eine sauberere Möglichkeit, den Wert von zu ändern, __FILE__ist#line __LINE__ "foobar.c"
Keith Thompson

1
Dies führt zu einem internen Fehler im Präprozessor von gcc. gcc.gnu.org/bugzilla/show_bug.cgi?id=69665
Keith Thompson

1

Hier ist eine tragbare Funktion, die sowohl für Linux (Pfad '/') als auch für Windows (Mischung aus '\' und '/') funktioniert.
Kompiliert mit gcc, clang und 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;
}

Standardausgabe:

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

Hier ist die Lösung, die die Berechnung der Kompilierungszeit verwendet:

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

Wenn Sie auf dieser Seite nach einer Möglichkeit gesucht haben, den absoluten Quellpfad, der auf einen hässlichen Build-Speicherort verweist, aus der von Ihnen gelieferten Binärdatei zu entfernen, entspricht dies möglicherweise Ihren Anforderungen.

Obwohl dies nicht genau die Antwort liefert, die der Autor gewünscht hat , da es die Verwendung von CMake voraussetzt , kommt es ziemlich nahe. Schade, dass dies zuvor von niemandem erwähnt wurde, da es mir viel Zeit gespart hätte.

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

Wenn Sie die obige Variable auf setzen, ONwird ein Build-Befehl im folgenden Format generiert:

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

Infolgedessen wird das __FILE__Makro in aufgelöst../../src/path/to/source.c

CMake-Dokumentation

Beachten Sie jedoch die Warnung auf der Dokumentationsseite:

Verwenden Sie relative Pfade (funktioniert möglicherweise nicht!).

Es ist nicht garantiert, dass es in allen Fällen funktioniert, aber es funktioniert in meinem - CMake 3.13 + gcc 4.5


In der Dokumentation zu CMake 3.4+ heißt es: "Diese Variable hat keine Auswirkung. Die teilweise implementierte Auswirkung, die sie in früheren Versionen hatte, wurde in CMake 3.4 entfernt." Wenn es mit 3.13 für Sie funktioniert hat, gibt es einen anderen Grund dafür.
mike.dld

0

Da Sie GCC verwenden, können Sie Vorteil nehmen von

__BASE_FILE__Dieses Makro wird auf den Namen der Haupteingabedatei in Form einer C-String-Konstante erweitert. Dies ist die Quelldatei, die in der Befehlszeile des Präprozessors oder C-Compilers angegeben wurde

und steuern Sie dann, wie Sie den Dateinamen anzeigen möchten, indem Sie die Darstellung der Quelldatei (vollständiger Pfad / relativer Pfad / Basisname) zur Kompilierungszeit ändern.


6
macht keinen Unterschied. Ich habe verwendet __FILE__und __BASE_FILE__jedoch zeigen beide den vollständigen Pfad zur Datei
Mahmood

Wie rufst du den Compiler auf?
ziu

2
Dann wette ich, dass SCONS so gcc anruft gcc /absolute/path/to/file.c. Wenn Sie einen Weg finden, dieses Verhalten zu ändern (eine weitere Frage zu SO, lol?), Müssen Sie die Zeichenfolge zur Laufzeit nicht ändern
ziu

17
Diese Antwort ist 100% falsch. __BASE_FILE__(wie in den Dokumenten angegeben, wenn auch unklar) erzeugt den Namen der in der Befehlszeile angegebenen Datei , z. B. test.coder /tmp/test.cabhängig davon, wie Sie den Compiler aufgerufen haben. Das ist genau das Gleiche wie __FILE__, außer wenn Sie sich in einer Header-Datei befinden. In diesem Fall __FILE__wird der Name der aktuellen Datei (z. B. foo.h) erzeugt, während __BASE_FILE__weiterhin produziert wird test.c.
Quuxplusone

0

Hier ist eine Lösung, die für Umgebungen funktioniert, in denen die Zeichenfolgenbibliothek nicht vorhanden ist (Linux-Kernel, eingebettete Systeme usw.):

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

Jetzt einfach FILENAMEanstelle von verwenden __FILENAME__. Ja, es ist immer noch eine Laufzeitsache, aber es funktioniert.


Je nach Plattform möchten Sie möglicherweise sowohl '/' als auch '\' überprüfen.
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

0

Eine kurze, funktionierende Antwort für Windows und * nix:

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

Warum brauchst du std::max<const char*>statt nur std::max?
Chqrlie
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.