La macro __FILE__ muestra la ruta completa


164

La macro predefinida estándar __FILE__disponible en C muestra la ruta completa al archivo. ¿Hay alguna forma de acortar el camino? Quiero decir en lugar de

/full/path/to/file.c

Veo

to/file.c

o

file.c

18
Sería realmente genial encontrar una solución solo de preprocesador. Me temo que las sugerencias basadas en operaciones de cadena se ejecutarán en tiempo de ejecución.
cdleonard

9
Como estás usando gcc, creo que puedes cambiar lo que __FILE__contiene cambiando el nombre de archivo que pasas en la línea de comando. Entonces, en lugar de gcc /full/path/to/file.cintentarlo cd /full/path/to; gcc file.c; cd -;. Por supuesto, hay algo más que eso si confía en el directorio actual de gcc para la ruta de inclusión o la ubicación del archivo de salida. Editar: los documentos de gcc sugieren que es la ruta completa, no el argumento del nombre del archivo de entrada, pero eso no es lo que estoy viendo para gcc 4.5.3 en Cygwin. Así que también puedes probarlo en Linux y ver.
Steve Jessop

44
GCC 4.5.1 (creado específicamente para arm-none-eabi) utiliza el texto exacto del nombre del archivo en su línea de comando. En mi caso, fue culpa del IDE invocar GCC con todos los nombres de archivos totalmente calificados en lugar de colocar el directorio actual en algún lugar sensible (¿ubicación del archivo del proyecto, tal vez?) O configurable y usar rutas relativas desde allí. Sospecho que muchos IDEs hacen eso (especialmente en Windows) por algún tipo de molestia relacionada con explicar dónde está realmente el directorio "actual" para una aplicación GUI.
RBerteig

3
@SteveJessop: espero que leas este comentario. Tengo una situación en la que veo __FILE__impreso como ../../../../../../../../rtems/c/src/lib/libbsp/sparc/leon2/../../shared/bootcard.cy quiero saber dónde compiló gcc el archivo de modo que este archivo esté relativamente ubicado como se muestra.
Chan Kim

1
Esta pregunta no es un engaño de la vinculada. Por un lado, el vinculado es sobre C ++ y, en consecuencia, las respuestas profundizan en la macro esotérica de C ++. En segundo lugar, no hay nada en la pregunta de OP que exija una solución macro. Solo señala solemnemente un problema y hace una pregunta abierta.
Prof. Falken

Respuestas:


166

Tratar

#include <string.h>

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

Para Windows use '\\' en lugar de '/'.


13
/es un separador de ruta válido en Windows.
Hans Passant

11
/es un separador de ruta válido en los nombres de archivo pasados ​​a CreateFile () y así sucesivamente. Sin embargo, eso no siempre significa que puede usar /en cualquier lugar de Windows, ya que existe una tradición (heredada de CPM) de usar /como argumento inicial en el símbolo del sistema. Pero una herramienta de calidad sería cuidadosa al dividir los nombres de los archivos en los caracteres de barra diagonal inversa y barra diagonal inversa para evitar problemas a las personas que logran usar /.
RBerteig

55
@AmigableClarkKant, no, puedes mezclar ambos separadores en el mismo nombre de archivo.
RBerteig

2
Si su plataforma lo admite char* fileName = basename(__FILE__); , definitivamente está ahí en Linux y OS X, sin embargo, no conozca Windows.
JeremyP

14
Esto podría acortarse a strrchr("/" __FILE__, '/') + 1. Al anteponer "/" a __FILE__, se garantiza que strrchr encontrará algo y, por lo tanto, el condicional?: Ya no es necesario.
uroeuroburɳ

54

Aquí hay un consejo si estás usando cmake. De: http://public.kitware.com/pipermail/cmake/2013-January/053117.html

Estoy copiando la sugerencia, así que todo está en esta página:

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

Si está utilizando GNU make, no veo ninguna razón por la que no pueda extender esto a sus propios makefiles. Por ejemplo, puede tener una línea como esta:

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

¿Dónde $(SOURCE_PREFIX)está el prefijo que desea eliminar?

Luego use __FILENAME__en lugar de __FILE__.


11
Me temo que esto no funciona para el ARCHIVO al que se hace referencia en el archivo de encabezado.
Baiyan Huang

