La macro __FILE__ mostra il percorso completo


164

La macro predefinita standard __FILE__disponibile in C mostra il percorso completo del file. C'è un modo per abbreviare il percorso? Intendo invece di

/full/path/to/file.c

Vedo

to/file.c

o

file.c

18
Sarebbe davvero fantastico trovare una soluzione solo per il preprocessore. Temo che i suggerimenti basati sulle operazioni sulle stringhe verranno eseguiti in fase di esecuzione.
Cdleonard,

9
Dato che stai usando gcc, penso che puoi cambiare ciò che __FILE__contiene cambiando il nome del file che passi sulla riga di comando. Quindi invece di gcc /full/path/to/file.cprovare cd /full/path/to; gcc file.c; cd -;. Ovviamente c'è un po 'più di questo se si fa affidamento sulla directory corrente di gcc per il percorso di inclusione o la posizione del file di output. Modifica: i documenti di gcc suggeriscono che è il percorso completo, non l'argomento del nome del file di input, ma non è quello che sto vedendo per gcc 4.5.3 su Cygwin. Quindi puoi anche provarlo su Linux e vedere.
Steve Jessop,

4
GCC 4.5.1 (creato appositamente per arm-none-eabi) utilizza il testo esatto del nome del file sulla sua riga di comando. Nel mio caso è stata colpa dell'IDE per aver richiamato GCC con tutti i nomi di file pienamente qualificati invece di mettere la directory corrente in qualche luogo sensibile (posizione del file di progetto, forse?) O configurabile e usando i relativi percorsi da lì. Ho il sospetto che molti IDE lo facciano (soprattutto su Windows) per una sorta di disagio legato alla spiegazione di dove si trova realmente la directory "corrente" per un'app GUI.
RBerteig,

3
@SteveJessop - spero che tu abbia letto questo commento. Ho una situazione in cui vedo __FILE__stampato come ../../../../../../../../rtems/c/src/lib/libbsp/sparc/leon2/../../shared/bootcard.ce voglio sapere dove gcc ha compilato il file in modo tale che questo file si trovi relativamente come è mostrato.
Chan Kim,

1
Questa domanda non è una copia di quella collegata. Per uno, quello collegato riguarda C ++ e di conseguenza le risposte approfondiscono la macro esoterica di C ++. In secondo luogo, non c'è nulla nella domanda di OP che imponga una soluzione macro. Indica solennemente un problema e pone una domanda aperta.
Prof. Falken,

Risposte:


167

Provare

#include <string.h>

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

Per Windows usa '\\' invece di '/'.


13
/è un separatore di percorso valido in Windows.
Hans Passant,

11
/è un separatore di percorso valido nei nomi di file passati a CreateFile () e così via. Tuttavia, ciò non significa sempre che è possibile utilizzare /ovunque in Windows poiché esiste una tradizione (ereditata da CPM) di utilizzare /come argomento principale al prompt dei comandi. Ma uno strumento di qualità starebbe attento a dividere i nomi dei file sia con i caratteri slash che backslash per evitare qualsiasi problema per le persone che riescono a usare /.
RBerteig,

5
@AmigableClarkKant, no puoi mescolare entrambi i separatori con lo stesso nome file.
RBerteig,

2
Se la tua piattaforma lo supporta char* fileName = basename(__FILE__); È sicuramente presente in Linux e OS X, ma non conosci Windows.
JeremyP,

14
Questo potrebbe essere abbreviato in strrchr("/" __FILE__, '/') + 1. Anticipando "/" a __FILE__, si garantisce a strrchr di trovare qualcosa, e quindi il condizionale?: Non è più necessario.
ɲeuroburɳ,

54

Ecco un consiglio se stai usando cmake. Da: http://public.kitware.com/pipermail/cmake/2013-January/053117.html

Sto copiando il suggerimento, quindi è tutto su questa pagina:

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

Se stai usando GNU make, non vedo alcun motivo per cui non potresti estenderlo ai tuoi makefile. Ad esempio, potresti avere una linea come questa:

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

dove si $(SOURCE_PREFIX)trova il prefisso che si desidera rimuovere.

Quindi utilizzare __FILENAME__al posto di __FILE__.


11
Temo che questo non funzioni per il FILE a cui si fa riferimento nel file di intestazione.
Baiyan Huang,

