A macro __FILE__ mostra o caminho completo


164

A macro predefinida padrão __FILE__disponível em C mostra o caminho completo para o arquivo. Existe alguma maneira de diminuir o caminho? Quero dizer em vez de

/full/path/to/file.c

Entendo

to/file.c

ou

file.c

18
Seria realmente ótimo encontrar uma solução apenas para pré-processador. Receio que as sugestões baseadas em operações de string sejam executadas em tempo de execução.
Cdeonard

9
Como você está usando o gcc, acho que você pode alterar o __FILE__conteúdo, alterando o nome do arquivo que você passa na linha de comando. Então, em vez de gcc /full/path/to/file.c, tente cd /full/path/to; gcc file.c; cd -;. Claro que há um pouco mais do que isso, se você estiver contando com o diretório atual do gcc para o caminho de inclusão ou o local do arquivo de saída. Edit: os documentos do gcc sugerem que é o caminho completo, não o argumento do nome do arquivo de entrada, mas não é isso que estou vendo no gcc 4.5.3 no Cygwin. Então você pode tentar no Linux e ver.
precisa

4
O GCC 4.5.1 (criado especificamente para arm-none-eabi) usa o texto exato do nome do arquivo em sua linha de comando. No meu caso, foi culpa do IDE invocar o GCC com todos os nomes de arquivos totalmente qualificados, em vez de colocar o diretório atual em algum lugar sensato (local do arquivo do projeto, talvez?) Ou configurável e usar caminhos relativos a partir daí. Suspeito que muitos IDEs façam isso (especialmente no Windows) devido a algum tipo de desconforto relacionado à explicação de onde o diretório "atual" realmente é para um aplicativo GUI.
RBerteig 13/09/12

3
@SteveJessop - espero que você leia este comentário. Eu tenho uma situação em que vejo __FILE__impressa como ../../../../../../../../rtems/c/src/lib/libbsp/sparc/leon2/../../shared/bootcard.ce quero saber onde o gcc compilou o arquivo, de modo que esse arquivo esteja relativamente localizado, como é mostrado.
Chan Kim

1
Esta questão não é uma mentira da vinculada. Por um lado, o vinculado é sobre C ++, e as respostas, consequentemente, se aprofundam na macro esotérica C ++. Segundo, nada na pergunta do OP exige uma solução macro. Apenas aponta solenemente um problema e faz uma pergunta em aberto.
Prof. Falken

Respostas:


166

Experimentar

#include <string.h>

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

Para Windows, use '\\' em vez de '/'.


13
/é um separador de caminho válido no Windows.
Hans Passant

11
/é um separador de caminho válido nos nomes de arquivos passados ​​para CreateFile () e assim por diante. No entanto, isso nem sempre significa que você pode usar /em qualquer lugar do Windows, já que existe uma tradição (herdada do CPM) de usar /como argumento no prompt de comando. Mas uma ferramenta de qualidade teria o cuidado de dividir os nomes dos arquivos nos caracteres barra e barra invertida para evitar problemas para as pessoas que conseguem usar /.
RBerteig 13/09/12

5
@AmigableClarkKant, não, você pode misturar os dois separadores com o mesmo nome de arquivo.
RBerteig 13/09/12

2
Se a sua plataforma suporta, char* fileName = basename(__FILE__); definitivamente está lá no Linux e no OS X, embora não conheça o Windows.
JeremyP

14
Isso pode ser reduzido para strrchr("/" __FILE__, '/') + 1. Ao adicionar "/" a __FILE__, strrchr será garantido que encontrará algo e, portanto, o condicional?: Não será mais necessário.
ɲeuroburɳ

54

Aqui está uma dica se você estiver usando o cmake. De: http://public.kitware.com/pipermail/cmake/2013-January/053117.html

Estou copiando a dica, então está tudo nesta página:

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

Se você estiver usando o GNU make, não vejo razão para que você não possa estender isso para seus próprios makefiles. Por exemplo, você pode ter uma linha como esta:

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

onde $(SOURCE_PREFIX)é o prefixo que você deseja remover.

Em seguida, use __FILENAME__no lugar de __FILE__.


11
Receio que isso não funcione para o arquivo referenciado no arquivo de cabeçalho.
Baiyan Huang 12/09