2
De acuerdo con @BaiyanHuang pero no estoy seguro de que el comentario sea claro. __FILE__no es un simple símbolo de preprocesador, cambia al archivo actual y se usa a menudo para emitir el nombre del archivo actual (encabezado o módulo fuente). Esto __FILENAME__sólo tendría la fuente externa
NHED

3
La solución de esta respuesta no es portátil ya que utiliza escapes de shell Bourne. Es mejor usar CMake para implementarlo de manera limpia y portátil. Vea la respuesta de macro define_file_basename_for_sources aquí.
Colin D Bennett

3
La variante GNU Make de esto es CFLAGS + = -D__FILENAME __ = \ "$ (notdir $ <) \"
ctuffli

44
@firegurafiku En las plataformas integradas, el tamaño del código suele ser más una restricción que la velocidad. Si tiene declaraciones de depuración en cada archivo, eso puede aumentar rápidamente el tamaño del archivo con cadenas de ruta completa. Estoy lidiando con una plataforma de este tipo en este momento, y hacer referencia a __FILE__todas partes está saliendo del espacio de código.
mrtumnus

26

Acabo de pensar en una gran solución para esto que funciona con archivos de origen y de encabezado, es muy eficiente y funciona en tiempo de compilación en todas las plataformas sin extensiones específicas del compilador. Esta solución también conserva la estructura de directorios relativa de su proyecto, para que sepa en qué carpeta se encuentra el archivo, y solo en relación con la raíz de su proyecto.

La idea es obtener el tamaño del directorio de origen con su herramienta de compilación y simplemente agregarlo a la __FILE__macro, eliminando el directorio por completo y solo mostrando el nombre del archivo que comienza en su directorio de origen.

El siguiente ejemplo se implementa usando CMake, pero no hay razón para que no funcione con otras herramientas de compilación, porque el truco es muy simple.

En el archivo CMakeLists.txt, defina una macro que tenga la longitud de la ruta a su proyecto en 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}")

En su código fuente, defina una __FILENAME__macro que simplemente agregue el tamaño de la ruta de origen a la __FILE__macro:

#define __FILENAME__ (__FILE__ + SOURCE_PATH_SIZE)

Luego use esta nueva macro en lugar de la __FILE__macro. Esto funciona porque la __FILE__ruta siempre comenzará con la ruta al directorio de origen de CMake. Al eliminarlo de la __FILE__cadena, el preprocesador se encargará de especificar el nombre de archivo correcto y todo será relativo a la raíz de su proyecto CMake.

Si se preocupan por el rendimiento, esto es tan eficiente como el uso __FILE__, porque ambos __FILE__y SOURCE_PATH_SIZEse conocen las constantes de tiempo de compilación, por lo que puede ser optimizado de distancia por el compilador.

El único lugar donde esto podría fallar es si está usando esto en archivos generados y están en una carpeta de compilación fuera del código fuente. Entonces probablemente tendrá que crear otra macro usando la CMAKE_BUILD_DIRvariable en lugar de CMAKE_SOURCE_DIR.


44
No entendí esto al principio. Codifiqué ejemplos y los ejecuté contra gcc y clang y funcionan. También experimento con solo agregar varios valores numéricos literales, y eso se comporta como esperaba. Entonces finalmente me di cuenta. __FILE__es un puntero a una matriz de bytes. Por lo tanto, agregar un literal numérico es solo la suma del puntero. Muy inteligente @RenatoUtsch
Daniel

Funciona solo si un archivo fuente está en el directorio de la lista de cmake. Si un archivo fuente está afuera, se romperá, podría tener acceso fuera de una cadena literal. Así que ten cuidado con eso.
Andry

En realidad no reduce el tamaño del código. Supongo que toda la ruta aún se compila en el binario, solo se modifica el puntero.
Tarion

Tanto la __FILE__macro como las macros SOURCE_PATH_SIZE son constantes conocidas en tiempo de compilación. Esperaría que los compiladores de optimización modernos pudieran detectar que parte de la cadena no se usa y simplemente eliminarla del binario. De todos modos, no creo que estos pocos bytes hagan una diferencia significativa en el tamaño binario, así que realmente no me importaría eso.
RenatoUtsch