2
Concordo con @BaiyanHuang ma non sono sicuro che il commento sia chiaro. __FILE__non è un semplice simbolo del preprocessore, ma cambia nel file corrente viene spesso utilizzato per emettere il nome del file corrente (intestazione o modulo sorgente). Ciò __FILENAME__avrebbe solo la fonte più esterna
nhed

3
La soluzione di questa risposta non è portatile poiché utilizza escape della shell Bourne. Meglio usare CMake per implementarlo in modo pulito e portatile. Vedere la risposta della macro define_file_basename_for_sources qui.
Colin D Bennett,

3
La variante GNU Make di questo è CFLAGS + = -D__FILENAME __ = \ "$ (notdir $ <) \"
ctuffli

4
@firegurafiku Su piattaforme integrate, la dimensione del codice è spesso più un vincolo che una velocità. Se in ogni file sono presenti istruzioni di debug, è possibile eseguire rapidamente il raggruppamento delle dimensioni del file con stringhe a percorso completo. Al momento ho a che fare con una piattaforma del genere e riferimenti __FILE__dappertutto stanno esplodendo dallo spazio del codice.
mrtumnus,

26

Ho appena pensato a un'ottima soluzione che funziona sia con i file sorgente che con quelli di intestazione, è molto efficiente e funziona in fase di compilazione su tutte le piattaforme senza estensioni specifiche del compilatore. Questa soluzione conserva anche la relativa struttura di directory del progetto, in modo da sapere in quale cartella si trova il file e solo relativamente alla radice del progetto.

L'idea è di ottenere le dimensioni della directory di origine con lo strumento di compilazione e di aggiungerla alla __FILE__macro, rimuovendo completamente la directory e mostrando solo il nome del file a partire dalla directory di origine.

L'esempio seguente è implementato usando CMake, ma non c'è motivo per cui non funzionerebbe con nessun altro strumento di compilazione, perché il trucco è molto semplice.

Nel file CMakeLists.txt, definire una macro che abbia la lunghezza del percorso del progetto su 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}")

Sul tuo codice sorgente, definisci una __FILENAME__macro che aggiunge semplicemente la dimensione del percorso di origine alla __FILE__macro:

#define __FILENAME__ (__FILE__ + SOURCE_PATH_SIZE)

Quindi usa questa nuova macro anziché la __FILE__macro. Questo funziona perché il __FILE__percorso inizierà sempre con il percorso della directory dei sorgenti di CMake. Rimuovendolo dalla __FILE__stringa, il preprocessore si occuperà di specificare il nome file corretto e sarà tutto relativo alla radice del progetto CMake.

Se ti preoccupi delle prestazioni, questo è efficiente quanto l'utilizzo __FILE__, perché entrambi __FILE__e SOURCE_PATH_SIZEsono noti costanti di tempo di compilazione, quindi può essere ottimizzato dal compilatore.

L'unico posto in cui ciò fallirebbe se lo si utilizza su file generati e si trovano in una cartella di build off-source. Quindi probabilmente dovrai creare un'altra macro usando la CMAKE_BUILD_DIRvariabile anziché CMAKE_SOURCE_DIR.


4
All'inizio non l'ho capito. Ho codificato degli esempi e li ho confrontati con gcc e clang e funzionano. Provo anche ad aggiungere vari valori numerici letterali, e questo si comporta come mi aspettavo. Poi finalmente mi sono reso conto. __FILE__è un puntatore a una matrice di byte. Quindi aggiungere un valore letterale numerico è solo un'aggiunta al puntatore. Molto intelligente @RenatoUtsch
Daniel

Funziona solo se un file di origine si trova nella directory dell'elenco cmake. Se un file sorgente è esterno, allora si romperà, potrebbe avere accesso al di fuori di una stringa letterale. Quindi stai attento.
Andry,

In realtà non riduce la dimensione del codice. Immagino che l'intero percorso sia ancora compilato nel binario, solo il puntatore viene modificato.
Tarion,

Sia la __FILE__macro che le macro SOURCE_PATH_SIZE sono costanti note al momento della compilazione. Mi aspetto che i compilatori di ottimizzazione moderni siano in grado di rilevare che parte della stringa non viene utilizzata e semplicemente rimuoverla dal file binario. Ad ogni modo, non credo che questi pochi byte farebbero una differenza significativa nella dimensione binaria, quindi non me ne preoccuperei.
RenatoUtsch,