2
Concorde com @BaiyanHuang, mas não tenha certeza de que o comentário é claro. __FILE__não é um símbolo simples de pré-processador, é alterado frequentemente para o arquivo atual para emitir o nome do arquivo atual (cabeçalho ou módulo de origem). Isso __FILENAME__teria apenas a fonte mais externa
encerrada

3
A solução desta resposta não é portátil, pois usa escapes de shell Bourne. Melhor usar o CMake para implementá-lo de maneira limpa e portátil. Consulte a resposta da macro define_file_basename_for_sources aqui.
Colin D Bennett

3
A variante GNU Make disso é CFLAGS + = -D__FILENAME __ = \ "$ (notdir $ <) \"
ctuffli

4
@firegurafiku Em plataformas incorporadas, o tamanho do código geralmente é mais uma restrição do que velocidade. Se você tiver instruções de depuração em cada arquivo, isso pode aumentar rapidamente o tamanho do arquivo com cadeias de caracteres de caminho completo. Estou lidando com essa plataforma no momento e a referência a __FILE__todos os lugares está saindo do espaço do código.
Mrtumnus

26

Acabei de pensar em uma ótima solução para isso, que funciona com os arquivos de origem e de cabeçalho, é muito eficiente e funciona em tempo de compilação em todas as plataformas sem extensões específicas do compilador. Essa solução também preserva a estrutura de diretórios relativa do seu projeto, para que você saiba em qual pasta o arquivo está e apenas em relação à raiz do seu projeto.

A idéia é obter o tamanho do diretório de origem com sua ferramenta de construção e apenas adicioná-lo à __FILE__macro, removendo o diretório completamente e mostrando apenas o nome do arquivo começando no diretório de origem.

O exemplo a seguir é implementado usando o CMake, mas não há razão para que ele não funcione com outras ferramentas de construção, porque o truque é muito simples.

No arquivo CMakeLists.txt, defina uma macro que tenha o comprimento do caminho para o seu projeto no 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}")

No seu código-fonte, defina uma __FILENAME__macro que apenas adicione o tamanho do caminho de origem à __FILE__macro:

#define __FILENAME__ (__FILE__ + SOURCE_PATH_SIZE)

Em seguida, basta usar essa nova macro em vez da __FILE__macro. Isso funciona porque o __FILE__caminho sempre começa com o caminho para o diretório de origem do CMake. Ao removê-lo da __FILE__string, o pré-processador se encarregará de especificar o nome correto do arquivo e tudo será relativo à raiz do seu projeto CMake.

Se você se preocupa com o desempenho, este é tão eficiente quanto usar __FILE__, porque ambos __FILE__e SOURCE_PATH_SIZEsão conhecidos constantes de tempo de compilação, para que possa ser otimizado afastado pelo compilador.

O único lugar em que isso falharia é se você estiver usando isso em arquivos gerados e eles estiverem em uma pasta de compilação externa. Então você provavelmente terá que criar outra macro usando a CMAKE_BUILD_DIRvariável em vez de CMAKE_SOURCE_DIR.


4
Eu não entendi isso no começo. Eu codifiquei exemplos e os executei contra o gcc e o clang, e eles funcionam. Também experimento anexar vários valores numéricos literais, e isso se comporta como eu esperava. Então finalmente me ocorreu. __FILE__é um ponteiro para uma matriz de bytes. Portanto, adicionar um literal numérico é apenas uma adição de ponteiro. Muito inteligente @RenatoUtsch
Daniel

Funciona apenas se um arquivo de origem estiver no diretório cmake list. Se um arquivo de origem estiver fora, ele será interrompido, com acesso fora de uma cadeia literal. Portanto, tenha cuidado com isso.
Andry

Na verdade, não reduz o tamanho do código. Eu acho que todo o caminho ainda está compilado no binário, apenas o ponteiro é modificado.
Tarion

A __FILE__macro e as macros SOURCE_PATH_SIZE são constantes conhecidas em tempo de compilação. Eu esperaria que os compiladores de otimização modernos pudessem detectar que parte da string não é usada e simplesmente removê-la do binário. De qualquer forma, não acho que esses poucos bytes fariam uma diferença significativa no tamanho binário, então eu realmente não me importo com isso.
RenatoUtsch 20/02/19

