Fichier macro affiche le chemin complet

La MACRO prédéfinie standard__ FILE _ _ disponible en C affiche le chemin d'accès complet au fichier. Est-il un moyen de court-circuiter le chemin? Je veux dire au lieu de

/full/path/to/file.c

Je vois

to/file.c

Ou

file.c
131
demandé sur Ajay 2011-12-13 14:57:27

17 réponses

Essayez

#include <string.h>

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

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

124
répondu red1ynx 2015-04-22 20:00:51

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

Je copie l'astuce donc tout est 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 étendre cela à vos propres makefiles. Par exemple, vous pourriez avoir une ligne comme ceci:

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__.

46
répondu Patrick 2013-05-20 21:40:20

Je viens de penser à une excellente solution qui fonctionne avec les fichiers source et d'en-tête, est très efficace et fonctionne au moment de la compilation sur toutes les plates-formes sans extensions spécifiques au compilateur. Cette solution préserve également la structure de répertoire relative de votre projet, de sorte que vous savez dans quel dossier se trouve le fichier, et seulement par rapport à la racine de votre projet.

L'idée est d'obtenir la taille du répertoire source avec votre outil de construction et de l'ajouter à la macro __FILE__ , supprimer entièrement le répertoire et afficher uniquement le nom du fichier à partir de votre répertoire source.

L'exemple suivant est implémenté en utilisant CMake, mais il n'y a aucune raison que cela ne fonctionne pas avec d'autres outils de construction, car l'astuce est très simple.

Sur les CMakeLists.fichier 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 au __FILE__ macro:

#define __FILENAME__ (__FILE__ + SOURCE_PATH_SIZE)

Ensuite, utilisez simplement cette nouvelle macro au lieu de la macro __FILE__. Cela fonctionne car le chemin __FILE__ commencera toujours par le chemin d'accès à votre répertoire source CMake. En le supprimant de la chaîne __FILE__, le préprocesseur prendra soin de spécifier le nom de fichier correct et tout sera relatif à la racine de votre projet CMake.

Si vous vous souciez de la performance, c'est aussi efficace que d'utiliser __FILE__, car __FILE__ et SOURCE_PATH_SIZE sont des constantes de compilation connues, donc peut être optimisé par le compilateur.

Le seul endroit où cela échouerait est si vous l'utilisez sur les fichiers générés et qu'ils se trouvent dans un dossier de construction hors source. Ensuite, vous devrez probablement créer une autre macro en utilisant la variable CMAKE_BUILD_DIR au lieu de CMAKE_SOURCE_DIR.

10
répondu RenatoUtsch 2017-09-20 20:06:06

Purement solution de compilation ici. Il est basé sur le fait que sizeof() d'une chaîne littérale 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 de l'opérateur conditionnel au nom de fichier raisonnable maximum dans le projet. La longueur du chemin n'a pas d'importance, tant que vous vérifiez assez loin de la fin de la chaîne.

Je vais voir si je peux obtenir une macro similaire sans longueur codée en dur avec la récursivité de la macro...

9
répondu Seva Alekseyev 2018-02-02 17:24:33

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

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

Le {[3] } s'étendra à "/full/path/to/file.c". Si vous faites plutôt ceci:

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

Alors {[3] } s'étendra à seulement "file.c".

Cela peut ou peut ne pas être pratique.

La norme C n'exige pas ce comportement. Tout ce qu'il dit à propos de __FILE__ est qu'il se développe en "le nom présumé du fichier source actuel (un caractère chaîne de caractères littérale)".

Une autre solution consiste à utiliser la directive #line. Il remplace le numéro de ligne actuel et éventuellement le nom du fichier source. Si vous voulez remplacer le nom du fichier mais laisser le numéro de ligne seul, Utilisez la macro __LINE__.

Par exemple, vous pouvez ajouter ceci en haut de file.c:

#line __LINE__ "file.c"

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

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

8
répondu Keith Thompson 2015-06-10 18:44:58

Il n'y a pas de moyen de compilation pour le faire. Évidemment, vous pouvez le faire à l'exécution en utilisant le runtime C, comme certaines des autres réponses l'ont démontré, mais au moment de la compilation, lorsque le pré-procesor entre en jeu, vous n'avez pas de chance.

4
répondu Sean 2011-12-13 11:29:32

Puisque vous utilisez GCC, vous pouvez profitez de

__BASE_FILE__ cette macro se développe au nom du fichier d'entrée principal, sous la forme d'une constante de chaîne C. C'est le fichier source qui était spécifié sur la ligne de commande du préprocesseur ou compilateur C

Puis contrôlez comment vous souhaitez afficher le nom de fichier en modifiant la représentation du fichier source (chemin complet/chemin relatif/nom de base) au moment de la compilation.

4
répondu ziu 2011-12-13 11:44:21

Utilisez la fonctionbasename () 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
répondu Prof. Falken 2011-12-13 11:54:33

Une légère variation sur ce que @ red1ynx proposait de 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 .C (pp) fichiers ajouter:

SET_THIS_FILE_NAME();

, Alors 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 .C (pp) au lieu de chaque fois que la macro est référencée.

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

3
répondu hmjd 2011-12-13 11:39:58

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

, Il peut être facilement fait en définissant une variable globale statique dans .h fichier. Cette définition donne des variables distinctes et indépendantes dans chaque .rpc fichier qui contient la .h . Afin d'être un multithreading-proof, il vaut la peine de faire en sorte que les variables soient également thread local(TLS).

Une variable stocke le nom du fichier (rétrécir). 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;
}

Le removePath () peut être implémenté de différentes manières, mais le rapide et simple 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
répondu Kunis 2013-07-18 14:49:46

J'espère juste améliorer un peu la macro de fichier:

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

Cela attrape / et \, comme czarek Tomczak l'a demandé, et cela fonctionne très bien dans mon environnement mixte.

2
répondu alexander golks 2013-05-06 13:49:42

Si vous utilisez CMAKE avec le compilateur GNU ce global définir fonctionne:

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__MY_FILE__='\"$(notdir $(abspath $<))\"'")
2
répondu GerneralAmerica 2017-02-28 11:09:50

Essayez

#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.

1
répondu Alain Totouom 2015-06-12 09:42:57

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

1
répondu user7382523 2017-01-06 05:33:05

Voici une solution qui fonctionne pour les environnements qui n'ont pas la bibliothèque de chaînes (noyau Linux, Systèmes Embarqué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 marche.

0
répondu beavis9k 2017-11-17 19:41:18

Voici une fonction portable qui fonctionne à la fois pour 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: 
0
répondu Sasha Zezulinsky 2018-03-13 13:27:54

Voici la solution qui utilise le calcul à 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
répondu Dmitry Gordon 2018-07-24 16:17:17