Handhabung der __FILE__-Makromanipulation zur Kompilierzeit

Lesezeit: 9 Minuten

Benutzer-Avatar
Gruseliger Erdferkel

Eines der Probleme, die ich beim Portieren einiger Sachen von Solaris nach Linux hatte, ist, dass der Solaris-Compiler das Makro erweitert __FILE__ während der Vorverarbeitung auf den Dateinamen (z. B. MeineDatei.cpp), während gcc unter Linux auf den vollständigen Pfad erweitert wird (z. B. /home/user/MeineDatei.cpp). Dies kann relativ einfach mit basename() gelöst werden, aber …. wenn Sie es häufig verwenden, müssen sich all diese Aufrufe von basename() summieren, oder?

Hier ist die Frage. Gibt es eine Möglichkeit, mithilfe von Vorlagen und statischer Metaprogrammierung basename() oder ähnliches zur Kompilierzeit auszuführen? Seit __FILE__ ist konstant und zur Kompilierzeit bekannt, dies könnte es einfacher machen. Was denkst du? Kann es getan werden?

  • Ein paar schnelle Experimente zeigen das __FILE__ wird zum Dateinamen erweitert, wie er auf der Befehlszeile angegeben wurde, der entweder absolut oder relativ sein kann. Der Unterschied liegt wahrscheinlich im Makefile. __BASE_FILE__eine gcc-Erweiterung, unterscheidet sich nur darin, dass sie eher den äußersten Dateinamen als irgendetwas liefert #included.

    – Keith Thompson

    17. Juli 2012 um 4:33 Uhr

Benutzer-Avatar
Colin D. Bennett

In Projekten, die CMake zum Steuern des Build-Prozesses verwenden, können Sie ein Makro wie dieses verwenden, um eine portable Version zu implementieren, die auf jedem Compiler oder jeder Plattform funktioniert. Obwohl ich persönlich den Dummkopf bemitleide, der etwas anderes als gcc verwenden muss … 🙂

# Helper function to add preprocesor definition of FILE_BASENAME
# to pass the filename without directory path for debugging use.
#
# Note that in header files this is not consistent with
# __FILE__ and __LINE__ since FILE_BASENAME will be the
# compilation unit source file name (.c/.cpp).
#
# Example:
#
#   define_file_basename_for_sources(my_target)
#
# Will add -DFILE_BASENAME="filename" for each source file depended on
# by my_target, where filename is the name of the file.
#
function(define_file_basename_for_sources targetname)
    get_target_property(source_files "${targetname}" SOURCES)
    foreach(sourcefile ${source_files})
        # Add the FILE_BASENAME=filename compile definition to the list.
        get_filename_component(basename "${sourcefile}" NAME)
        # Set the updated compile definitions on the source file.
        set_property(
            SOURCE "${sourcefile}" APPEND
            PROPERTY COMPILE_DEFINITIONS "FILE_BASENAME=\"${basename}\"")
    endforeach()
endfunction()

Um das Makro dann zu verwenden, rufen Sie es einfach mit dem Namen des CMake-Ziels auf:

define_file_basename_for_sources(myapplication)

  • Die Operation erwähnt cmake nicht, aber für Leute, die nach einer cmake-Lösung suchen, ist das eine wunderbare Antwort.

    – abdus_salam

    26. September 2018 um 12:07 Uhr

  • Ich habe eine zu verwendende Bearbeitung vorgeschlagen set_property(APPEND) statt get_property() und list(APPEND) manuell.

    – mathstuf

    7. November 2019 um 13:14 Uhr

  • Als Ersatz ist das leider nicht geeignet __FILE__ weil es nur den Dateinamen der Quelldatei der Kompilierungseinheit meldet, NICHT den Header, in den Sie möglicherweise den tatsächlichen Code eingefügt haben (was LINIE steht im Zusammenhang mit).

    – Steven Lu

    3. August 2021 um 2:02 Uhr

  • @StevenLu das ist ein guter Punkt. Es ist eine unvollkommene Lösung. Seine Nützlichkeit hängt also davon ab, wie Sie es verwenden. Wenn Sie es für Zusicherungen in Ihrer Implementierungsdatei verwenden, funktioniert es wie vorgesehen.

    – Colin D. Bennett

    3. August 2021 um 15:36 Uhr

  • Ich ging widerwillig mit strrchr klumpen. Uns wurde schon seit einiger Zeit die String-Manipulation zur Kompilierzeit versprochen. Wo ist es? Argh.

    – Steven Lu

    3. August 2021 um 17:52 Uhr