@RenatoUtsch O projeto no qual estou trabalhando tem uma alteração que apenas especifica o nome do arquivo, mas tem a desvantagem de atribuir o nome do arquivo C ao cabeçalho também. A alteração foi feita para obter compilações reproduzíveis. Então, com o gcc com -O2, a string seria realmente otimizada e a compilação tornada reproduzível?
Paul Stelian

18

Compile a solução de tempo aqui. É baseado no fato de que sizeof()uma string literal retorna seu comprimento + 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__)

Sinta-se à vontade para estender a cascata condicional do operador até o nome máximo de arquivo sensível no projeto. O comprimento do caminho não importa, desde que você verifique o suficiente a partir do final da string.

Vou ver se consigo obter uma macro semelhante sem comprimento codificado com recursão de macro ...


3
Resposta legal. Você precisa usar pelo menos -O1para usar isso em tempo de compilação.
Digital Trauma

1
Não é isso ao contrário? Você deseja encontrar a última ocorrência de '/', o que significa que você deve começar sizeof(s) > 2primeiro com a verificação. Além disso, isso não funcionou em tempo de compilação para mim, no -Os. As cadeias de caminho completo estavam presentes no binário de saída.
Mrtumnus

15

Pelo menos para gcc, o valor de __FILE__é o caminho do arquivo, conforme especificado na linha de comando do compilador . Se você compilar file.cassim:

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

a __FILE__ expandirá para "/full/path/to/file.c". Se você fizer isso:

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

então __FILE__será expandido para apenas "file.c".

Isso pode ou não ser prático.

O padrão C não requer esse comportamento. Tudo o que diz sobre__FILE__ é que ele se expande para "O nome presumido do arquivo de origem atual (uma literal de cadeia de caracteres)".

Uma alternativa é usar o #line diretiva. Ele substitui o número da linha atual e, opcionalmente, o nome do arquivo de origem. Se você deseja substituir o nome do arquivo, mas deixar o número da linha em branco, use a __LINE__macro.

Por exemplo, você pode adicionar isso próximo ao topo de file.c:

#line __LINE__ "file.c"

O único problema disso é que ele atribui o número da linha especificada à linha a seguir e o primeiro argumento #linedeve ser uma sequência de dígitos, para que você não possa fazer algo como

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

Assegurando que o nome do arquivo no #line diretiva corresponda ao nome real do arquivo seja deixado como um exercício.

Pelo menos para o gcc, isso também afetará o nome do arquivo relatado nas mensagens de diagnóstico.


1
Keith Thompson, é uma ótima solução, obrigado. O único problema é que parece que essa macro reduz o __LINE__valor em um. Então, __LINE__escrito em linha xgest avaliado como x-1. Pelo menos com o gcc 5.4.
Elbowpo #

1
@Klepak: Você está certo, e esse é o comportamento padrão. A diretiva "faz com que a implementação se comporte como se a seguinte sequência de linhas de origem começasse com uma linha de origem que tenha um número de linha especificado pela sequência de dígitos". E tem que ser uma sequência de dígitos , para que você não possa usar #line __LINE__-1, "file.c". Vou atualizar minha resposta.
9788 Keith Thompson #

1
Como seria para outros compiladores como Clang, MSVC ou Intel C Compiler?
Franklin Yu

8

Um pouco atrasado para a festa, mas para o GCC dê uma olhada na -ffile-prefix-map=old=newopção:

Ao compilar arquivos que residem no diretório antigo , registre todas as referências a eles no resultado da compilação como se os arquivos residissem no diretório novo . Especificar esta opção é equivalente a especificar todas as -f*-prefix-mapopções individuais . Isso pode ser usado para criar construções reproduzíveis independentes do local. Veja também -fmacro-prefix-mape -fdebug-prefix-map.

Portanto, para minhas compilações Jenkins , adicionarei uma -ffile-prefix-map=${WORKSPACE}/=/outra para remover o prefixo local de instalação do pacote de desenvolvimento.

OBSERVAÇÃO Infelizmente, a -ffile-prefix-mapopção só está disponível no GCC 8 em diante, como é o -fmacro-prefix-mapque, penso eu , faz a __FILE__parte. Por exemplo, no GCC 5, temos apenas o -fdebug-prefix-mapque não parece (afetar) __FILE__.


