El preprocesador de C es el preprocesador de macros para los lenguajes de programación de computadoras C , Objective-C y C ++ . El preprocesador proporciona la capacidad para la inclusión de archivos de encabezado , expansiones de macros , compilación condicional y control de línea.
En muchas implementaciones de C, es un programa separado invocado por el compilador como la primera parte de la traducción .
El lenguaje de las directivas del preprocesador 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]
Historia
Preprocesador se introdujo a C alrededor de 1973, a instancias de Alan Snyder y también en el reconocimiento de la utilidad de los mecanismos de inclusión de archivos disponibles en BCPL y PL / I . Su versión original permitía solo incluir archivos y realizar reemplazos simples de cadenas: #include
y #define
de macros sin parámetros. Poco después, fue ampliado, principalmente por Mike Lesk y luego por John Reiser, para incorporar macros con argumentos y compilación condicional. [2]
El preprocesador de C era parte de una larga tradición de macrolenguaje en Bell Labs, que fue iniciada por Douglas Eastwood y Douglas McIlroy en 1959. [3]
Etapas
El preprocesamiento está definido por las primeras cuatro (de ocho) fases de traducción especificadas en el Estándar C.
- Reemplazo de trígrafos : el preprocesador reemplaza las secuencias de trígrafos con los caracteres que representan.
- Empalme de línea: las líneas de origen físico que continúan con secuencias de nueva línea de escape se empalman para formar líneas lógicas.
- Tokenización: el preprocesador divide el resultado en tokens de preprocesamiento y espacios en blanco . Reemplaza los comentarios con espacios en blanco.
- Expansión de macros y manejo de directivas: se ejecutan las líneas de directiva de preprocesamiento, incluida la inclusión de archivos y la compilación condicional. El preprocesador expande simultáneamente las macros y, desde la versión 1999 del estándar C, maneja los
_Pragma
operadores.
Incluyendo archivos
Uno de los usos más comunes del preprocesador es incluir otro archivo:
#include int main ( void ) { printf ( "¡Hola, mundo! \ n " ); return 0 ; }
El preprocesador reemplaza la línea #include
con el contenido textual del archivo 'stdio.h', que declara la printf()
función entre otras cosas.
Esto también se puede escribir con comillas dobles, por ejemplo #include "stdio.h"
. Si el nombre del archivo está encerrado entre corchetes angulares, el archivo se busca en las rutas de inclusión del compilador estándar. Si el nombre del archivo está entre comillas dobles, la ruta de búsqueda se expande para incluir el directorio del archivo de origen actual. 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 mediante un indicador de línea de comandos, que se puede parametrizar mediante un archivo MAKE , de modo que, por ejemplo, se pueda intercambiar un conjunto diferente de archivos de inclusión para diferentes sistemas operativos.
Por convención, incluyen archivos se denominan ya sea con un .h o .hpp extensión. Sin embargo, no existe ningún requisito de que esto se cumpla. Los archivos con una extensión .def pueden indicar archivos diseñados para ser incluidos varias veces, cada vez expandiendo 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
a menudo obliga al uso de #include
guardias o #pragma once
para evitar la doble inclusión.
Compilación condicional
Los si-si no directivas #if
, #ifdef
, #ifndef
, #else
, #elif
y #endif
pueden ser utilizados para la compilación condicional . #ifdef
y #ifndef
son abreviaturas simples de #if defined(...)
y #if !defined(...)
.
#if VERBOSO> = 2 printf ( "mensaje de seguimiento" ); #terminara si
La mayoría de los compiladores que apuntan 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. En su lugar, algunos compiladores definen WIN32
. Para los compiladores que no definen implícitamente la _WIN32
macro, se puede especificar en la línea de comandos del compilador, utilizando -D_WIN32
.
#ifdef __unix__ / * __unix__ generalmente es definido por compiladores que apuntan a sistemas Unix * /# include #elif definido _WIN32 / * _WIN32 generalmente lo definen los compiladores que tienen como objetivo sistemas Windows de 32 o 64 bits * /# include #endif
El código de ejemplo prueba si __unix__
se define una macro . Si es así,
se incluye el archivo . De lo contrario, prueba si _WIN32
se define una macro en su lugar. Si es así,
se incluye el archivo .
Un #if
ejemplo más complejo puede usar operadores, por ejemplo, algo como:
#if! (definido __LP64__ || definido __LLP64__) || definido _WIN32 &&! defined _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 admitido #endif
Definición y expansión de macros
Hay dos tipos de macros, de tipo objeto y de función . Las macros de tipo objeto no toman parámetros; las macros de función sí lo hacen (aunque la lista de parámetros puede estar vacía). La sintaxis genérica para declarar un identificador como una macro de cada tipo es, respectivamente:
#define // macro similar a un objeto #define () // macro similar a una función, parámetros de nota
La declaración de macro de 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 con todo comenzando desde el primer paréntesis agregado a la lista de tokens.
Una definición de macro se puede eliminar con #undef
:
#undef // 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 de función, solo se reemplaza cuando el siguiente token es también 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 de funciones con argumentos es sutil.
Las macros de tipo objeto se usaban convencionalmente como parte de una buena práctica de programación para crear nombres simbólicos para constantes, por ejemplo,
#define 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:
#define RADTODEG (x) ((x) * 57.29578)
Esto define una radianes conversión -to-grados que se puede insertar en el código cuando se requiera, es decir, 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
se incluye en su propio par de paréntesis para evitar la posibilidad de un orden incorrecto de 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)
Del mismo modo, el par de paréntesis exterior mantiene el orden correcto de funcionamiento. 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
Orden de expansión
La macro expansión similar a una función ocurre en las siguientes etapas:
- Las operaciones de encadenamiento se reemplazan con la representación textual de la lista de reemplazo de su argumento (sin realizar la expansión).
- Los parámetros se reemplazan con su lista de reemplazo (sin realizar la expansión).
- Las operaciones de concatenación se reemplazan con el resultado concatenado de los dos operandos (sin expandir el token resultante).
- Los tokens que se originan a partir de parámetros se expanden.
- Los tokens resultantes se expanden normalmente.
Esto puede producir resultados sorprendentes:
#define HE HI #define LLO _THERE #define HELLO "HI THERE" #define CAT (a, b) a ## b #define XCAT (a, b) CAT (a, b) #define CALL (fn) fn (HE, LLO) CAT ( HE , LLO ) // "HI THERE", 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 ) // "HI THERE", porque los parámetros se expanden primero
Directivas y macros especiales
Ciertos símbolos deben ser definidos por una implementación durante el preprocesamiento. Estos incluyen __FILE__
y __LINE__
, predefinidos por el preprocesador mismo, que se expanden al archivo actual y al número de línea. Por ejemplo lo siguiente:
// depurando 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 ( "hey, x =% d \ n " , x );
imprime el valor de x
, precedido por el archivo y el número de línea en el flujo 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. P.ej:
#line 314 "pi.c" printf ( "línea =% d archivo =% s \ n " , __LINE__ , __FILE__ );
genera la función printf:
printf ( "línea =% 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 usa C como el idioma de destino de un compilador, para un idioma totalmente diferente. La primera Norma C especificaba que la macro __STDC__
se definiera en 1 si la implementación se ajustaba a la Norma ISO y 0 en caso contrario, y la macro se __STDC_VERSION__
definía como un literal numérico que especificaba la versión de la Norma admitida 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 establecer 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á contenida, 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 un número variable de argumentos ( macros variadic ) 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 envoltorios en funciones que toman un número variable de parámetros, como printf
, por ejemplo, cuando se registran advertencias y errores.
Un patrón de uso poco conocido del preprocesador de C se conoce como X-Macros . [5] [6] [7] Una X-Macro es un archivo de encabezado . Por lo general, estos utilizan la extensión ".def" en lugar de la tradicional ".h". Este archivo contiene una lista de llamadas de macro similares, que se pueden denominar "macros de componentes". A continuación, se hace referencia al archivo de inclusión repetidamente.
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 de macros de compilador de 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 de tiempo de ejecución básicas en tiempo de compilación ".
Cadena de tokens
El operador # (conocido como "Operador de cadena de caracteres ") convierte un token en un literal de cadena C , evitando las comillas o barras diagonales inversas de forma adecuada.
Ejemplo:
#define str (s) #sstr ( p = "foo \ n " ;) // genera "p = \" foo \\ n \ ";" str ( \ n ) // genera "\ n"
Si desea secuenciar la expansión de un argumento de macro, debe usar dos niveles de macros:
#define xstr (s) str (s) #define str (s) #s #define foo 4str ( foo ) // genera "foo" xstr ( foo ) // genera "4"
No puede combinar un argumento de macro con texto adicional y secuenciarlo todo junto. Sin embargo, puede escribir una serie de constantes de cadena adyacentes y argumentos en cadena: el compilador de C combinará todas las constantes de cadena adyacentes en una cadena larga.
Concatenación de tokens
El operador ## (conocido como "Operador de pegado de tokens") concatena dos tokens en uno.
Ejemplo:
#define DECLARE_STRUCT_TYPE (nombre) typedef nombre de estructura ## _ s nombre ## _ tDECLARE_STRUCT_TYPE ( g_object ); // Resultados: typedef struct g_object_s g_object_t;
Errores de compilación definidos por el usuario
La #error
directiva genera un mensaje a través del flujo de errores.
#error "mensaje de error"
Implementaciones
Todas las implementaciones de C, C ++ y Objective-C proporcionan un preprocesador, ya que el preprocesamiento es un paso obligatorio 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 con los estándares escritos. Su comportamiento exacto puede depender de los indicadores de la línea de comandos suministrados en la invocación. Por ejemplo, el preprocesador GNU C se puede hacer más compatible con los estándares proporcionando ciertos indicadores. [8]
Características del preprocesador específicas del compilador
La #pragma
directiva es una directiva específica del compilador , que los proveedores de compiladores pueden utilizar para sus propios fines. Por ejemplo, a #pragma
se usa a menudo para permitir la supresión de mensajes de error específicos, administrar el montón y la depuración 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 el formulario #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 .
- Muchas implementaciones no admiten trígrafos o no los reemplazan de forma predeterminada.
- Muchas implementaciones (incluidos, por ejemplo, los compiladores de C de GNU, Intel, Microsoft e IBM) proporcionan una directiva no estándar para imprimir un mensaje de advertencia en la salida, pero sin detener el proceso de compilación. 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".
// Mensaje de Microsoft #pragma ("No utilice ABC, que está obsoleto. Utilice XYZ en su lugar").
- Algunos preprocesadores de Unix proporcionaban tradicionalmente "aserciones", que tienen poca similitud con las aserciones utilizadas en programación. [9]
- GCC permite
#include_next
encadenar encabezados con el mismo nombre. [10] - Los preprocesadores de Objective-C tienen
#import
, que es similar#include
pero solo incluye el archivo una vez. Un pragma de proveedor común con una funcionalidad similar en C es #pragma once .
Otros usos
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 preprocesar Fortran . Sin embargo, dicho uso como preprocesador de propósito general es limitado: el lenguaje de entrada debe ser suficientemente similar a C. [8] 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. [11] Intel ofrece un preprocesador Fortran, fpp, para usar con el compilador ifort , que tiene capacidades similares. [12]
CPP también funciona aceptablemente con la mayoría de lenguajes ensambladores y lenguajes tipo Algol. Esto requiere que la sintaxis del lenguaje no entre en conflicto con la sintaxis 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 tienen otro significado sintáctico. El "modo tradicional" (que actúa como un preprocesador anterior a ISO C) es generalmente más permisivo y más adecuado para tal uso. [13] Se prefiere una variante más flexible del preprocesador de C llamada GPP para casos más complejos. [14]
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. [15] Sin embargo, el preprocesador de C no está diseñado para ser, ni funciona bien, como un lenguaje de programación de propósito general. Como el preprocesador de C no tiene características de algunos otros preprocesadores, como macros recursivas, expansión selectiva según cotización y evaluación de cadenas en condicionales, es muy limitado en comparación con un macroprocesador más general como m4 .
Ver también
- Sintaxis C
- Fabricar
- Preprocesador
- m4 (lenguaje de computadora)
- Preprocesador PL / I
Referencias
- ^ Preprocesamiento de texto de uso general con el preprocesador de C. Con JavaScript
- ^ Ritchie (1993)
- ^ "Bell SAP - SAP con macros condicionales y recursivas" . HOPL: Enciclopedia histórica en línea de lenguajes de programación .
- ^ Lista de macros de implementación ANSI C y Microsoft C ++ predefinidas.
- ^ Wirzenius, Lars. C "Truco del preprocesador para implementar tipos de datos similares". Consultado el 9 de enero de 2011
- ^ Meyers, Randy (mayo de 2001). "Las nuevas macros de C: X" . Diario del Dr. Dobb . Consultado el 1 de mayo de 2008 .
- ^ Beal, Stephan (agosto de 2004). "Supermacros" . Consultado el 27 de octubre de 2008 . Cite journal requiere
|journal=
( ayuda ) - ^ a b "El preprocesador C: descripción general" . Consultado el 17 de julio de 2016 .
- ^ Características obsoletas de GCC
- ^ https://gcc.gnu.org/onlinedocs/cpp/Wrapper-Headers.html
- ^ "1.3 Preprocesamiento y compilación condicional" . gnu.org.
- ^ "Uso del preprocesador fpp" . Intel . Consultado el 14 de octubre de 2015 .
- ^ "Descripción general (el preprocesador C)" . gcc.gnu.org .
Habiendo dicho eso, a menudo puede salirse con la suya usando cpp en cosas que no son C. Otros lenguajes de programación tipo Algol son a menudo seguros (Ada, etc.) También lo es el ensamblaje, con precaución. El modo -traditional-cpp conserva más espacios en blanco y, por lo demás, es más permisivo. Muchos de los problemas se pueden evitar escribiendo comentarios de estilo C o C ++ en lugar de comentarios en el idioma nativo y manteniendo las macros simples.
- ^ "GPP 2.23 - Preprocesador genérico" . Consultado el 17 de julio de 2016 .
- ^ "¿El preprocesador C99 Turing está completo?" . Archivado desde el original el 24 de abril de 2016.
Fuentes
- Ritchie, Dennis M. (marzo de 1993). "El desarrollo del lenguaje C". Avisos ACM SIGPLAN . ACM. 28 (3): 201-208. doi : 10.1145 / 155360.155580 .
Ritchie, Dennis M. (1993). "El desarrollo del lenguaje C" . La Segunda Conferencia ACM SIGPLAN sobre Historia de los Lenguajes de Programación (HOPL-II) . ACM . págs. 201–208. doi : 10.1145 / 154766.155580 . ISBN 0-89791-570-4. Consultado el 4 de noviembre de 2014 .
enlaces externos
- ISO / IEC 9899 . El estándar C oficial. A partir de 2014, la última versión disponible públicamente es un documento de trabajo para C11 .
- Manual en línea de GNU CPP
- Referencia del preprocesador de Visual Studio .NET
- Proyecto de macros de compilador de C / C ++ predefinidas : enumera "varias macros de compilador predefinidas que se pueden utilizar 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"