@RenatoUtsch Il progetto a cui sto lavorando ha una modifica che specifica solo il nome del file, ma ha lo svantaggio di dare anche il nome del file C all'intestazione. La modifica è stata apportata per ottenere build riproducibili. Quindi con gcc con -O2, la stringa sarebbe davvero ottimizzata e la build resa riproducibile?
Paul Stelian,

19

Compilare la soluzione del tempo puramente qui. Si basa sul fatto che sizeof()di una stringa letterale restituisce la sua lunghezza + 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__)

Sentiti libero di estendere la cascata dell'operatore condizionale al nome file massimo sensibile nel progetto. La lunghezza del percorso non ha importanza, purché controlli abbastanza lontano dalla fine della stringa.

Vedrò se riesco a ottenere una macro simile senza lunghezza hardcoded con ricorsione macro ...


3
Bella risposta. Devi usare almeno -O1per usare questo per essere in fase di compilazione.
Trauma digitale

1
Non è questo al contrario? Vuoi trovare l' ultima occorrenza di '/', il che significa che dovresti iniziare prima con il sizeof(s) > 2segno di spunta. Inoltre, questo non ha funzionato in fase di compilazione per me, in -Os. Le stringhe del percorso completo erano presenti nel file binario di output.
mrtumnus,

15

Almeno per gcc, il valore di __FILE__è il percorso del file come specificato nella riga di comando del compilatore . Se compili in file.cquesto modo:

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

la __FILE__si espanderà per "/full/path/to/file.c". Se invece lo fai:

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

quindi __FILE__si espanderà a solo "file.c".

Questo può o meno essere pratico.

Lo standard C non richiede questo comportamento. Tutto ciò che dice __FILE__è che si espande in "Il presunto nome del file sorgente corrente (una stringa di caratteri letterale)".

Un'alternativa è usare la #linedirettiva. Sostituisce il numero di riga corrente e, facoltativamente, il nome del file di origine. Se si desidera sovrascrivere il nome del file ma lasciare solo il numero di riga, utilizzare la __LINE__macro.

Ad esempio, puoi aggiungerlo nella parte superiore di file.c:

#line __LINE__ "file.c"

L'unico problema è che assegna il numero di riga specificato alla riga seguente e il primo argomento #linedeve essere una sequenza di cifre, quindi non puoi fare qualcosa di simile

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

Garantire che il nome del file nella #linedirettiva corrisponda al nome effettivo del file viene lasciato come esercizio.

Almeno per gcc, questo influenzerà anche il nome del file riportato nei messaggi diagnostici.


1
Keith Thompson è un'ottima soluzione, grazie. L'unico problema è che sembra che questa macro riduca il __LINE__valore di uno. Così __LINE__scritto in linea xgest valutato a x-1. Almeno con gcc 5.4.
elklepo,

1
@Klepak: hai ragione, e questo è un comportamento standard. La direttiva "fa sì che l'implementazione si comporti come se la seguente sequenza di linee di origine inizia con una linea di origine che ha un numero di linea come specificato dalla sequenza di cifre". E deve essere una sequenza di cifre , quindi non puoi usarla #line __LINE__-1, "file.c". Aggiornerò la mia risposta.
Keith Thompson,

1
Come sarebbe per altri compilatori come Clang, MSVC o Intel C Compiler?
Franklin Yu,

8

Un po 'in ritardo per la festa, ma per GCC dai un'occhiata -ffile-prefix-map=old=newall'opzione:

Quando si compilano file che risiedono nella directory precedente , registrare tutti i riferimenti ad essi nel risultato della compilazione come se fossero invece i file nella directory nuova . Specificare questa opzione equivale a specificare tutte le singole -f*-prefix-mapopzioni. Questo può essere usato per creare build riproducibili indipendenti dalla posizione. Vedi anche -fmacro-prefix-mape -fdebug-prefix-map.

Quindi per le mie build Jenkins aggiungerò -ffile-prefix-map=${WORKSPACE}/=/e un altro per rimuovere il prefisso di installazione del pacchetto di sviluppo locale.

NOTA Sfortunatamente l' -ffile-prefix-mapopzione è disponibile solo in GCC 8 in poi, così come -fmacro-prefix-map, credo , fa la __FILE__parte. Ad esempio, per GCC 5, abbiamo solo quelli -fdebug-prefix-mapche non sembrano (sembrano) influenzare __FILE__.