1
Opção de -ffile-prefix-mapfato implica ambos -fdebug-prefix-mape -fmacro-prefix-mapopções. Veja também as referências em reproducible-builds.org/docs/build-path O bug do GCC que rastreia -fmacro-prefix-mape -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__)]);
    }

O código gera um deslocamento do tempo de compilação quando:

  • gcc: pelo menos gcc6.1 + -O1
  • msvc: colocar resultado na variável constexpr:

      constexpr auto file = &__FILE__[get_file_name_offset(__FILE__)];
      printf("%s\n", file);
  • clang: persiste na não compilação da avaliação do tempo

Existe um truque para forçar todos os 3 compiladores a compilarem a avaliação do tempo, mesmo na configuração de depuração com otimização desabilitada:

    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


Cara, isso é lindo e funciona, obrigado! Não faço ideia por que essa resposta é tão subestimada.
Roman

5

No VC, ao usar /FC, __FILE__expande para o caminho completo, sem a /FCopção __FILE__expande o nome do arquivo. ref: aqui


4

Não há tempo de compilação para fazer isso. Obviamente, você pode fazê-lo em tempo de execução usando o tempo de execução C, como algumas das outras respostas demonstraram, mas em tempo de compilação, quando o pré-processador entrar em ação, você estará sem sorte.


1
a strrchrresposta poderia ser plausivelmente calculada em tempo de compilação, embora, é claro, ainda não o seja pelo pré-processador. Não sei se o gcc realmente faz isso, não verifiquei, mas tenho certeza de que ele calcula strlenliterais de strings em tempo de compilação.
precisa

@ Steve - talvez, mas essa é uma grande dependência do comportamento específico do compilador.
Sean

Não acho que seja uma grande dependência, porque duvido muito que esse código seja crítico para o desempenho. E se for, mova-o para fora do loop. Nos casos em que isso é um grande negócio, porque você absolutamente precisa de uma literal de cadeia contendo apenas o nome da base, talvez possa computar a cadeia correta no tempo de construção executando algum script na fonte.
precisa

5
Pode não ser crítico para o desempenho, mas pode ser facilmente visto como crítico para a privacidade. Não há uma boa razão para revelar minhas práticas organizacionais por cliente em sequências congeladas em um arquivo EXE liberado. Pior, para aplicativos criados em nome de um cliente, essas sequências de caracteres podem revelar coisas que meu cliente prefere não, como não ser o autor de seu próprio produto. Como __FILE__é invocado implicitamente por assert(), esse vazamento pode ocorrer sem nenhum outro ato manifesto.
RBerteig 13/09/12

O @RBerteig, o __FILE__próprio nome da base , também pode revelar coisas que o cliente pode preferir, portanto, o uso de __FILE__qualquer lugar - se ele contém o nome do caminho absoluto completo ou apenas o nome da base - tem os mesmos problemas que você apontou. Nessa situação, toda a saída precisará ser examinada e uma API especial deve ser introduzida para a saída dos clientes. O restante da saída deve ser descarregado para / dev / NULL ou stdout e o stderr deve ser fechado. :-)
tchen 11/01

4

Use a função basename () ou, se você estiver no Windows, _splitpath () .

#include <libgen.h>

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

Tente também man 3 basenameem uma concha.


2
@mahmood: char file_copy[] = __FILE__; const char *filename = basename(__FILE__);. O motivo da cópia é que o nome da base pode modificar a sequência de entrada. Você também deve observar que o ponteiro do resultado só é bom até que basenameseja chamado novamente. Isso significa que não é seguro para threads.
precisa

@ SteveJessop, ah eu esqueci. Verdade.
Prof. Falken