Benutzer-Avatar
Chetan Reddy

Mit C++11 haben Sie mehrere Optionen. Lassen Sie uns zuerst definieren:

constexpr int32_t basename_index (const char * const path, const int32_t index = 0, const int32_t slash_index = -1)
{
     return path [index]
         ? ( path [index] == "https://stackoverflow.com/"
             ? basename_index (path, index + 1, index)
             : basename_index (path, index + 1, slash_index)
           )
         : (slash_index + 1)
     ;
}

Wenn Ihr Compiler Anweisungsausdrücke unterstützt und Sie sicher sein möchten, dass die Berechnung des Basisnamens zur Kompilierzeit erfolgt, können Sie Folgendes tun:

// stmt-expr version
#define STRINGIZE_DETAIL(x) #x
#define STRINGIZE(x) STRINGIZE_DETAIL(x)

#define __FILELINE__ ({ static const int32_t basename_idx = basename_index(__FILE__);\
                        static_assert (basename_idx >= 0, "compile-time basename");  \
                        __FILE__ ":" STRINGIZE(__LINE__) ": " + basename_idx;})

Wenn Ihr Compiler keine Anweisungsausdrücke unterstützt, können Sie diese Version verwenden:

// non stmt-expr version
#define __FILELINE__ (__FILE__ ":" STRINGIZE(__LINE__) ": " + basename_index(__FILE__))

Mit dieser Nicht-stmt-expr-Version rufen gcc 4.7 und 4.8 basename_index zur Laufzeit auf, daher ist es besser, die stmt-expr-Version mit gcc zu verwenden. ICC 14 erzeugt optimalen Code für beide Versionen. ICC13 kann die stmt-expr-Version nicht kompilieren und erzeugt suboptimalen Code für die Nicht-stmt-expr-Version.

Nur der Vollständigkeit halber, hier ist der Code an einem Ort:

#include <iostream>
#include <stdint.h>

constexpr int32_t basename_index (const char * const path, const int32_t index = 0, const int32_t slash_index = -1)
{
   return path [index]
       ? ( path [index] == "https://stackoverflow.com/"
           ? basename_index (path, index + 1, index)
           : basename_index (path, index + 1, slash_index)
           )
       : (slash_index + 1)
       ;
}

#define STRINGIZE_DETAIL(x) #x
#define STRINGIZE(x) STRINGIZE_DETAIL(x)

#define __FILELINE__ ({ static const int32_t basename_idx = basename_index(__FILE__); \
                        static_assert (basename_idx >= 0, "compile-time basename");   \
                        __FILE__ ":" STRINGIZE(__LINE__) ": " + basename_idx;})


int main() {
  std::cout << __FILELINE__ << "It works" << std::endl;
}

  • Die Idee mit static_assert() in einem Anweisungsausdruck ist sehr nett und funktioniert perfekt mit GCC, da Anweisungsausdrücke jedoch, wie Sie sagten, eine nicht standardmäßige Erweiterung sind, ist diese Lösung nicht universell. Ich denke, es gibt eine Möglichkeit, eine Auswertung der Kompilierzeit zu erzwingen, ohne sich auf Anweisungsausdrücke zu verlassen, indem eine Vorlage mit ganzzahligen Parametern verwendet wird. Ich habe eine Antwort gepostet, die es beschreibt, was verwendet basename_index() aus dieser Antwort.

    – TerraPass

    30. Oktober 2017 um 16:39 Uhr


Es gibt derzeit keine Möglichkeit, eine vollständige Zeichenfolgenverarbeitung zur Kompilierungszeit durchzuführen (das Maximum, mit dem wir in Vorlagen arbeiten können, sind die seltsamen Vier-Zeichen-Literale).