@RenatoUtsch El proyecto en el que estoy trabajando tiene un cambio que solo especifica el nombre del archivo, pero tiene la desventaja de dar también el nombre del archivo C al encabezado. El cambio se realizó para obtener compilaciones reproducibles. Entonces, con gcc con -O2, ¿la cadena se optimizaría y la construcción se haría reproducible?
Paul Stelian

18

Solución de tiempo puramente de compilación aquí. Se basa en el hecho de que sizeof()un literal de cadena devuelve su longitud + 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__)

Siéntase libre de extender la cascada de operador condicional al nombre de archivo sensible máximo en el proyecto. La longitud de la ruta no importa, siempre que verifique lo suficientemente lejos del final de la cadena.

Veré si puedo obtener una macro similar sin longitud codificada con recursión de macro ...


3
Buena respuesta. Necesita usar al menos -O1para usar esto para ser tiempo de compilación.
Digital Trauma

1
¿No es esto al revés? Desea encontrar la última aparición de '/', lo que significa que sizeof(s) > 2primero debe comenzar con la verificación. Además, esto no funcionó en tiempo de compilación para mí, en -Os. Las cadenas de ruta completas estaban presentes en el binario de salida.
mrtumnus

15

Al menos para gcc, el valor de __FILE__es la ruta del archivo como se especifica en la línea de comando del compilador . Si compila file.casí:

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

el __FILE__se expandirá a "/full/path/to/file.c". Si en cambio haces esto:

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

luego __FILE__se expandirá a solo "file.c".

Esto puede o no ser práctico.

El estándar C no requiere este comportamiento. Todo lo que dice __FILE__es que se expande a "El supuesto nombre del archivo fuente actual (una cadena de caracteres literal)".

Una alternativa es usar la #linedirectiva. Anula el número de línea actual y, opcionalmente, el nombre del archivo de origen. Si desea anular el nombre del archivo pero dejar solo el número de línea, use la __LINE__macro.

Por ejemplo, puede agregar esto cerca de la parte superior de file.c:

#line __LINE__ "file.c"

El único problema con esto es que asigna el número de línea especificado a la siguiente línea, y el primer argumento #linetiene que ser una secuencia de dígitos para que no pueda hacer algo como

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

Asegurar que el nombre del archivo en la #linedirectiva coincida con el nombre real del archivo se deja como ejercicio.

Al menos para gcc, esto también afectará el nombre del archivo informado en los mensajes de diagnóstico.


1
Keith Thompson es una gran solución, gracias. El único problema es que parece que esta macro reduce el __LINE__valor en uno. Así __LINE__escrito en línea xgest evaluado a x-1. Al menos con gcc 5.4.
elklepo

1
@ Klepak: Tienes razón, y ese es el comportamiento estándar. La directiva "hace que la implementación se comporte como si la siguiente secuencia de líneas de origen comenzara con una línea de origen que tiene un número de línea según lo especificado por la secuencia de dígitos". Y tiene que ser una secuencia de dígitos , por lo que no puede usar #line __LINE__-1, "file.c". Actualizaré mi respuesta.
Keith Thompson, el

1
¿Cómo sería para otros compiladores como Clang, MSVC o Intel C Compiler?
Franklin Yu

8

Un poco tarde para la fiesta, pero para GCC eche un vistazo a la -ffile-prefix-map=old=newopción:

Al compilar archivos que residen en el directorio anterior , registre cualquier referencia a ellos en el resultado de la compilación como si los archivos residieran en el directorio nuevo . Especificar esta opción es equivalente a especificar todas las -f*-prefix-mapopciones individuales . Esto se puede usar para hacer compilaciones reproducibles que son independientes de la ubicación. Ver también -fmacro-prefix-mapy -fdebug-prefix-map.

Entonces, para mis compilaciones de Jenkins, agregaré -ffile-prefix-map=${WORKSPACE}/=/y otra para eliminar el prefijo de instalación del paquete de desarrollo local.

NOTA Desafortunadamente, la -ffile-prefix-mapopción solo está disponible en GCC 8 en adelante, ya -fmacro-prefix-mapque creo que hace la __FILE__parte. Para, digamos, GCC 5, solo tenemos -fdebug-prefix-maplo que no (parece) afectar __FILE__.


