stringtranslate.com

preprocesador C

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]

Historia

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 #includey #definepara 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]

Etapas

El preprocesamiento se define por las primeras cuatro (de ocho) fases de traducción especificadas en el Estándar C.

  1. Reemplazo de trígrafos: el preprocesador reemplaza las secuencias de trígrafos con los caracteres que representan. Esta fase se eliminará en C23 siguiendo los pasos de C++17 .
  2. Empalme de líneas: las líneas de origen físicas que continúan con secuencias de nueva línea escapadas se empalman para formar líneas lógicas.
  3. Tokenización: el preprocesador divide el resultado en tokens de preprocesamiento y espacios en blanco . Reemplaza los comentarios con espacios en blanco.
  4. Expansión de macros y manejo de directivas: se ejecutan líneas de directivas de preprocesamiento, incluida la inclusión de archivos y la compilación condicional. El preprocesador simultáneamente expande macros y, desde la versión 1999 del estándar C, maneja _Pragmaoperadores.

Incluyendo archivos

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 .ho .hpp. Sin embargo, no existe ningún requisito para que esto se cumpla. Los archivos con una .defextensió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).

#includeobliga a menudo al uso de #includeguardas o #pragma oncepara evitar la doble inclusión.

Compilación condicional

Las directivas if-else#if , #ifdef, #ifndef, #else, #elify #endifse pueden utilizar para la compilación condicional . #ifdefy #ifndefson 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 WIN32en su lugar. Para compiladores que no definen implícitamente la _WIN32macro, 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 _WIN32en su lugar se define una macro. Si es así, <windows.h>se incluye el archivo.

Un ejemplo más complejo #ifpuede 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 #errordirectiva:

#if RUBY_VERSION == 190 #error 1.9.0 no compatible #endif

Definición y expansión de macros.

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 constcalificador 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 xestá 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

Orden de expansión

La macroexpansión similar a una función ocurre en las siguientes etapas:

  1. Las operaciones de stringificación se reemplazan con la representación textual de la lista de reemplazo de sus argumentos (sin realizar expansión).
  2. Los parámetros se reemplazan con su lista de reemplazo (sin realizar expansión).
  3. Las operaciones de concatenación se reemplazan con el resultado concatenado de los dos operandos (sin expandir el token resultante).
  4. Los tokens que se originan a partir de parámetros se expanden.
  5. Los tokens resultantes se expanden normalmente.

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)     

Macros y directivas especiales

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 WHERESTRargumento está concatenado con la cadena que le sigue. Los valores de __FILE__y __LINE__se pueden manipular con la #linedirectiva. La #linedirectiva 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 printffunció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 __cplusplusmacro. 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 .defen 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."

Cadena de tokens

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.

Concatenación de tokens

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; 

Errores de compilación definidos por el usuario

La #errordirectiva genera un mensaje a través del flujo de error.

#error "mensaje de error"

Inclusión de recursos binarios

El C23 introducirá la #embeddirectiva 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 -iy sin el uso de cadenas literales que tienen un límite de longitud en MSVC. . De manera similar, xxd -ila 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 charse inicializa usando una #embeddirectiva, 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, #embedtambié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 limitparámetro se utiliza para limitar el ancho de los datos incluidos. Está pensado principalmente para usarse con archivos "infinitos" como urandom . Los parámetros prefixy suffixpermiten 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_emptypará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.

Implementaciones

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]

Funciones de preprocesador específicas del compilador

La #pragmadirectiva es específica del compilador , que los proveedores de compiladores pueden utilizar para sus propios fines. Por ejemplo, a #pragmase 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 forbucle con #pragma omp parallel for.

C99 introdujo algunas #pragmadirectivas 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 .

Funciones de preprocesador específicas del idioma

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.

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 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 .

Ver también

Referencias

  1. ^ Preprocesamiento de texto de uso general con el preprocesador C. Incluye JavaScript
  2. ^ Ritchie (1993)
  3. ^ "Bell SAP - SAP con macros condicionales y recursivas". HOPL: Enciclopedia histórica en línea de lenguajes de programación .
  4. ^ Lista de macros de implementación ANSI C y Microsoft C++ predefinidas.
  5. ^ Wirzenius, Lars. C "Truco del preprocesador para implementar tipos de datos similares". Consultado el 9 de enero de 2011.
  6. ^ Meyers, Randy (mayo de 2001). "Las nuevas macros de C: X". Diario del Dr. Dobb . Consultado el 1 de mayo de 2008 .
  7. ^ Beal, Stephan (agosto de 2004). "Supermacros" . Consultado el 27 de octubre de 2008 . {{cite journal}}: Cite journal requires |journal= (help)
  8. ^ "WG14-N3017 : #embed – a scannable, tooling-friendly binary resource inclusion mechanism". open-std.org. 27 June 2022. Archived from the original on 24 December 2022.
  9. ^ a b "The C Preprocessor: Overview". Retrieved 17 July 2016.
  10. ^ "WG14-N3096 : Draft for ISO/IEC 9899:2023" (PDF). open-std.org. 1 April 2023. Archived (PDF) from the original on 2 April 2023.
  11. ^ "Working Draft, Standard for Programming Language C++" (PDF). 22 March 2023.
  12. ^ GCC Obsolete features
  13. ^ "Wrapper Headers (The C Preprocessor)".
  14. ^ "N4720: Working Draft, Extensions to C++ for Modules" (PDF). Archived (PDF) from the original on 30 April 2019.
  15. ^ "P1857R1 – Modules Dependency Discovery".
  16. ^ "1.3 Preprocessing and conditional compilation". GNU Project.
  17. ^ "Using the fpp Preprocessor". Intel. Retrieved 14 October 2015.
  18. ^ "Overview (The C Preprocessor)". gcc.gnu.org. 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.
  19. ^ "Is the C99 preprocessor Turing complete?". Archived from the original on 24 April 2016.

Sources

External links