1
L'opzione -ffile-prefix-mapimplica davvero sia -fdebug-prefix-mape -fmacro-prefix-mapopzioni. Vedi anche i riferimenti su riproducible-builds.org/docs/build-path Il bug GCC che tiene traccia -fmacro-prefix-maped -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__)]);
    }

Il codice genera un offset di tempo di compilazione quando:

  • gcc: almeno gcc6.1 + -O1
  • msvc: inserisci il risultato nella variabile constexpr:

      constexpr auto file = &__FILE__[get_file_name_offset(__FILE__)];
      printf("%s\n", file);
  • clang: persiste nella non valutazione del tempo di compilazione

C'è un trucco per forzare tutti e 3 i compilatori a compilare la valutazione del tempo anche nella configurazione di debug con ottimizzazione disabilitata:

    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


Amico, è bellissimo e funziona, grazie! Non ho idea del perché questa risposta sia così sottovalutata.
Roman

5

In VC, durante l'utilizzo /FC, si __FILE__espande al percorso completo, senza l' /FCopzione __FILE__espande il nome del file. ref: qui


4

Non esiste un modo per compilare il tempo per farlo. Ovviamente puoi farlo in fase di runtime usando il runtime C, come hanno dimostrato alcune delle altre risposte, ma in fase di compilazione, quando il pre-procuratore entra, sei sfortunato.


1
la strrchrrisposta potrebbe essere plausibilmente calcolata in fase di compilazione, sebbene ovviamente non sia ancora dal preprocessore. Non so se gcc lo faccia davvero, non ho controllato, ma sono abbastanza sicuro che calcola strleni letterali delle stringhe al momento della compilazione.
Steve Jessop,

@Steve - forse, ma questa è una grande dipendenza dal comportamento specifico del compilatore.
Sean,

Non penso che sia una grande dipendenza, perché dubito fortemente che questo codice sia critico per le prestazioni. E se lo è, spostalo fuori dal giro. Nei casi in cui questo è un grosso problema, poiché hai assolutamente bisogno di una stringa letterale contenente solo il nome base, potresti forse calcolare la stringa giusta al momento della compilazione eseguendo alcuni script sul sorgente.
Steve Jessop,

5
Potrebbe non essere critico per le prestazioni, ma può essere facilmente considerato critico per la privacy. Non c'è davvero un buon motivo per rivelare le mie pratiche organizzative per cliente in stringhe congelate in un file EXE rilasciato. Peggio ancora, per le applicazioni create per conto di un cliente, quelle stringhe potrebbero rivelare cose che il mio cliente preferirebbe non fare, come non essere l'autore del proprio prodotto. Poiché __FILE__viene invocato implicitamente da assert(), questa perdita può verificarsi senza alcun altro atto palese.
RBerteig,

@RBerteig il nome di base di __FILE__se stesso può anche rivelare cose che il cliente potrebbe preferire non fare, quindi l'utilizzo __FILE__ovunque - indipendentemente dal fatto che contenga il nome di percorso assoluto completo o solo il nome di base - ha gli stessi problemi che hai sottolineato. In questa situazione, tutti gli output dovranno essere controllati e un'API speciale dovrebbe essere introdotta per l'output ai clienti. Il resto dell'output dovrebbe essere scaricato su / dev / NULL o stdout e stderr dovrebbe essere chiuso. :-)
Tchen

4

Usa la funzione basename () o, se sei su Windows, _splitpath () .

#include <libgen.h>

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

Prova anche man 3 basenamein una shell.


2
@mahmood: char file_copy[] = __FILE__; const char *filename = basename(__FILE__);. Il motivo della copia è che basename può modificare la stringa di input. Devi anche fare attenzione che il puntatore del risultato è valido solo fino a quando non basenameviene richiamato. Ciò significa che non è thread-safe.
Steve Jessop,

@SteveJessop, ah ho dimenticato. Vero.
Prof. Falken,

1
@Amigable: a dire il vero, ho il sospetto che basenamein realtà non modificherà la stringa di input che risulta da __FILE__, perché la stringa di input non ha un /alla fine e quindi non c'è bisogno di modifiche. Quindi potresti cavartela, ma immagino che la prima volta che qualcuno vede basename, dovrebbero vederlo con tutte le restrizioni.
Steve Jessop,

@SteveJessop La pagina man di BSd per basename () menziona che la versione legacy di basename () accetta un carattere const * e non modifica la stringa. La pagina man di linux non menziona nulla di const ma menziona che può restituire una parte della stringa dell'argomento. Quindi, è meglio essere prudenti con basename ().
Prof. Falken,