1
De -ffile-prefix-maphecho, la opción implica ambas -fdebug-prefix-mapy -fmacro-prefix-mapopciones. Consulte también las referencias en reproducible-builds.org/docs/build-path El error de GCC que rastrea -fmacro-prefix-mapy -ffile-prefix-mapes 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__)]);
    }

El código genera una compensación de tiempo de compilación cuando:

  • gcc: al menos gcc6.1 + -O1
  • msvc: poner el resultado en la variable constexpr:

      constexpr auto file = &__FILE__[get_file_name_offset(__FILE__)];
      printf("%s\n", file);
  • clang: persiste en la evaluación del tiempo de no compilación

Hay un truco para forzar que los 3 compiladores compilen la evaluación del tiempo incluso en la configuración de depuración con optimización deshabilitada:

    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


Hombre, eso es hermoso y funciona, ¡gracias! No tengo idea de por qué esta respuesta está tan subestimada.
Roman

5

En VC, cuando se usa /FC, se __FILE__expande a la ruta completa, sin la /FCopción __FILE__expande el nombre del archivo. ref: aquí


4

No hay forma de compilar el tiempo para hacer esto. Obviamente, puede hacerlo en tiempo de ejecución utilizando el tiempo de ejecución C, como han demostrado algunas de las otras respuestas, pero en el momento de la compilación, cuando el preprocesador entra en funcionamiento, no tiene suerte.


1
la strrchrrespuesta podría calcularse plausiblemente en tiempo de compilación, aunque, por supuesto, aún no lo hizo el preprocesador. No sé si gcc realmente lo hace, no lo he comprobado, pero estoy bastante seguro de que calcula strlenlos literales de cadena en tiempo de compilación.
Steve Jessop

@Steve: tal vez, pero esa es una gran dependencia del comportamiento específico del compilador.
Sean

No creo que sea una gran dependencia, porque dudo mucho que este código sea crítico para el rendimiento. Y si es así, muévalo fuera del bucle. En los casos en que esto es un gran problema, ya que absolutamente necesita un literal de cadena que contenga solo el nombre base, tal vez podría calcular la cadena correcta en el momento de la compilación ejecutando algún script sobre la fuente.
Steve Jessop

55
Puede que no sea crítico para el rendimiento, pero puede verse fácilmente como crítico para la privacidad. No hay una buena razón para revelar mis prácticas organizativas por cliente en cadenas congeladas en un archivo EXE publicado. Peor aún, para las aplicaciones creadas en nombre de un cliente, esas cadenas pueden revelar cosas que mi cliente podría preferir no hacer, como no ser el autor de su propio producto. Como __FILE__se invoca implícitamente por assert(), esta fuga puede ocurrir sin ningún otro acto manifiesto.
RBerteig

@RBerteig, el nombre base de __FILE__sí mismo también puede revelar cosas que el cliente podría preferir no hacer, por lo que usar __FILE__cualquier lugar, ya sea que contenga el nombre de ruta absoluto completo o solo el nombre base, tiene los mismos problemas que usted señaló. En esta situación, todos los resultados deberán ser analizados y se deberá introducir una API especial para los resultados a los clientes. El resto de la salida debe volcarse a / dev / NULL o stdout y stderr debe cerrarse. :-)
tchen

4

Use la función basename () o, si está en Windows, _splitpath () .

#include <libgen.h>

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

Prueba también man 3 basenameen una concha.


2
@mahmood: char file_copy[] = __FILE__; const char *filename = basename(__FILE__);. El motivo de la copia es que basename puede modificar la cadena de entrada. También debe tener cuidado de que el puntero de resultados solo sea bueno hasta que basenamese vuelva a llamar. Esto significa que no es seguro para subprocesos.
Steve Jessop

@SteveJessop, ah, lo olvidé. Cierto.
Prof. Falken

1
@Amigable: para ser justos, sospecho que basenamede hecho no modificará la cadena de entrada resultante __FILE__, porque la cadena de entrada no tiene un /final y, por lo tanto, no hay necesidad de modificación. Así que puede salirse con la suya, pero me imagino que la primera vez que alguien ve basename, debería verlo con todas las restricciones.
Steve Jessop