Warum nicht einfach den bearbeiteten Namen statisch speichern, zB:

namespace 
{
  const std::string& thisFile() 
  {
      static const std::string s(prepocessFileName(__FILE__));
      return s;
  }
}

Auf diese Weise erledigen Sie die Arbeit nur einmal pro Datei. Natürlich kann man das auch in ein Makro packen etc.

Benutzer-Avatar
Andreas Keith

Vielleicht möchten Sie das versuchen __BASE_FILE__ Makro. Dies Seite beschreibt viele Makros, die gcc unterstützt.

Benutzer-Avatar
Jonathan Hatchett

Noch ein C++11 constexpr Methode ist wie folgt:

constexpr const char * const strend(const char * const str) {
    return *str ? strend(str + 1) : str;
}

constexpr const char * const fromlastslash(const char * const start, const char * const end) {
    return (end >= start && *end != "https://stackoverflow.com/" && *end != '\\') ? fromlastslash(start, end - 1) : (end + 1);
}

constexpr const char * const pathlast(const char * const path) {
    return fromlastslash(path, strend(path));
}

Die Verwendung ist auch ziemlich einfach:

std::cout << pathlast(__FILE__) << "\n";

Das constexpr wird nach Möglichkeit zur Kompilierzeit ausgeführt, andernfalls wird auf die Ausführung der Anweisungen zur Laufzeit zurückgegriffen.

Der Algorithmus unterscheidet sich ein wenig dadurch, dass er das Ende der Zeichenfolge findet und dann rückwärts arbeitet, um den letzten Schrägstrich zu finden. Es ist wahrscheinlich langsamer als die andere Antwort, aber da es zur Kompilierzeit ausgeführt werden soll, sollte es kein Problem sein.

  • Gute Idee, aber es funktioniert nicht richtig. || sollte durch && ersetzt werden, um es zu beheben.

    – Maxim Cholyavkin

    21. Juli 2016 um 5:40 Uhr

  • Die einzige Antwort, die tatsächlich mit dem Microsoft Visual Studio-Compiler funktioniert!

    – Benutzer

    26. Mai 2019 um 23:57 Uhr

  • Wir können Macht und Garantie Dies wird zur Kompilierzeit ausgewertet, indem das Ergebnis der Funktion in a gespeichert wird constexpr variabel als constexpr const char* myExpression = pathlast(__FILE__); std::cout << myExpression << "\n";. Quelle: Berechnung der Länge eines C-Strings zur Kompilierzeit. Ist das wirklich ein constexpr?

    – Benutzer

    20. Dezember 2019 um 1:23 Uhr


  • HIER IST DER DROIDE, DEN SIE SUCHEN

    – Steven Lu

    4. August 2021 um 17:21 Uhr

Ich mag die Antwort von @Chetan Reddy, die die Verwendung vorschlägt static_assert() in einem Anweisungsausdruck, um einen Kompilierzeitaufruf für die Funktion zu erzwingen, die den letzten Schrägstrich findet, wodurch Laufzeit-Overhead vermieden wird.

Anweisungsausdrücke sind jedoch keine standardmäßige Erweiterung und werden nicht universell unterstützt. Beispielsweise konnte ich den Code aus dieser Antwort unter Visual Studio 2017 (MSVC++ 14.1, glaube ich) nicht kompilieren.

Warum nicht stattdessen eine Vorlage mit ganzzahligen Parametern verwenden, wie zum Beispiel:

template <int Value>
struct require_at_compile_time
{
    static constexpr const int value = Value;
};

Nachdem wir eine solche Vorlage definiert haben, können wir sie mit verwenden basename_index() Funktion aus der Antwort von @Chetan Reddy:

require_at_compile_time<basename_index(__FILE__)>::value

Dies stellt dies sicher basename_index(__FILE__) wird tatsächlich zur Kompilierzeit aufgerufen, da zu diesem Zeitpunkt das Template-Argument bekannt sein muss.

Damit ist der vollständige Code für, nennen wir es JUST_FILENAMEMakro, das nur die Dateinamenkomponente von auswertet __FILE__ würde so aussehen:

