web-dev-qa-db-fra.com

La macro __FILE__ indique le chemin complet

Le MACRO __FILE__ prédéfini standard disponible en C indique le chemin complet du fichier. Est-il possible de raccourcir le chemin? Je veux dire au lieu de

/full/path/to/file.c

Je vois

to/file.c

ou

file.c
144
mahmood

Essayer

#include <string.h>

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

Pour Windows, utilisez "\\" au lieu de "/".

145
red1ynx

Voici un conseil si vous utilisez cmake. De: http://public.kitware.com/pipermail/cmake/2013-January/053117.html

Je copie le conseil pour que tout se trouve sur cette page:

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

Si vous utilisez GNU make, je ne vois aucune raison pour que vous ne puissiez pas l'étendre à vos propres fichiers Makefiles. Par exemple, vous pourriez avoir une ligne comme celle-ci:

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

$(SOURCE_PREFIX) est le préfixe que vous souhaitez supprimer.

Ensuite, utilisez __FILENAME__ à la place de __FILE__.

50
Patrick

Je viens de penser à une excellente solution à ce problème, qui fonctionne à la fois avec les fichiers source et en-tête, est très efficace et fonctionne à la compilation dans toutes les plates-formes sans extensions spécifiques au compilateur. Cette solution préserve également la structure de répertoires relative de votre projet afin que vous sachiez dans quel dossier se trouve le fichier et uniquement par rapport à la racine de votre projet.

L'idée est d'obtenir la taille du répertoire source avec votre outil de génération et de simplement l'ajouter à la macro __FILE__, en supprimant entièrement le répertoire et en affichant uniquement le nom du fichier commençant par votre répertoire source.

L'exemple suivant est implémenté à l'aide de CMake, mais rien n'empêche que cela ne fonctionne pas avec d'autres outils de construction, car l'astuce est très simple.

Sur le fichier CMakeLists.txt, définissez une macro qui a la longueur du chemin d'accès à votre projet sur 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}")

Sur votre code source, définissez une macro __FILENAME__ qui ajoute simplement la taille du chemin source à la macro __FILE__:

#define __FILENAME__ (__FILE__ + SOURCE_PATH_SIZE)

Ensuite, utilisez simplement cette nouvelle macro à la place de la macro __FILE__. Cela fonctionne parce que le chemin __FILE__ commencera toujours par le chemin de votre répertoire source CMake. En le supprimant de la chaîne __FILE__, le préprocesseur se chargera de spécifier le nom de fichier correct, qui sera relatif à la racine de votre projet CMake.

Si vous vous souciez de la performance, cela est aussi efficace que d'utiliser __FILE__, car __FILE__ et SOURCE_PATH_SIZE sont des constantes de compilation connues, de sorte que le compilateur peut les optimiser.

Le seul endroit où cela échouerait serait si vous l'utilisiez sur des fichiers générés et qu'ils se trouvaient dans un dossier de génération hors source. Ensuite, vous devrez probablement créer une autre macro en utilisant la variable CMAKE_BUILD_DIR à la place de CMAKE_SOURCE_DIR.

21
RenatoUtsch

Compilez purement la solution temporelle ici. Il est basé sur le fait que sizeof() d'un littéral de chaîne renvoie sa longueur + 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__)

N'hésitez pas à étendre la cascade d'opérateurs conditionnelle au nom de fichier sensible maximum dans le projet. La longueur du chemin n'a pas d'importance, tant que vous vérifiez suffisamment loin de la fin de la chaîne.

Je verrai si je peux obtenir une macro similaire sans longueur codée en dur avec la récursivité des macros ...

13
Seva Alekseyev

Au moins pour gcc, la valeur de __FILE__ est le chemin du fichier tel que spécifié sur la ligne de commande du compilateur. Si vous compilez file.c comme ceci:

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

le __FILE__ sera étendu à "/full/path/to/file.c". Si vous faites plutôt ceci:

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

alors __FILE__ sera étendu à juste "file.c".

Cela peut ou peut ne pas être pratique.

La norme C ne nécessite pas ce comportement. Tout ce qu’il est dit sur __FILE__, c’est qu’il se développe en "Le nom présumé du fichier source actuel (un littéral de chaîne de caractères)".

Une alternative consiste à utiliser la directive #line. Il remplace le numéro de ligne actuel et éventuellement le nom du fichier source. Si vous souhaitez remplacer le nom de fichier tout en laissant le numéro de ligne seul, utilisez la macro __LINE__.

Par exemple, vous pouvez ajouter ceci près du sommet de file.c:

#line __LINE__ "file.c"