@SteveJessop, hehe, solo ora dopo aver esaminato attentamente il tuo commento, dopo quattro anni, mi rendo conto che /alla fine della stringa significa che basename potrebbe avere una buona ragione per modificare il suo argomento.
Prof. Falken,

4

Se si utilizza CMAKEcon il compilatore GNU, la globaldefinizione funziona correttamente:

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

4

Ho usato la stessa soluzione con la risposta di @Patrick per anni.

Ha un piccolo problema quando il percorso completo contiene un collegamento simbolico.

Soluzione migliore.

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

Perché dovrebbe usare questo?

  • -Wno-builtin-macro-redefinedper silenziare gli avvisi del compilatore per la ridefinizione della __FILE__macro.

    Per quei compilatori che non supportano questo, fare riferimento al modo Robusto di seguito.

  • Spogliare il percorso del progetto dal percorso del file è il vero requisito. Non ti piacerebbe perdere tempo per scoprire dove si trova un header.hfile src/foo/header.ho src/bar/header.h.

  • Dovremmo eliminare la __FILE__macrocmake file di configurazione.

    Questa macro viene utilizzata nella maggior parte dei codici esistenti. Basta ridefinire può liberarti.

    I compilatori come gccpredefiniscono questa macro dagli argomenti della riga di comando. E il percorso completo è scritto in makefiles generato da cmake.

  • CMAKE_*_FLAGSÈ richiesto il codice di accesso .

    Esistono alcuni comandi per aggiungere opzioni o definizioni del compilatore in alcune versioni più recenti, come add_definitions()e add_compile_definitions(). Questi comandi analizzeranno le funzioni make come substprima di applicarle ai file sorgente. Non è quello che vogliamo.

Modo robusto per -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)

Ricorda di rimuovere questa opzione del compilatore dalla set(*_FLAGS ... -D__FILE__=...)riga.


Questo non funziona per i contenuti provenienti dai file include.
Chqrlie,

puoi pubblicare del codice? Un caso comune è quello di impostare variabili nell'ambito locale e utilizzarlo in un altro.
Levi.G

Ad esempio, se si utilizza __FILE__con la propria definizione per produrre una diagnostica in una funzione incorporata definita in un file di intestazione, la diagnostica di runtime segnalerà il nome del file passato al compilatore anziché il nome del file include, mentre il numero di riga verrebbe fare riferimento al file include.
Chqrlie,

sì, è progettato per essere quello, per l'uso più comune è #define LOG(fmt, args...) printf("%s " fmt, __FILE__, ##args). quando si utilizza la LOG()macro, non si desidera davvero vedere log.hnei messaggi. dopotutto, la __FILE__macro viene espansa in ogni file C / Cpp (unità di compilazione) anziché nei file inclusi.
Levi.G,

3

Una leggera variazione su ciò che @ red1ynx proposto sarebbe creare la seguente macro:

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

In ciascuno dei tuoi file .c (pp) aggiungi:

SET_THIS_FILE_NAME();

Quindi puoi fare riferimento THIS_FILE_NAMEinvece di __FILE__:

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

Ciò significa che la costruzione viene eseguita una volta per file .c (pp) anziché ogni volta che viene fatto riferimento alla macro.

È limitato all'uso solo da file .c (pp) e sarebbe inutilizzabile da file di intestazione.


3

Ho fatto una macro __FILENAME__ che evita di tagliare ogni volta il percorso completo. Il problema è mantenere il nome del file risultante in una variabile cpp-local.

Può essere fatto facilmente definendo una variabile globale statica nel file .h . Questa definizione fornisce variabili separate e indipendenti in ciascun file .cpp che include .h . Per essere a prova di multithreading vale la pena rendere anche le variabili thread locali (TLS).

Una variabile memorizza il nome del file (ridotto). Un altro contiene il valore non tagliato che ha __FILE__dato. Il file h:

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

La macro stessa chiama il metodo con tutta la logica:

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

E la funzione è implementata in questo modo:

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 () può essere implementato in diversi modi, ma il veloce e semplice sembra essere con 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

spero solo di migliorare un po 'la macro FILE:

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

questo cattura / e \, come richiesto da Czarek Tomczak, e funziona benissimo nel mio ambiente misto.