1
@ Amigable: para ser justo, suspeito que basename, de fato, não modifique a string de entrada resultante __FILE__, porque a string de entrada não possui um /no final e, portanto, não há necessidade de modificação. Então você pode se safar, mas acho que na primeira vez que alguém vê basename, deve vê-lo com todas as restrições.
18711 Steve Jobs (

@SteveJessop, a página de manual do BSd para basename () menciona que a versão herdada de basename () recebe um const char * e não modifica a string. A página de manual do linux não menciona nada sobre const, mas menciona que ele pode retornar uma parte da sequência de argumentos. Portanto, é melhor ser conservador ao lidar com basename ().
Prof. Falken

@SteveJessop, hehe, só agora, depois de analisar seu comentário com cuidado, depois de quatro anos, percebo que /no final da string significa que basename pode ter um bom motivo para modificar seu argumento.
Prof. Falken

4

Se você estiver usando o CMAKEcompilador GNU, isso globaldefine bem:

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

4

Eu uso a mesma solução com a resposta do @Patrick há anos.

Há um pequeno problema quando o caminho completo contém um link de símbolo.

Melhor solução.

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

Por que usar isso?

  • -Wno-builtin-macro-redefinedpara silenciar os avisos do compilador para redefinir a __FILE__macro.

    Para esses compiladores não suportam isso, consulte a maneira Robusta abaixo.

  • Tirar o caminho do projeto do caminho do arquivo é o seu requisito real . Você não vai gostar de perder tempo para descobrir onde está um header.harquivo src/foo/header.hou src/bar/header.h.

  • Devemos tirar a __FILE__macrocmake arquivo de configuração.

    Essa macro é usada na maioria dos códigos existentes. Simplesmente redefina que pode liberar você.

    Compiladores como gccpredefinem essa macro a partir dos argumentos da linha de comando. E o caminho completo é escrito em makefiles gerado por cmake.

  • CMAKE_*_FLAGSÉ necessário o código rígido .

    Existem alguns comandos para adicionar opções ou definições do compilador em uma versão mais recente, como add_definitions()e add_compile_definitions(). Esses comandos analisam as funções make, como substantes se aplicam aos arquivos de origem. Não é isso que queremos.

Maneira robusta para -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)

Lembre-se de remover esta opção do compilador da set(*_FLAGS ... -D__FILE__=...)linha.


Isso não funciona para conteúdos provenientes de arquivos de inclusão.
chqrlie 30/03

Pode postar algum código? Um caso comum é definir variáveis ​​no escopo local e usá-las em outro.
Levi.G

Por exemplo, se você usar __FILE__com sua definição para produzir um diagnóstico em uma função embutida definida em um arquivo de cabeçalho, o diagnóstico de tempo de execução relatará o nome do arquivo passado ao compilador em vez do nome do arquivo de inclusão, enquanto o número da linha seria consulte o arquivo de inclusão.
chqrlie 10/04

Sim, ele foi projetado para isso, para o uso mais comum #define LOG(fmt, args...) printf("%s " fmt, __FILE__, ##args). ao usar a LOG()macro, você realmente não deseja ver log.hnas mensagens. afinal, a __FILE__macro é expandida em todos os arquivos C / Cpp (unidade de compilação) em vez dos arquivos incluídos.
Levi.G 14/06

3

Uma pequena variação do que o @ red1ynx proposto criaria a seguinte macro:

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

Em cada um dos seus arquivos .c (pp), adicione:

SET_THIS_FILE_NAME();

Então você pode consultar em THIS_FILE_NAMEvez de __FILE__:

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

Isso significa que a construção é executada uma vez por arquivo .c (pp) em vez de cada vez que a macro é referenciada.

É limitado a usar apenas de arquivos .c (pp) e não pode ser usado nos arquivos de cabeçalho.


3

Eu fiz uma macro __FILENAME__ que evita cortar caminho completo a cada vez. O problema é manter o nome do arquivo resultante em uma variável local cpp.

Isso pode ser feito facilmente, definindo uma variável global estática no arquivo .h . Essa definição fornece variáveis ​​separadas e independentes em cada arquivo .cpp que inclui o arquivo .h . Para ser uma prova de multithreading, vale a pena fazer com que a (s) variável (s) também encadeie (TLS) local.

Uma variável armazena o nome do arquivo (reduzido). Outro detém o valor não cortado que __FILE__deu. O arquivo h:

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

A própria macro chama método com toda a lógica:

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

E a função é implementada desta maneira:

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;
}

O removePath () pode ser implementado de diferentes maneiras, mas o rápido e o simples parecem estar com o 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

só espero melhorar um pouco a macro FILE:

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