@SteveJessop, la página de manual de BSd para basename () menciona que la versión heredada de basename () toma un const char * y no modifica la cadena. La página de manual de Linux no menciona nada sobre const, pero menciona que puede devolver una parte de la cadena de argumento. Por lo tanto, es mejor ser conservador al tratar con basename ().
Prof. Falken

@SteveJessop, jeje, solo ahora después de mirar su comentario cuidadosamente, después de cuatro años, me doy cuenta de que /al final de la cadena significa que basename puede tener una buena razón para modificar su argumento.
Prof. Falken

4

Si está utilizando el CMAKEcompilador GNU, esta globaldefinición funciona bien:

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

4

He usado la misma solución con la respuesta de @Patrick durante años.

Tiene un pequeño problema cuando la ruta completa contiene enlace de símbolo.

Mejor solucion.

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 qué debería usar esto?

  • -Wno-builtin-macro-redefinedpara silenciar las advertencias del compilador para redefinir la __FILE__macro.

    Para aquellos compiladores que no admiten esto, consulte el modo Robusto a continuación.

  • Eliminar la ruta del proyecto de la ruta del archivo es su requisito real . No querrá perder el tiempo para averiguar dónde hay un header.harchivo, src/foo/header.ho src/bar/header.h.

  • Deberíamos quitar la __FILE__macro en el cmakearchivo de configuración.

    Esta macro se usa en la mayoría de los códigos existentes. Simplemente redefinirlo puede liberarlo.

    Los compiladores como gccpredefinen esta macro a partir de los argumentos de la línea de comandos. Y la ruta completa se escribe en makefiles generada por cmake.

  • CMAKE_*_FLAGSSe requiere código duro .

    Hay algunos comandos para agregar opciones o definiciones del compilador en alguna versión más reciente, como add_definitions()y add_compile_definitions(). Estos comandos analizarán las funciones substde creación como antes se aplican a los archivos de origen. Eso no es lo que queremos.

Manera 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)

Recuerde eliminar esta opción del compilador de la set(*_FLAGS ... -D__FILE__=...)línea.


Esto no funciona para contenidos provenientes de archivos de inclusión.
chqrlie

¿Puedes publicar algún código? Un caso común es establecer variables en el ámbito local y usarlo en otro.
Levi.G

Por ejemplo, si usa __FILE__su definición para producir un diagnóstico en una función en línea definida en un archivo de encabezado, el diagnóstico de tiempo de ejecución informará el nombre del archivo pasado al compilador en lugar del nombre del archivo de inclusión, mientras que el número de línea sería consulte el archivo de inclusión.
chqrlie

Sí, está diseñado para ser eso, para el uso más común es #define LOG(fmt, args...) printf("%s " fmt, __FILE__, ##args). cuando usa la LOG()macro, realmente no desea ver log.hen los mensajes. después de todo, la __FILE__macro se expande en cada archivo C / Cpp (unidad de compilación) en lugar de los archivos incluidos.
Levi.G

3

Una ligera variación de lo que propuso @ red1ynx sería crear la siguiente macro:

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

En cada uno de sus archivos .c (pp) agregue:

SET_THIS_FILE_NAME();

Entonces puede referirse en THIS_FILE_NAMElugar de __FILE__:

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

Esto significa que la construcción se realiza una vez por archivo .c (pp) en lugar de cada vez que se hace referencia a la macro.

Se limita a su uso solo desde archivos .c (pp) y sería inutilizable desde archivos de encabezado.


3

Hice una macro __FILENAME__que evita cortar el camino completo cada vez. El problema es mantener el nombre del archivo resultante en una variable local cpp.

Se puede hacer fácilmente definiendo una variable global estática en el archivo .h . Esta definición proporciona variables separadas e independientes en cada archivo .cpp que incluye el .h . Para ser a prueba de subprocesos múltiples, vale la pena hacer que las variables también se enhebren localmente (TLS).

Una variable almacena el nombre del archivo (reducido). Otro tiene el valor no cortado que __FILE__dio. El archivo h:

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

La macro misma llama al método con toda la lógica:

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

Y la función se implementa de esta manera:

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 () se puede implementar de diferentes maneras, pero lo rápido y simple parece ser 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

solo espero mejorar un poco la macro ARCHIVO:

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

esto atrapa / y \, como Czarek Tomczak solicitó, y esto funciona muy bien en mi entorno mixto.