6
Definire una macro denominata FILEè una pessima idea se includi <stdio.h>.
Keith Thompson,

buono a sapersi. volevo solo mostrare a Czarek la mia soluzione \\ /, quindi non mi preoccupavo degli schemi di denominazione.
alexander golks,

2

Provare

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

dopo le istruzioni include nel file sorgente e aggiungi

#pragma pop_macro("__FILE__")

alla fine del file sorgente.


2
push_macroe pop_macronon sono standard. (gcc li supporta per la compatibilità con i compilatori di Microsoft Windows.) In ogni caso, non ha senso spingere e schioccare la definizione di __FILE__; il valore ripristinato non verrà comunque utilizzato dopo la fine del file di origine. Un modo più pulito per modificare il valore di __FILE__è#line __LINE__ "foobar.c"
Keith Thompson,

1
E questo provoca un errore interno nel preprocessore di gcc. gcc.gnu.org/bugzilla/show_bug.cgi?id=69665
Keith Thompson,

1

Ecco una funzione portatile che funziona sia per Linux (percorso '/') che per Windows (mix di '\' e '/').
Compila con gcc, clang e 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;
}

Uscita standard:

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

Ecco la soluzione che utilizza il calcolo in fase di compilazione:

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

Se sei finito in questa pagina alla ricerca di un modo per rimuovere il percorso di origine assoluto che punta a una brutta posizione di costruzione dal binario che stai spedendo, di seguito potrebbe soddisfare le tue esigenze.

Sebbene questo non produca esattamente la risposta che l'autore ha espresso il suo desiderio poiché presume l'uso di CMake , si avvicina piuttosto. È un peccato che non sia stato menzionato prima da nessuno in quanto mi avrebbe risparmiato un sacco di tempo.

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

L'impostazione della variabile sopra su ONgenererà il comando build nel formato:

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

Di conseguenza, la __FILE__macro verrà risolta in../../src/path/to/source.c

Documentazione CMake

Fai attenzione all'avvertimento nella pagina della documentazione:

Usa percorsi relativi (potrebbe non funzionare!).

Non è garantito per funzionare in tutti i casi, ma ha funzionato nel mio - CMake 3.13 + gcc 4.5


La documentazione di CMake 3.4+ afferma che "Questa variabile non ha alcun effetto. L'effetto parzialmente implementato che aveva nelle versioni precedenti è stato rimosso in CMake 3.4". Se ha funzionato per te con 3.13, c'è un'altra ragione per questo.
mike.dld,

0

Dal momento che si sta utilizzando GCC, è possibile usufruire di

__BASE_FILE__Questa macro si espande nel nome del file di input principale, sotto forma di costante di stringa C. Questo è il file di origine che è stato specificato nella riga di comando del preprocessore o del compilatore C.

e quindi controllare come si desidera visualizzare il nome file modificando la rappresentazione del file di origine (percorso completo / percorso relativo / nome base) al momento della compilazione.


6
non fa differenza. Ho usato __FILE__e __BASE_FILE__comunque entrambi mostrano il percorso completo del file
mahmood

come invochi il compilatore?
ziu,

2
Quindi scommetto che SCONS sta chiamando gcc in questo modo gcc /absolute/path/to/file.c. Se trovi un modo per cambiare questo comportamento (aprendo un'altra domanda su SO, lol?), Non è necessario modificare la stringa in fase di esecuzione
ziu

17
Questa risposta è sbagliata al 100%. __BASE_FILE__(come dicono i documenti, anche se in modo poco chiaro) produce il nome del file specificato nella riga di comando , ad esempio test.co in /tmp/test.cbase a come hai invocato il compilatore. È esattamente la stessa cosa __FILE__, tranne se ci si trova all'interno di un file di intestazione, nel qual caso __FILE__produce il nome del file corrente (ad es. foo.h) Mentre __BASE_FILE__continua a produrre test.c.
Quuxplusone,

0

Ecco una soluzione che funziona per ambienti che non dispongono della libreria di stringhe (kernel Linux, sistemi integrati, ecc.):

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

Ora basta usare FILENAMEinvece di __FILENAME__. Sì, è ancora una cosa runtime ma funziona.


Potrebbe essere necessario controllare sia '/' che '\', a seconda della piattaforma.
Tecnophile,

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

Una risposta breve e funzionante sia per Windows che per * nix:

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

Perché hai bisogno std::max<const char*>invece di solo std::max?
Chqrlie,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.