Le seul problème avec ceci est qu'il attribue le numéro de ligne spécifié à la ligne suivante, et le premier argument de #line doit être un séquence de chiffres so vous ne pouvez pas faire quelque chose comme

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

S'assurer que le nom du fichier dans la directive #line correspond au nom réel du fichier est laissé comme exercice.

Au moins pour gcc, cela affectera également le nom de fichier indiqué dans les messages de diagnostic.

11
Keith Thompson

Un peu tard pour la soirée, mais pour GCC, jetez un coup d'œil à l'option -ffile-prefix-map=old=new:

Lors de la compilation de fichiers résidant dans le répertoire old , enregistrez toute référence à ces fichiers dans le résultat de la compilation comme si les fichiers résidaient dans le répertoire new à la place. Spécifier cette option revient à spécifier toutes les options individuelles -f*-prefix-map. Cela peut être utilisé pour créer des versions reproductibles indépendantes de l'emplacement. Voir aussi -fmacro-prefix-map et -fdebug-prefix-map.

Donc, pour mon Jenkins builds, je vais ajouter -ffile-prefix-map=${WORKSPACE}/=/, et un autre pour supprimer le préfixe d'installation du paquet dev local.

NOTE Malheureusement, l'option -ffile-prefix-map n'est disponible qu'à partir de GCC 8, de même que -fmacro-prefix-map qui, , je pense , fait la partie __FILE__. Pour, disons, GCC 5, nous n’avons que -fdebug-prefix-map qui ne semble (apparemment pas) affecter __FILE__.

5
simon.watts

Si vous utilisez CMAKE avec GNU compilateur, cette global define fonctionne correctement:

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

Utilisez la fonction basename () , ou, si vous êtes sous Windows, _ splitpath () .

#include <libgen.h>

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

Essayez également man 3 basename dans un shell.

4
Prof. Falken

dans vs, quand avec/FC, FICHIER est égal au chemin complet, sans/FC FICHIER est égal au nom du fichier. ref ici

4
user7382523

Il n'y a pas de moyen de compilation pour le faire. Évidemment, vous pouvez le faire au moment de l'exécution en utilisant le runtime C, comme le montrent certaines des réponses, mais au moment de la compilation, lorsque le pré-processeur se déclenche, vous n'avez pas de chance.

4
Sean

Une légère variation sur ce que @ red1ynx a proposé de créer créer la macro suivante:

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

Dans chacun de vos fichiers .c (pp), ajoutez:

SET_THIS_FILE_NAME();

Ensuite, vous pouvez vous référer à THIS_FILE_NAME au lieu de __FILE__:

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

Cela signifie que la construction est effectuée une fois par fichier .c (pp) au lieu de chaque fois que la macro est référencée.

Il est limité à utiliser uniquement à partir de fichiers .c (pp) et serait inutilisable à partir de fichiers en-tête.

3
hmjd
  • C++ 11
  • msvc2015u3, gcc5.4, clang3.8.0

    template <size_t S>
    inline constexpr size_t get_file_name_offset(const char (& 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 <size_t S>
    inline constexpr size_t get_file_name_offset(const wchar_t (& str)[S], size_t i = S - 1)
    {
        return (str[i] == L'/' || str[i] == L'\\') ? i + 1 : (i > 0 ? get_file_name_offset(str, i - 1) : 0);
    }
    
    template <size_t S>
    inline constexpr size_t get_file_name_offset(const char16_t (& str)[S], size_t i = S - 1)
    {
        return (str[i] == u'/' || str[i] == u'\\') ? i + 1 : (i > 0 ? get_file_name_offset(str, i - 1) : 0);
    }
    
    template <size_t S>
    inline constexpr size_t get_file_name_offset(const char32_t (& str)[S], size_t i = S - 1)
    {
        return (str[i] == U'/' || str[i] == U'\\') ? 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__)]);
    }
    

Le code génère un décalage de temps de compilation lorsque:

  • gcc: au moins gcc6.1 + -O1
  • msvc: mettre le résultat dans la variable constexpr:

      constexpr auto file = &__FILE__[get_file_name_offset(__FILE__)];
      printf("%s\n", file);
    
  • clang: persiste sur l'évaluation du temps de compilation

Il y a une astuce pour forcer les 3 compilateurs à faire une évaluation du temps de compilation même dans la configuration de débogage avec optimisation désactivée:

    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/P1y3ZI

3
Andry

J'ai fait une macro __FILENAME__ qui évite de couper le chemin complet à chaque fois. Le problème est de conserver le nom du fichier résultant dans une variable cpp-local.

