web-dev-qa-db-fra.com

Comment extraire le nom du fichier source sans chemin ni suffixe au moment de la compilation?

Utiliser à la fois gcc avec -std = c11 et g ++ avec -std = c ++ 14.

Par exemple. pour un fichier nommé src/dir/Hello.cxx, il devrait s'étendre à quelque chose comme par exemple:

const char basename[] = "Hello";

ou

const char basename[] = getStaticBasename(__FILE__);

comme où getStaticBasename() est une macro (pour les sources C) ou une fonction constexpr (pour les sources C++) qui se traduit par "Bonjour".

Je dois éviter de diviser la chaîne de __FILE__ Au moment de l'exécution, car le chemin et le suffixe ne doivent en aucun cas être compilés dans l'exécutable.

La solution doit être sans dépendances à d'énormes bibliothèques telles que boost.

Comme je n'ai pas de makefile, des solutions comme this ne peuvent pas être utilisées dans mon cas.

Avait-on une solution à cela?

Modifier 2015-07-02:

  • Je n'ai aucune influence sur la façon dont le compilateur et l'éditeur de liens ont été invoqués (parfois via makefile, parfois à partir de la ligne de commande, ou certains IDE (marque gérée par Eclipse CDT, Crossworks, Xcode et cetera). être en code uniquement.
  • Mon cas d'utilisation est de fournir une sorte d '"identificateur de région générique" pour une solution de journalisation à faible encombrement. Le code d'application (qui utilise mon enregistreur) ne devrait que #include <Joe/Logger.h> Et dans les appels ultérieurs à par exemple LOG_DEBUG(...) Je vais implicitement utiliser "l'identifiant générique de région" généré automatiquement.
  • Ma solution actuelle est que le code d'application doit déclarer une JOE_LOG_FILE_REGION(Hello); (après #include <Joe/Logger.h>) Avant de pouvoir placer LOG_DEBUG(...) dans son code.
27
Joe
  • la fonction intégrée de gcc peut obtenir le nom de fichier d'un chemin complet au moment de la compilation.

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

ou

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

  • c ++ 11 constexpr peut également le faire au moment de la compilation.

exemple:

#include <stdio.h>

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

constexpr bool str_slant(const char *str) {
    return *str == '/' ? true : (*str ? str_slant(str + 1) : false);
}

constexpr const char* r_slant(const char* str) {
    return *str == '/' ? (str + 1) : r_slant(str - 1);
}
constexpr const char* file_name(const char* str) {
    return str_slant(str) ? r_slant(str_end(str)) : str;
}

int main() {
    constexpr const char *const_file = file_name(__FILE__);
    puts(const_file);
    return 0;
}

le nom du fichier source est foo/foo1/foo2/foo3/foo4.cpp

utilisation g++ -o foo.exe foo/foo1/foo2/foo3/foo4.cpp -std=c++11 --save-temps pour compiler ce fichier.

vous pouvez le voir.

.file   "foo4.cpp"
        .section        .rodata
.LC0:
        .string "foo/foo1/foo2/foo3/foo4.cpp"
        .text
        .globl  main
        .type   main, @function
main:
.LFB4:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        subq    $16, %rsp
        movq    $.LC0+19, -8(%rbp) 
        movl    $.LC0+19, %edi
        call    puts
        movl    $0, %eax
        leave
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE4:
        .size   main, .-main
        .ident  "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4"
        .section        .note.GNU-stack,"",@progbits

movl $.LC0+19, %edi .LC0 + 19 est l'adresse de la chaîne de nom de fichier sans chemin ni suffixe

19
pexeer

Si vous exécutez gcc à partir du dossier où se trouve le fichier source, vous obtiendrez un __FILE__ Différent que si vous passez un chemin absolu (c'est-à-dire remis à gcc via un IDE).

  • gcc test.c -otest.exe Me donne __FILE__ En tant que test.c.
  • gcc c:\tmp\test.c -otest.exe Me donne __FILE__ En tant que c:\tmp\test.c.

Peut-être qu'appeler gcc à partir du chemin où se trouve la source est suffisant comme solution de contournement?


MODIFIER

Voici un hack "sale" mais sûr qui supprime l'extension du fichier au moment de la compilation. Pas vraiment quelque chose que je recommanderais, mais c'était amusant d'écrire :) Alors, prenez-le pour ce qu'il vaut. Cela ne fonctionne qu'en C.

#include <stdio.h>

#define EXT_LENGTH (sizeof(".c") - 1) // -1 null term

typedef union
{
  char filename_no_nul [sizeof(__FILE__)-EXT_LENGTH-1]; // -1 null term
  char filename_nul    [sizeof(__FILE__)-EXT_LENGTH];
} remove_ext_t;

int main (void)
{
  const remove_ext_t file = { __FILE__ };

  puts(file.filename_nul);

  return 0;
}

L'union alloue un membre qui est assez grand pour contenir l'extension du chemin complet moins et le terminateur nul. Et il alloue un membre qui est assez grand pour contenir l'extension complète du chemin moins, mais avec un terminateur nul.

Le membre qui est trop petit pour contenir le __FILE__ Est initialisé avec autant de __FILE__ Que possible. C'est ok en C mais pas autorisé en C++. Si __FILE__ Contient test.c, Le membre de l'union sera maintenant initialisé pour contenir test sans terminateur nul.

Il y aura cependant toujours des zéros à la fin après cette chaîne, car ce hack abuse du fait que l'autre membre du syndicat a été initialisé selon les règles d'initialisation "agrégat/union". Cette règle force tous les éléments restants de l '"agrégat" à être initialisés comme s'ils avaient une durée de stockage statique, c'est-à-dire à zéro. Ce qui se trouve être la valeur du terminateur nul.

8
Lundin

extraire le nom de fichier de base au moment de la compilation sans astuces de préprocesseur et sans scripts externes? c ++ 14? pas de problème Monsieur.

#include <iostream>
#include <string>

using namespace std;

namespace detail {
    constexpr bool is_path_sep(char c) {
        return c == '/' || c == '\\';
    }

    constexpr const char* strip_path(const char* path)
    {
        auto lastname = path;
        for (auto p = path ; *p ; ++p) {
            if (is_path_sep(*p) && *(p+1)) lastname = p+1;
        }
        return lastname;
    }

    struct basename_impl
    {
        constexpr basename_impl(const char* begin, const char* end)
        : _begin(begin), _end(end)
        {}

        void write(std::ostream& os) const {
            os.write(_begin, _end - _begin);
        }

        std::string as_string() const {
            return std::string(_begin, _end);
        }

        const char* const _begin;
        const char* const _end;
    };

    inline std::ostream& operator<<(std::ostream& os, const basename_impl& bi) {
        bi.write(os);
        return os;
    }

    inline std::string to_string(const basename_impl& bi) {
        return bi.as_string();
    }

    constexpr const char* last_dot_of(const char* p) {
        const char* last_dot = nullptr;
        for ( ; *p ; ++p) {
            if (*p == '.')
                last_dot = p;
        }
        return last_dot ? last_dot : p;
    }
}

// the filename with extension but no path
constexpr auto filename = detail::strip_path(__FILE__);
constexpr auto basename = detail::basename_impl(filename, detail::last_dot_of(filename));

auto main() -> int
{
    cout << filename << endl;
    cout << basename << endl;

    cout << to_string(basename) << endl;

    return 0;
}
7
Richard Hodges

Cela se révèle très simple, il vous suffit de #line directive préprocesseur , exemple

#line 0 "Hello"

en haut du fichier, tel quel, si tout ce que vous voulez est de masquer complètement le nom du fichier,

#line 0 ""

travaillerait.

Si vous ne voulez pas utiliser Makefiles, vous pouvez utiliser ceci

file=cfile;
content=$(sed -e "1s/^/#line 0 \"$file\"\n/" example/${file}.c);
echo $content | gcc -xc -O3 -o ${file} -

Le -xc Le drapeau gcc ci-dessus signifie (d'après la documentation de gcc):

-x Langue:

Spécifiez explicitement la langue des fichiers d'entrée suivants (plutôt que de laisser le compilateur choisir une valeur par défaut en fonction du suffixe du nom de fichier). Cette option s'applique à tous les fichiers d'entrée suivants jusqu'à l'option -x suivante. Les valeurs possibles pour la langue sont:

          c  c-header  cpp-output
          c++  c++-header  c++-cpp-output
          objective-c  objective-c-header  objective-c-cpp-output
          objective-c++ objective-c++-header objective-c++-cpp-output
          assembler  assembler-with-cpp
          ada
          f77  f77-cpp-input f95  f95-cpp-input
          go
          Java

Si vous n'avez pas de script qui vous aide à construire la source, il n'y a aucun moyen de le faire, je pense.

En outre, vous pouvez voir dans la citation ci-dessus de la documentation gcc, que vous pouvez enregistrer les fichiers sans aucune extension, puis combiner @ Lundin original solution avec ceci et utiliser

gcc -xc -o file filename_without_extension

dans ce cas __FILE__ s'étendrait à "filename_without_extension", et vous obtiendrez ce que vous voulez, bien que vous deviez compiler le fichier dans le même répertoire où il se trouve, car sinon il contiendra le chemin d'accès au fichier.

3
Iharob Al Asimi