El preprocesador C es el preprocesador de macros para varios lenguajes de programación de computadoras , como C , Objective-C , C++ y una variedad de lenguajes Fortran . El preprocesador proporciona la inclusión de archivos de encabezado , expansiones de macros , compilación condicional y control de línea.
El lenguaje de las directivas del preprocesador sólo está débilmente relacionado con la gramática de C, por lo que a veces se utiliza para procesar otros tipos de archivos de texto . [1]
El preprocesador se introdujo en C alrededor de 1973 a instancias de Alan Snyder y también en reconocimiento de la utilidad de los mecanismos de inclusión de archivos disponibles en BCPL y PL/I . Su versión original solo ofrecía inclusión de archivos y reemplazo simple de cadenas usando #include
y #define
para macros sin parámetros, respectivamente. Fue ampliado poco después, primero por Mike Lesk y luego por John Reiser, para incorporar macros con argumentos y compilación condicional. [2]
El preprocesador C era parte de una larga tradición de macrolenguaje en los Laboratorios Bell, iniciada por Douglas Eastwood y Douglas McIlroy en 1959. [3]
El preprocesamiento se define por las primeras cuatro (de ocho) fases de traducción especificadas en el Estándar C.
_Pragma
operadores.Uno de los usos más comunes del preprocesador es incluir otro archivo fuente:
#incluir <stdio.h> int main ( void ) { printf ( "¡Hola mundo! \n " ); devolver 0 ; }
El preprocesador reemplaza la línea #include <stdio.h>
con el contenido textual del archivo 'stdio.h', que declara la printf()
función, entre otras cosas.
Esto también se puede escribir usando comillas dobles, por ejemplo #include "stdio.h"
. Si el nombre del archivo está entre corchetes angulares, el archivo se busca en las rutas de inclusión estándar del compilador. Si el nombre del archivo está entre comillas dobles, la ruta de búsqueda se expande para incluir el directorio del archivo fuente actual. Todos los compiladores de C y los entornos de programación tienen una función que permite al programador definir dónde se pueden encontrar los archivos de inclusión. Esto se puede introducir a través de un indicador de línea de comandos, que se puede parametrizar mediante un archivo MAKE , de modo que se pueda intercambiar un conjunto diferente de archivos de inclusión para diferentes sistemas operativos, por ejemplo.
Por convención, los archivos de inclusión reciben nombres con una extensión .h
o .hpp
. Sin embargo, no existe ningún requisito para que esto se cumpla. Los archivos con una .def
extensión pueden indicar archivos diseñados para incluirse varias veces, expandiendo cada vez el mismo contenido repetitivo; #include "icon.xbm"
Es probable que se refiera a un archivo de imagen XBM (que es al mismo tiempo un archivo fuente C).
#include
obliga a menudo al uso de #include
guardas o #pragma once
para evitar la doble inclusión.
Las directivas if-else#if
, #ifdef
, #ifndef
, #else
, #elif
y #endif
se pueden utilizar para la compilación condicional . #ifdef
y #ifndef
son abreviaturas simples de #if defined(...)
y #if !defined(...)
.
#if VERBOSE >= 2 printf ( "mensaje de seguimiento" ); #terminara si
La mayoría de los compiladores destinados a Microsoft Windows definen implícitamente _WIN32
. [4] Esto permite que el código, incluidos los comandos del preprocesador, se compile solo cuando se dirige a sistemas Windows. Algunos compiladores definen WIN32
en su lugar. Para compiladores que no definen implícitamente la _WIN32
macro, se puede especificar en la línea de comando del compilador, usando -D_WIN32
.
#ifdef __unix__ /* __unix__ generalmente lo definen compiladores destinados a sistemas Unix */ # include <unistd.h> #elif definido _WIN32 /* _WIN32 generalmente lo definen compiladores destinados a sistemas Windows de 32 o 64 bits */ # include <windows.h > #endif
El código de ejemplo prueba si __unix__
hay una macro definida. Si es así, <unistd.h>
se incluye el archivo. De lo contrario, prueba si _WIN32
en su lugar se define una macro. Si es así, <windows.h>
se incluye el archivo.
Un ejemplo más complejo #if
puede utilizar operadores; Por ejemplo:
#si !(definido __LP64__ || definido __LLP64__) || definido _WIN32 && !definido _WIN64 // estamos compilando para un sistema de 32 bits #else // estamos compilando para un sistema de 64 bits #endif
La traducción también puede fallar mediante el uso de la #error
directiva:
#if RUBY_VERSION == 190 #error 1.9.0 no compatible #endif
Hay dos tipos de macros: de tipo objeto y de función . Las macros similares a objetos no toman parámetros; las macros similares a funciones lo hacen (aunque la lista de parámetros puede estar vacía). La sintaxis genérica para declarar un identificador como macro de cada tipo es, respectivamente:
#define <identificador> <lista de tokens de reemplazo> // macro similar a un objeto #define <identificador>(<lista de parámetros>) <lista de tokens de reemplazo> // macro similar a una función, anota los parámetros
La declaración de macro similar a una función no debe tener ningún espacio en blanco entre el identificador y el primer paréntesis de apertura. Si hay espacios en blanco, la macro se interpretará como un objeto y todo lo que comience desde el primer paréntesis se agregará a la lista de tokens.
Una definición de macro se puede eliminar con #undef
:
#undef <identificador> // eliminar la macro
Siempre que el identificador aparece en el código fuente, se reemplaza con la lista de tokens de reemplazo, que puede estar vacía. Para un identificador declarado como una macro similar a una función, solo se reemplaza cuando el siguiente token también es un paréntesis izquierdo que comienza la lista de argumentos de la invocación de la macro. El procedimiento exacto seguido para la expansión de macros similares a funciones con argumentos es sutil.
Las macros similares a objetos se usaban convencionalmente como parte de buenas prácticas de programación para crear nombres simbólicos para constantes; Por ejemplo:
#definir PI 3.14159
en lugar de codificar números en todo el código. Una alternativa tanto en C como en C++, especialmente en situaciones en las que se requiere un puntero al número, es aplicar el const
calificador a una variable global. Esto hace que el valor se almacene en la memoria, en lugar de ser sustituido por el preprocesador.
Un ejemplo de una macro similar a una función es:
#definir RADTODEG(x) ((x) * 57.29578)
Esto define una conversión de radianes a grados que se puede insertar en el código cuando sea necesario; Por ejemplo, RADTODEG(34)
. Esto se expande in situ, de modo que la multiplicación repetida por la constante no se muestra en todo el código. La macro aquí está escrita en mayúsculas para enfatizar que es una macro, no una función compilada.
El segundo x
está encerrado entre su propio par de paréntesis para evitar la posibilidad de un orden incorrecto de las operaciones cuando se trata de una expresión en lugar de un valor único. Por ejemplo, la expresión se expande correctamente como ; sin paréntesis, da prioridad a la multiplicación.RADTODEG(r + 1)
((r + 1) * 57.29578)
(r + 1 * 57.29578)
De manera similar, el par exterior de paréntesis mantiene el orden correcto de operación. Por ejemplo, se expande a ; sin paréntesis, da prioridad a la división.1 / RADTODEG(r)
1 / ((r) * 57.29578)
1 / (r) * 57.29578
La macroexpansión similar a una función ocurre en las siguientes etapas:
Esto puede producir resultados sorprendentes:
#definir HE HI #definir LLO _ ALLÍ #definir HOLA "HI ALLÍ" #definir CAT(a,b) a##b #definir XCAT(a,b) CAT(a,b) #definir CALL(fn) fn(HE, LLO) CAT ( HE , LLO ) // "HI ALLÍ", porque la concatenación ocurre antes de la expansión normal XCAT ( HE , LLO ) // HI_THERE, porque los tokens que se originan a partir de los parámetros ("HE" y "LLO") se expanden primero CALL ( CAT ) // "HOLA", porque esto se evalúa como CAT(a,b)
Es necesario que una implementación defina ciertos símbolos durante el preprocesamiento. Estos incluyen __FILE__
y __LINE__
, predefinidos por el propio preprocesador, que se expanden al archivo actual y al número de línea. Por ejemplo, lo siguiente:
// depurar macros para que podamos precisar el origen del mensaje de un vistazo // es malo #define WHERESTR "[file %s, line %d]: " #define WHEREARG __FILE__, __LINE__ #define DEBUGPRINT2(...) fprintf(stderr , __VA_ARGS__) #define DEBUGPRINT(_fmt, ...) DEBUGPRINT2(WHERESTR _fmt, WHEREARG, __VA_ARGS__) // O // es bueno #define DEBUGPRINT(_fmt, ...) fprintf(stderr, "[archivo %s, línea %d]: " _fmt, __FILE__, __LINE__, __VA_ARGS__) DEBUGPRINT ( "oye, x=%d \n " , x );
imprime el valor de x
, precedido por el archivo y el número de línea en la secuencia de error, lo que permite un acceso rápido a la línea en la que se produjo el mensaje. Tenga en cuenta que el WHERESTR
argumento está concatenado con la cadena que le sigue. Los valores de __FILE__
y __LINE__
se pueden manipular con la #line
directiva. La #line
directiva determina el número de línea y el nombre de archivo de la línea siguiente. Por ejemplo:
#línea 314 "pi.c" printf ( "línea=%d archivo=%s \n " , __LINE__ , __FILE__ );
genera la printf
función:
printf ( "linea=%d archivo=%s \n " , 314 , "pi.c" );
Los depuradores de código fuente también se refieren a la posición fuente definida con __FILE__
y __LINE__
. Esto permite la depuración del código fuente cuando se utiliza C como lenguaje de destino de un compilador, para un lenguaje totalmente diferente. El primer Estándar C especificaba que la macro __STDC__
se definiría en 1 si la implementación se ajusta al Estándar ISO y 0 en caso contrario, y la macro __STDC_VERSION__
se definiría como un literal numérico que especificaba la versión del Estándar soportada por la implementación. Los compiladores estándar de C++ admiten la __cplusplus
macro. Los compiladores que se ejecutan en modo no estándar no deben configurar estas macros o deben definir otras para señalar las diferencias.
Otras macros estándar incluyen __DATE__
, la fecha actual y __TIME__
, la hora actual.
La segunda edición del estándar C, C99 , agregó soporte para __func__
, que contiene el nombre de la definición de función dentro de la cual está contenido, pero debido a que el preprocesador es independiente de la gramática de C, esto debe hacerse en el propio compilador usando un variable local a la función.
Las macros que pueden tomar una cantidad variable de argumentos ( macros variadas ) no están permitidas en C89, pero fueron introducidas por varios compiladores y estandarizadas en C99 . Las macros variables son particularmente útiles cuando se escriben contenedores para funciones que toman un número variable de parámetros, como printf
, por ejemplo, al registrar advertencias y errores.
Un patrón de uso poco conocido del preprocesador C se conoce como X-Macros . [5] [6] [7] Una X-Macro es un archivo de encabezado . Comúnmente estos utilizan la extensión .def
en lugar de la tradicional .h
. Este archivo contiene una lista de llamadas de macros similares, a las que se puede hacer referencia como "macros de componentes". Luego se hace referencia repetidamente al archivo de inclusión.
Muchos compiladores definen macros adicionales no estándar, aunque a menudo están mal documentadas. Una referencia común para estas macros es el proyecto Macros de compilador C/C++ predefinidas, que enumera "varias macros de compilador predefinidas que se pueden usar para identificar estándares, compiladores, sistemas operativos, arquitecturas de hardware e incluso bibliotecas básicas en tiempo de ejecución". en tiempo de compilación."
El #
operador (conocido como operador de encadenamiento u operador de encadenamiento ) convierte un token en un literal de cadena C , escapando adecuadamente de las comillas o barras invertidas.
Ejemplo:
#definir cadena(s) #sstr ( p = "foo \n " ;) // genera "p = \"foo\\n\";" str ( \ n ) // genera "\n"
Si se desea encadenar la expansión de un argumento macro, se deben utilizar dos niveles de macros:
#definir xstr(s) str(s) #definir str(s) #s #definir foo 4str ( foo ) // genera "foo" xstr ( foo ) // genera "4"
Un argumento de macro no se puede combinar con texto adicional y luego codificarlo. Sin embargo, se puede escribir una serie de constantes de cadena adyacentes y argumentos encadenados: el compilador de C combinará todas las constantes de cadena adyacentes en una cadena larga.
El ##
operador (conocido como "Operador de pegado de tokens") concatena dos tokens en uno solo.
Ejemplo:
#define DECLARE_STRUCT_TYPE(nombre) typedef nombre de estructura##_s nombre##_tDECLARE_STRUCT_TYPE ( g_objeto ); // Salidas: typedef struct g_object_s g_object_t;
La #error
directiva genera un mensaje a través del flujo de error.
#error "mensaje de error"
El C23 introducirá la #embed
directiva para la inclusión de recursos binarios. [8] Esto permite incluir archivos binarios (como imágenes) en el programa sin que sean archivos fuente C válidos (como XBM), sin requerir procesamiento por herramientas externas como xxd -i
y sin el uso de cadenas literales que tienen un límite de longitud en MSVC. . De manera similar, xxd -i
la directiva se reemplaza por una lista separada por comas de números enteros correspondientes a los datos del recurso especificado. Más precisamente, si una matriz de tipo unsigned char
se inicializa usando una #embed
directiva, el resultado es el mismo que si el recurso se escribiera en la matriz usando fread
(a menos que un parámetro cambie el ancho del elemento incrustado a algo distinto de CHAR_BIT
). Además de la conveniencia, #embed
también es más fácil de manejar para los compiladores, ya que pueden omitir la expansión de la directiva a su forma completa debido a la regla como-si .
El archivo que se va a incrustar se puede especificar de forma idéntica a #include
, es decir, entre chevrones o entre comillas. La directiva también permite que se le pasen ciertos parámetros para personalizar su comportamiento, que siguen al nombre del archivo. El estándar C define los siguientes parámetros y las implementaciones pueden definir los suyos propios. El limit
parámetro se utiliza para limitar el ancho de los datos incluidos. Está pensado principalmente para usarse con archivos "infinitos" como urandom . Los parámetros prefix
y suffix
permiten al programador especificar un prefijo y un sufijo para los datos incrustados, que se utilizan si y sólo si el recurso incrustado no está vacío. Finalmente, el if_empty
parámetro reemplaza la directiva completa si el recurso está vacío (lo que sucede si el archivo está vacío o se especifica un límite de 0). Todos los parámetros estándar también pueden estar rodeados por guiones bajos dobles, al igual que los atributos estándar en C23, por ejemplo, __prefix__
es intercambiable con prefix
. Los parámetros definidos por la implementación utilizan una forma similar a la sintaxis de atributos (por ejemplo, vendor::attr
) pero sin corchetes. Si bien todos los parámetros estándar requieren que se les pase un argumento (por ejemplo, el límite requiere un ancho), esto generalmente es opcional e incluso el conjunto de paréntesis se puede omitir si no se requiere un argumento, lo que podría ser el caso de algunas implementaciones. parámetros definidos.
Todas las implementaciones de C, C++ y Objective-C proporcionan un preprocesador, ya que el preprocesamiento es un paso requerido para esos lenguajes y su comportamiento se describe en los estándares oficiales para estos lenguajes, como el estándar ISO C.
Las implementaciones pueden proporcionar sus propias extensiones y desviaciones, y variar en su grado de cumplimiento de las normas escritas. Su comportamiento exacto puede depender de los indicadores de la línea de comandos proporcionados durante la invocación. Por ejemplo, el preprocesador GNU C se puede hacer más compatible con los estándares proporcionando ciertas banderas. [9]
La #pragma
directiva es específica del compilador , que los proveedores de compiladores pueden utilizar para sus propios fines. Por ejemplo, a #pragma
se utiliza a menudo para permitir la supresión de mensajes de error específicos, gestionar la depuración del montón y de la pila, etc. Un compilador compatible con la biblioteca de paralelización OpenMP puede paralelizar automáticamente un for
bucle con #pragma omp parallel for
.
C99 introdujo algunas #pragma
directivas estándar, tomando la forma #pragma STDC ...
, que se utilizan para controlar la implementación de punto flotante. _Pragma(...)
También se agregó la forma alternativa, similar a una macro .
#warning
a la norma para este propósito). Un uso típico es advertir sobre el uso de algún código antiguo, que ahora está en desuso y solo se incluye por razones de compatibilidad; Por ejemplo:// GNU, Intel e IBM #advertencia "No utilice ABC, que está en desuso. Utilice XYZ en su lugar".
// Microsoft #pragma message("No utilice ABC, que está en desuso. Utilice XYZ en su lugar.")
#include_next
encadenar encabezados con el mismo nombre. [13]Hay algunas directivas de preprocesador que se han agregado al preprocesador C mediante las especificaciones de algunos lenguajes y son específicas de dichos lenguajes.
#import
, que es similar #include
pero solo incluye el archivo una vez. Un pragma común de los proveedores con una funcionalidad similar en C es #pragma once
.#
carácter; en su lugar, comienzan con import
y module
respectivamente, precedidos opcionalmente por export
.Como el preprocesador de C se puede invocar por separado del compilador con el que se suministra, se puede utilizar por separado en diferentes lenguajes. Ejemplos notables incluyen su uso en el ahora obsoleto sistema imake y para el preprocesamiento de Fortran . Sin embargo, su uso como preprocesador de propósito general es limitado: el lenguaje de entrada debe ser suficientemente parecido a C. [9] El compilador GNU Fortran llama automáticamente al "modo tradicional" (ver más abajo) cpp antes de compilar el código Fortran si se utilizan ciertas extensiones de archivo. [16] Intel ofrece un preprocesador Fortran, fpp, para usar con el compilador ifort , que tiene capacidades similares. [17]
CPP también funciona aceptablemente con la mayoría de los lenguajes ensambladores y lenguajes similares a Algol. Esto requiere que la sintaxis del lenguaje no entre en conflicto con la sintaxis de CPP, lo que significa que no hay líneas que comiencen con #
y que las comillas dobles, que cpp interpreta como cadenas literales y, por lo tanto, ignora, no tengan otro significado sintáctico que ese. El "modo tradicional" (que actúa como un preprocesador anterior a ISO C) es generalmente más permisivo y más adecuado para dicho uso. [18]
El preprocesador de C no es Turing completo , pero se acerca mucho: se pueden especificar cálculos recursivos, pero con un límite superior fijo en la cantidad de recursividad realizada. [19] Sin embargo, el preprocesador C no está diseñado para ser, ni funciona bien, un lenguaje de programación de propósito general. Como el preprocesador de C no tiene características de otros preprocesadores, como macros recursivas, expansión selectiva según comillas y evaluación de cadenas en condicionales, es muy limitado en comparación con un macroprocesador más general como m4 .
{{cite journal}}
: Cite journal requires |journal=
(help)Having said that, you can often get away with using cpp on things which are not C. Other Algol-ish programming languages are often safe (Ada, etc.) So is assembly, with caution. -traditional-cpp mode preserves more white space, and is otherwise more permissive. Many of the problems can be avoided by writing C or C++ style comments instead of native language comments, and keeping macros simple.