Cela peut être facilement fait en définissant une variable globale statique dans le fichier . H. Cette définition donne des variables séparées et indépendantes dans chaque fichier . Cpp qui inclut le fichier . H. Afin d’être à l’épreuve du multithreading, il est intéressant de faire en sorte que la ou les variables soient également threades en local (TLS).

Une variable stocke le nom de fichier (rétréci). Un autre contient la valeur non coupée que __FILE__ a donnée. Le fichier h:

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

La macro elle-même appelle la méthode avec toute la logique:

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

Et la fonction est implémentée de cette façon:

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 () peut être implémenté de différentes manières, mais le plus simple et rapide semble être avec 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;
}
3
Kunis

Essayer

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

après les instructions include dans votre fichier source et ajoutez

#pragma pop_macro("__FILE__")

à la fin de votre fichier source.

2
Alain Totouom

j'espère juste améliorer un peu la macro FILE:

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

cette capture/et \, comme l'a demandé Czarek Tomczak, et cela fonctionne très bien dans mon environnement mixte.

2
alexander golks

Si vous vous retrouvez sur cette page à la recherche d'un moyen de supprimer du chemin binaire le chemin source absolu qui pointe vers un emplacement de construction moche, vous trouverez ci-dessous des solutions qui répondent à vos besoins.

Bien que cela ne produise pas exactement la réponse que l'auteur a exprimée son souhait puisqu'il suppose l'utilisation de CMake , il s'en rapproche beaucoup. Dommage que cela n'ait été mentionné auparavant par personne, car cela m'aurait permis de gagner beaucoup de temps.

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

Définir la variable ci-dessus sur ON générera une commande de construction au format suivant:

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

En conséquence, la macro __FILE__ sera résolue en ../../src/path/to/source.c

documentation de CMake

Attention toutefois à l'avertissement sur la page de documentation:

Utilisez des chemins relatifs (peut ne pas fonctionner!).

Il n’est pas garanti de fonctionner dans tous les cas, mais a travaillé dans le mien - CMake 3.13 + gcc 4.5

1
IvanR

Voici une fonction portable qui fonctionne à la fois sous Linux (chemin '/') et Windows (mélange de '\' et '/').
Compile avec gcc, clang et 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;
}

Sortie 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
Sasha Zezulinsky

J'ai utilisé la même solution avec @Patrick's answer pendant des années.

Il y a un petit problème lorsque le chemin complet contient symbol-link.

Meilleure solution.

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

Pourquoi devrait utiliser cela?

  • -Wno-builtin-macro-redefined pour désactiver les avertissements du compilateur pour redéfinir la macro __FILE__.

    Pour les compilateurs ne supportant pas cela, référez-vous à la méthode Robust ci-dessous.

  • Supprimer le chemin du projet du chemin du fichier correspond à votre exigence real . Vous n'aimerez pas perdre votre temps à découvrir où se trouve un fichier header.h, src/foo/header.h ou src/bar/header.h.

  • Nous devrions supprimer la macro __FILE__ dans le fichier de configuration cmake.

    Cette macro est utilisée dans la plupart des codes existants. Redéfinissez-le simplement, il peut vous libérer.

    Des compilateurs comme gcc prédéfinissent cette macro à partir des arguments de la ligne de commande. Et le chemin complet est écrit dans makefiles générée par cmake.

  • Le code brut dans CMAKE_*_FLAGS est requis.

    Certaines commandes permettent d'ajouter des options ou des définitions du compilateur dans une version plus récente, telles que add_definitions() et add_compile_definitions(). Ces commandes analyseront les fonctions make telles que subst avant de les appliquer aux fichiers source. Ce n'est pas ce que nous voulons.

Manière robuste pour -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)

N'oubliez pas de supprimer cette option du compilateur de la ligne set(*_FLAGS ... -D__FILE__=...).

0
Levi.G
#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
Dimfred

Voici une solution qui fonctionne pour les environnements ne disposant pas de la bibliothèque de chaînes (noyau Linux, systèmes intégrés, etc.):

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

Maintenant, utilisez simplement FILENAME au lieu de __FILENAME__. Oui, c'est toujours une chose d'exécution mais ça fonctionne.

0
beavis9k

Puisque vous utilisez GCC, vous pouvez profiter de

__BASE_FILE__ Cette macro se développe pour donner le nom du fichier d'entrée principal, sous la forme d'une constante de chaîne C. Il s'agit du fichier source spécifié sur la ligne de commande du préprocesseur ou du compilateur C

puis contrôlez le mode d'affichage du nom de fichier en modifiant la représentation du fichier source (chemin complet/chemin relatif/nom de base) au moment de la compilation.

0
ziu

Voici la solution qui utilise le calcul au moment de la compilation:

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__);
0
Dmitry Gordon