isso captura / e \, como Czarek Tomczak solicitou, e funciona muito bem no meu ambiente misto.


6
Definir uma macro chamada FILEé uma péssima idéia, se você incluir <stdio.h>.
21413 Keith Thompson

bom saber. Eu só queria mostrar ao Czarek minha \\ / solution, para não me preocupar com esquemas de nomeação.
alexander golks

2

Experimentar

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

após as instruções de inclusão no seu arquivo de origem e adicione

#pragma pop_macro("__FILE__")

no final do seu arquivo de origem.


2
push_macroe pop_macronão são padrão. (O gcc oferece suporte a eles para compatibilidade com os compiladores do Microsoft Windows.) De qualquer forma, não faz sentido empurrar e exibir a definição de __FILE__; o valor restaurado não será usado após o final do arquivo de origem. A maneira mais limpa para alterar o valor de __FILE__é#line __LINE__ "foobar.c"
Keith Thompson

1
E isso causa um erro interno no pré-processador do gcc. gcc.gnu.org/bugzilla/show_bug.cgi?id=69665
Keith Thompson

1

Aqui está uma função portátil que funciona para Linux (caminho '/') e Windows (combinação de '\' e '/').
Compila com 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;
}

Saída padrão:

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

Aqui está a solução que usa o cálculo em tempo de compilação:

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 você acabou nesta página procurando uma maneira de remover o caminho de origem absoluto que está apontando para o local de criação feio do binário que você está enviando, abaixo pode atender às suas necessidades.

Embora isso não produza exatamente a resposta que o autor expressou seu desejo, pois pressupõe o uso do CMake , ele fica bem próximo. É uma pena que isso não tenha sido mencionado anteriormente por ninguém, pois teria me poupado muito tempo.

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

A configuração acima da variável para ONgerará o comando build no formato:

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

Como resultado, a __FILE__macro resolverá../../src/path/to/source.c

Documentação do CMake

Cuidado com o aviso na página de documentação:

Use caminhos relativos (pode não funcionar!).

Não é garantido que funcione em todos os casos, mas funcionou no meu - CMake 3.13 + gcc 4.5


A documentação do CMake 3.4 ou superior afirma que "Esta variável não tem efeito. O efeito parcialmente implementado que possuía nas versões anteriores foi removido no CMake 3.4". Se funcionou para você com 3.13, há outra razão para isso.
mike.dld

0

Como você está usando o GCC, você pode tirar proveito de

__BASE_FILE__Essa macro se expande para o nome do arquivo de entrada principal, na forma de uma constante de seqüência C. Este é o arquivo de origem especificado na linha de comandos do pré-processador ou compilador C

e depois controle como você deseja exibir o nome do arquivo alterando a representação do arquivo de origem (caminho completo / caminho relativo / nome da base) no momento da compilação.


6
Não faz diferença. Eu usei __FILE__e __BASE_FILE__no entanto ambos mostrar caminho completo para arquivo
mahmood

como você chama o compilador?
Ziu

2
Então aposto que o SCONS está chamando gcc assim gcc /absolute/path/to/file.c. Se você encontrar uma maneira de alterar esse comportamento (? Abrindo uma outra pergunta sobre SO, lol), você não precisa de modificar a cadeia em tempo de execução
ziu

17
Esta resposta está 100% errada. __BASE_FILE__(como dizem os documentos, embora sem clareza) produz o nome do arquivo especificado na linha de comando , por exemplo, test.cou /tmp/test.cdependendo de como você chamou o compilador. É exatamente a mesma coisa que __FILE__, exceto se você estiver dentro de um arquivo de cabeçalho, caso em que __FILE__produz o nome do arquivo atual (por exemplo foo.h) enquanto __BASE_FILE__continua a produzir test.c.
Quuxplusone

0

Aqui está uma solução que funciona para ambientes que não possuem a biblioteca de strings (kernel do Linux, sistemas incorporados 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; })

Agora basta usar em FILENAMEvez de __FILENAME__. Sim, ainda é uma coisa de tempo de execução, mas funciona.


Pode querer verificar '/' e '\', dependendo da plataforma.
31518

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

Uma resposta curta e funcional para Windows e * nix:

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

Por que você precisa, em std::max<const char*>vez de apenas std::max?
chqrlie 30/03
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.