66
Definir una macro llamada FILEes una muy mala idea si incluye <stdio.h>.
Keith Thompson

bueno saber. Solo quería mostrarle a Czarek mi \\ / solución, por lo que no me molesté con los esquemas de nombres.
Alexander golks

2

Tratar

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

después de las declaraciones de inclusión en su archivo fuente y agregue

#pragma pop_macro("__FILE__")

al final de su archivo fuente.


2
push_macroy pop_macrono son estándar. (gcc los admite por compatibilidad con los compiladores de Microsoft Windows). En cualquier caso, no tiene sentido empujar y reventar la definición de __FILE__; de todos modos, el valor restaurado no se usará después del final del archivo fuente. Una forma más limpia de cambiar el valor de __FILE__es#line __LINE__ "foobar.c"
Keith Thompson,

1
Y esto provoca un error interno en el preprocesador de gcc. gcc.gnu.org/bugzilla/show_bug.cgi?id=69665
Keith Thompson

1

Aquí hay una función portátil que funciona tanto para Linux (ruta '/') como para Windows (combinación de '\' y '/').
Compila con gcc, clang y 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;
}

Salida estándar:

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

Aquí está la solución que utiliza el cálculo en tiempo de compilación:

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

Si terminó en esta página buscando una manera de eliminar la ruta de origen absoluta que apunta a una ubicación de compilación fea del binario que está enviando, a continuación podría satisfacer sus necesidades.

Aunque esto no produce exactamente la respuesta que el autor ha expresado su deseo, ya que supone el uso de CMake , se acerca bastante. Es una pena que esto no haya sido mencionado anteriormente por nadie, ya que me habría ahorrado mucho tiempo.

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

Establecer la variable anterior ONgenerará un comando de compilación en el formato:

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

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

CMake documentación

Sin embargo, tenga cuidado con la advertencia en la página de documentación:

Use rutas relativas (¡Puede que no funcione!).

No se garantiza que funcione en todos los casos, pero funcionó en el mío - CMake 3.13 + gcc 4.5


La documentación de CMake 3.4+ establece que "Esta variable no tiene ningún efecto. El efecto parcialmente implementado que tenía en versiones anteriores se eliminó en CMake 3.4". Si funcionó para usted con 3.13, hay otra razón para eso.
mike.dld

0

Dado que está utilizando GCC, puede aprovechar las ventajas de

__BASE_FILE__Esta macro se expande al nombre del archivo de entrada principal, en forma de una constante de cadena C. Este es el archivo fuente que se especificó en la línea de comando del preprocesador o compilador de C

y luego controle cómo desea mostrar el nombre del archivo cambiando la representación del archivo fuente (ruta completa / ruta relativa / nombre base) en el momento de la compilación.


66
No hace diferencia. Solía __FILE__y, __BASE_FILE__sin embargo, ambos muestran la ruta completa al archivo
mahmood

¿Cómo invocas al compilador?
ziu

2
Entonces apuesto a que SCONS está llamando a gcc de esta manera gcc /absolute/path/to/file.c. Si encuentra una manera de cambiar este comportamiento (abriendo otra pregunta sobre SO, ¿jaja?), No necesita modificar la cadena en tiempo de ejecución
ziu

17
Esta respuesta es 100% incorrecta. __BASE_FILE__(como dicen los documentos, aunque no sea claro) produce el nombre del archivo especificado en la línea de comando , por ejemplo, test.co /tmp/test.cdependiendo de cómo invocó el compilador. Eso es exactamente lo mismo que __FILE__, excepto si está dentro de un archivo de encabezado, en cuyo caso __FILE__produce el nombre del archivo actual (por ejemplo foo.h) mientras __BASE_FILE__continúa produciendo test.c.
Quuxplusone

0

Aquí hay una solución que funciona para entornos que no tienen la biblioteca de cadenas (kernel de Linux, sistemas integrados, 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; })

Ahora solo use en FILENAMElugar de __FILENAME__. Sí, todavía es una cosa de tiempo de ejecución pero funciona.


Es posible que desee marcar '/' y '\', dependiendo de la plataforma.
Technophile

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 respuesta breve y funcional para Windows y * nix:

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

¿Por qué necesitas en std::max<const char*>lugar de solo std::max?
chqrlie
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.