constexpr int32_t basename_index (
    const char * const path, const int32_t index = 0, const int32_t slash_index = -1
)
{
     return path [index]
         ? ((path[index] == "https://stackoverflow.com/" || path[index] == '\\')  // (see below)
             ? basename_index (path, index + 1, index)
             : basename_index (path, index + 1, slash_index)
           )
         : (slash_index + 1)
     ;
}

template <int32_t Value>
struct require_at_compile_time
{
    static constexpr const int32_t value = Value;
};

#define JUST_FILENAME (__FILE__ + require_at_compile_time<basename_index(__FILE__)>::value)

Ich habe gestohlen basename_index() fast wörtlich aus der zuvor erwähnten Antwort, außer dass ich eine Überprüfung auf Windows-spezifische Backslash-Trennzeichen hinzugefügt habe.

  • Gute Idee, aber es funktioniert nicht richtig. || sollte durch && ersetzt werden, um es zu beheben.

    – Maxim Cholyavkin

    21. Juli 2016 um 5:40 Uhr

  • Die einzige Antwort, die tatsächlich mit dem Microsoft Visual Studio-Compiler funktioniert!

    – Benutzer

    26. Mai 2019 um 23:57 Uhr

  • Wir können Macht und Garantie Dies wird zur Kompilierzeit ausgewertet, indem das Ergebnis der Funktion in a gespeichert wird constexpr variabel als constexpr const char* myExpression = pathlast(__FILE__); std::cout << myExpression << "\n";. Quelle: Berechnung der Länge eines C-Strings zur Kompilierzeit. Ist das wirklich ein constexpr?

    – Benutzer

    20. Dezember 2019 um 1:23 Uhr


  • HIER IST DER DROIDE, DEN SIE SUCHEN

    – Steven Lu

    4. August 2021 um 17:21 Uhr

Ein weiterer möglicher Ansatz bei der Verwendung von CMake besteht darin, eine benutzerdefinierte Präprozessordefinition hinzuzufügen, die direkt verwendet make‘s automatische Variablen (auf Kosten einer wohl hässlichen Flucht):

add_definitions(-D__FILENAME__=\\"$\(<F\)\\")

Oder, wenn Sie CMake >= 2.6.0 verwenden:

cmake_policy(PUSH)
cmake_policy(SET CMP0005 OLD) # Temporarily disable new-style escaping.
add_definitions(-D__FILENAME__=\\"$\(<F\)\\")
cmake_policy(POP)

(Ansonsten CMake wird den Dingen zu viel entkommen.)

Hier machen wir uns die Tatsache zunutze make Ersatz $(<F) mit dem Quelldateinamen ohne führende Komponenten und dies sollte als angezeigt werden -D__FILENAME__=\"MyFile.cpp\" im ausgeführten Compiler-Befehl.

(Während makeDie Dokumentation von empfiehlt die Verwendung von $(notdir path $<) Stattdessen scheint es CMake besser zu gefallen, keine Leerzeichen in der hinzugefügten Definition zu haben.)

Sie können dann verwenden __FILENAME__ in Ihrem Quellcode wie Sie verwenden würden __FILE__. Aus Kompatibilitätsgründen möchten Sie möglicherweise einen sicheren Fallback hinzufügen:

#ifndef __FILENAME__
#define __FILENAME__ __FILE__
#endif

  • Mit einer Toolchain hat das gut funktioniert, aber eine andere hat bestanden cmake_pch.hxx.gch Als ein __FILENAME__ für alle Dateien. Wahrscheinlich, weil diese Datei am Anfang der Quellen von make hinzugefügt wurde. Ich endete mit der Verwendung add_definitions(-D__FILENAME__=\\"$\(subst .o,,$\(@F\)\)\\") das funktioniert bei mir in beiden Fällen. Es nimmt den Zielnamen an, das ist konventionell source_name.cpp.ound entfernt .o

    – Polyglot

    25. Oktober 2021 um 11:30 Uhr


1354340cookie-checkHandhabung der __FILE__-Makromanipulation zur Kompilierzeit

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy