Articulo de referencia

Preprocesador de C

El preprocesador de C ( CPP ) es un procesador de archivos de texto que se utiliza con C , C++ y otras herramientas de programación . El preprocesador permite la inclusión de ar...

El preprocesador de C ( CPP ) es un procesador de archivos de texto que se utiliza con C , C++ y otras herramientas de programación . El preprocesador permite la inclusión de archivos (a menudo archivos de cabecera ), la expansión de macros , la compilación condicional y el control de líneas. Aunque su nombre está asociado a C y se utiliza con C, las capacidades del preprocesador no están intrínsecamente ligadas al lenguaje C. Puede utilizarse, y de hecho se utiliza, para procesar otros tipos de archivos. [ 1 ]

Idiomas

Los compiladores de C, C++ y Objective-C proporcionan una capacidad de preprocesador, tal como lo exige la definición de cada lenguaje. Algunos compiladores ofrecen extensiones y desviaciones del estándar del lenguaje de destino. Otros ofrecen opciones para controlar el cumplimiento de los estándares. Por ejemplo, el preprocesador GNU C puede hacerse más compatible con los estándares mediante el uso de ciertas opciones de línea de comandos. [ 2 ]

El lenguaje de programación C# también permite directivas , aunque no son leídas por un preprocesador y no se pueden usar para crear macros, y generalmente están más destinadas a características como la compilación condicional. [ 3 ] C# rara vez requiere el uso de directivas; por ejemplo, la inclusión de código no requiere ningún preprocesador (ya que C# se basa en un sistema de paquetes/espacios de nombres como Java, no es necesario "incluir" código). De manera similar, F# y Visual J# pueden llamar a estas directivas del preprocesador de C#.

El lenguaje de programación Haskell también permite el uso del preprocesador de C.

Las características del preprocesador están codificadas en el código fuente como directivas que comienzan con #.

Aunque los archivos fuente de C++ a menudo se nombran con la extensión .cpp , esa es una abreviatura de "C plus plus"; no de preprocesador de C.

Directivas del preprocesador

Los siguientes idiomas tienen las siguientes directivas aceptadas.

C/C++

Los siguientes tokens son reconocidos por el preprocesador en el contexto de las directivas del preprocesador.

  • #if
  • #elif
  • #else
  • #endif
  • #ifdef
  • #ifndef
  • #elifdef
  • #elifndef
  • #define
  • #undef
  • #include
  • #embed
  • #line
  • #error
  • #warning
  • #pragma
  • defined(sigue una directiva condicional; en realidad no es una directiva, sino un operador)
  • __has_include(operador)
  • __has_cpp_attribute(operador)
  • __has_c_attribute(operador)
  • __has_embed(operador)

Hasta C++26 , las palabras clave de C++ import, export, y moduletambién eran manejadas parcialmente por el preprocesador.

El lenguaje de programación Haskell también acepta directivas de preprocesador de C, que se invocan escribiendo {-# LANGUAGE CPP #-}al principio del archivo. Las directivas de preprocesador aceptadas coinciden con las del estándar C/C++.

DO#

Aunque C#, F# [ 4 ] y Visual J# no tienen un preprocesador separado, estas directivas se procesan como si lo tuvieran.

  • #nullable
  • #if
  • #elif
  • #else
  • #endif
  • #define
  • #undef
  • #region
  • #endregion
  • #error
  • #warning
  • #line
  • #pragma

C# no utiliza un preprocesador para gestionar estas directivas, por lo que no son gestionadas ni eliminadas por un preprocesador, sino que son leídas directamente por el compilador de C# como una característica del lenguaje.

Objetivo-C

Los siguientes tokens son reconocidos por el preprocesador en el contexto de las directivas del preprocesador.

  • #if
  • #elif
  • #else
  • #endif
  • #ifdef
  • #ifndef
  • #define
  • #undef
  • #include
  • #import
  • #error
  • #pragma
  • defined

Rápido

Los siguientes tokens, denominados "directivas del compilador", son detectados por el compilador. No son exactamente iguales a las directivas del preprocesador de C, pero tienen la misma estructura y sintaxis.

  • #if
    • arch(...)
    • canImport(...)
    • compiler(...)
    • hasAttribute(...)
    • hasFeature(...)
    • os(...)
    • swift(...)
    • targetEnvironment(...)
  • #elseif
  • #else
  • #endif
  • #available
  • #unavailable
  • #error
  • #warning
  • #file
  • #fileID
  • #filePath
  • #line
  • #column
  • #function
  • #dsohandle
  • #sourceLocation
  • #selector
  • #keyPath
  • #colorLiteral
  • #imageLiteral
  • #fileLiteral
  • #stringify
  • #externalMacro

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 . La primera versión ofrecía inclusión de archivos a través de #includey macros de reemplazo de cadenas sin parámetros a través de #define. Poco después se amplió, primero por Mike Lesk y luego por John Reiser, para agregar argumentos a las macros y para admitir la compilación condicional . [ 5 ]

El preprocesador C formaba parte de una larga tradición de lenguajes de macros en Bell Labs, que fue iniciada por Douglas Eastwood y Douglas McIlroy en 1959. [ 6 ]

Fases

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

  1. Sustitución de trigrafos: El preprocesador sustituye las secuencias de trigrafos por los caracteres que representan. Esta fase se eliminó 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 salto de 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 las líneas de directivas de preprocesamiento, incluyendo la inclusión de archivos y la compilación condicional. El preprocesador expande simultáneamente las macros y, desde la versión de 1999 del estándar C, maneja _Pragmalos operadores.

Características

Inclusión de archivos

En el preprocesador de C existen dos directivas para incluir el contenido de los archivos:

  • #include, utilizado para incluir directamente el contenido de un archivo en el mismo lugar (normalmente contiene algún tipo de código)
  • #embed, utilizado para incluir o incrustar directamente el contenido de un recurso binario en el mismo lugar.

Inclusión de código

Para incluir el contenido de un archivo en otro, el preprocesador reemplaza una línea que comienza con #includecon el contenido del archivo especificado después de la directiva. La inclusión puede ser lógica en el sentido de que el contenido resultante puede no almacenarse en el disco y ciertamente no se sobrescribe el archivo fuente. El archivo que se incluye no necesita contener ningún tipo de código, ya que esta directiva copiará el contenido del archivo incluido en su lugar, pero el uso más típico de #includees para incluir un archivo de encabezado (o, en algunos casos menos comunes, un archivo fuente).

En el siguiente código de ejemplo, el preprocesador reemplaza la línea con el contenido del archivo de cabecera de la biblioteca estándar llamado ' stdio.h ' en el que se declaran la función y otros símbolos.#include<stdio.h>printf()

#include <stdio.h>int main ( void ) { printf ( "Hola, Mundo! \n " ); return 0 ; }

En este caso, el nombre del archivo se incluye entre corchetes angulares para indicar que se trata de un archivo del sistema. Para un archivo del código fuente que se está compilando , se utilizan comillas dobles. El preprocesador puede usar un algoritmo de búsqueda diferente para encontrar el archivo según esta distinción.

En C, un archivo de cabecera suele tener la extensión .h . En C++, la convención para la extensión de archivos varía, siendo comunes las extensiones .h y .hpp . Sin embargo, el preprocesador incluye un archivo independientemente de la extensión. De hecho, a veces el código incluye archivos .c o .cpp .

Para evitar incluir el mismo archivo varias veces, lo que a menudo conduce a un error de compilación, un archivo de cabecera normalmente contiene una directiva #include guard o, si el preprocesador lo admite, #pragma once para evitar la inclusión múltiple.

Inclusión de recursos binarios

C23 y C++26 introducen la #embeddirectiva para la inclusión de recursos binarios , que permite incluir el contenido de un archivo binario en un código fuente incluso si no es código C válido. [ 7 ] [ 8 ] Esto permite que los recursos binarios (como imágenes) se incluyan en un programa sin requerir procesamiento por herramientas externas como xxd -iy sin el uso de literales de cadena , que tienen un límite de longitud en MSVC . De manera similar a xxd -i, la directiva se reemplaza por una lista separada por comas de enteros que corresponden a los datos del recurso especificado. Más precisamente, si una matriz de tipo se inicializa usando una directiva, el resultado es el mismo que si el recurso se escribiera en la matriz usando (a menos que un parámetro cambie el ancho del elemento incrustado a algo distinto de ). Además de la conveniencia, también es más fácil para los compiladores manejarlo, ya que se les permite omitir la expansión de la directiva a su forma completa debido a la regla as-if .unsignedchar#embedfreadCHAR_BIT#embed

El archivo a incrustar se especifica igual que para #include ya sea con corchetes o comillas dobles. La directiva también permite pasarle ciertos parámetros para personalizar su comportamiento. El estándar C define algunos parámetros y las implementaciones pueden definir otros adicionales. El limitparámetro se usa para limitar el ancho de los datos incluidos. Está pensado principalmente para usarse con archivos "infinitos" como urandom . Los parámetros prefixy suffixpermiten especificar un prefijo y un sufijo para los datos incrustados. Finalmente, el if_emptyparámetro reemplaza toda la directiva si el recurso está vacío. Todos los parámetros estándar pueden estar rodeados de 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 usan una forma similar a la sintaxis de atributos (por ejemplo, vendor::attr) pero sin los corchetes. Si bien todos los parámetros estándar requieren que se les pase un argumento (por ejemplo, limit 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 para algunos parámetros definidos por la implementación.

constexpr char ICON_DISPLAY_DATA [] = { #embed "art.png" };// especifique cualquier tipo que pueda inicializarse a partir de expresiones constantes enteras lo hará constexpr char RESET_BLOB [] = { #embed "data.bin" };// los atributos funcionan igual de bien alignas ( 8 ) constexpr char ALIGNED_DATA_STRING [] = { #embed "attributes.xml" };int main () { return #embed </dev/urandom> limit(1) ; }

Compilación condicional

La compilación condicional es compatible mediante las directivas principales if-else#if , #else, #elif, y #endify con las directivas de contracción #ifdefy #ifndef, que representan #if defined(...)y #if !defined(...), respectivamente. En el siguiente código de ejemplo, la printf()llamada solo se incluye para la compilación si VERBOSEestá definida.

#ifdef VERBOSE printf ( "mensaje de seguimiento" ); #endif

Lo siguiente demuestra una lógica más compleja:

#if !(defined __LP64__ || defined __LLP64__) || defined _WIN32 && !defined _WIN64 // código para un sistema de 32 bits #else // código para un sistema de 64 bits #endif

Reemplazo de cadena macro

Similar a un objeto

Una macro especifica cómo reemplazar texto en el código fuente con otro texto. Una macro de tipo objeto define un token que el preprocesador reemplaza con otro texto. No incluye sintaxis de parámetros y, por lo tanto, no admite parametrización. La siguiente definición de macro asocia el texto 1 / 12con el token VALUE:

#define VALOR 1 / 12

Similar a una función

Una macro de tipo función admite parámetros, aunque la lista de parámetros puede estar vacía. La siguiente definición de macro asocia la expresión (A + B)con el token ADDque tiene parámetros Ay B.

#define ADD(A, B) (A + B)

Una declaración de macro con características de función no puede tener espacios en blanco entre el token y el primer paréntesis de apertura. Si hay espacios en blanco, la macro se interpreta como un objeto, incluyendo todo lo que comienza en el primer paréntesis en el texto de reemplazo.

Expansión

El preprocesador reemplaza cada token del código que coincide con un token de macro con el texto de reemplazo asociado en lo que se conoce como expansión de macro . Tenga en cuenta que el texto de los literales de cadena y los comentarios no se analiza como tokens y, por lo tanto, se ignora para la expansión de macro. Para una macro de tipo función, los parámetros de la macro también se reemplazan con los valores especificados en la referencia de la macro. Por ejemplo, se expande a .ADD(VALUE,2)1/12+2

Variádico

Una macro variádica (introducida con C99 ) acepta un número variable de argumentos, lo cual es particularmente útil al encapsular funciones que aceptan un número variable de parámetros, como printf.

Orden de expansión

La expansión macro de tipo funcional se produce en las siguientes etapas:

  1. Las operaciones de conversión a cadena se reemplazan con la representación textual de la lista de reemplazo de su argumento (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 de forma normal.

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 originados por los parámetros ("HE" y "LLO") se expanden primero CALL ( CAT ) // "HI THERE", porque esto se evalúa como CAT(a,b)

Macro sin definir

Se puede eliminar una definición de macro del contexto del preprocesador de #undeftal manera que la referencia posterior al token de macro no se expanda. Por ejemplo:

#define VALOR 15// hacer cosas con VALOR...#undef VALUE // El token 'VALUE' ya no se expande desde aquí...

macros predefinidas

El preprocesador proporciona automáticamente algunas definiciones de macros. El estándar C especifica que __FILE__se expande al nombre del archivo que se está procesando y __LINE__se expande al número de la línea que contiene la directiva. La siguiente macro, DEBUGPRINT, formatea e imprime un mensaje con el nombre del archivo y el número de línea.

#define DEBUGPRINT(_fmt, ...) printf("[%s:%d]: " _fmt, __FILE__, __LINE__, __VA_ARGS__)

Para el código de ejemplo que aparece a continuación, en la línea 30 del archivo util.c y con un valor de conteo de 123, la salida es: .[util.c:30]: count=123

DEBUGPRINT ( "count=%d \n " , count );

El primer estándar C especificaba que __STDC__se expandiera a "1" si la implementación se ajustaba al estándar ISO y a "0" en caso contrario, y que __STDC_VERSION__se expandiera a un literal numérico que especificara la versión del estándar compatible con la implementación. Los compiladores estándar de C++ admiten la __cplusplusmacro. Los compiladores que se ejecutan en modo no estándar no deben establecer estas macros o deben definir otras para indicar 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 la función dentro de la cual está contenida, pero debido a que el preprocesador es agnóstico a la gramática de C, esto debe hacerse en el compilador mismo usando una variable local a la función.

Un patrón de uso poco conocido del preprocesador de C se conoce como X-Macros . [ 9 ] [ 10 ] [ 11 ] Una X-Macro es un archivo de cabecera . Comúnmente, estos usan la extensión .def en lugar de la tradicional .h . Este archivo contiene una lista de llamadas a macros similares, que pueden denominarse "macros de componentes". El archivo de inclusión se referencia repetidamente.

Muchos compiladores definen macros adicionales no estándar. Una referencia común para estas macros es el proyecto Pre-defined C/C++ Compiler Macros , 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 de tiempo de ejecución en tiempo de compilación".

La mayoría de los compiladores que se dirigen a Microsoft Windows definen implícitamente _WIN32. [ 12 ] 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 aquellos compiladores que no definen implícitamente la _WIN32macro, se puede especificar en la línea de comandos del compilador, usando -D_WIN32.

#ifdef __unix__ // __unix__ suele estar definido por compiladores que se dirigen a sistemas Unix #include <unistd.h> #elifdef _WIN32 // _WIN32 suele estar definido por compiladores que se dirigen a sistemas Windows de 32 o 64 bits #include <windows.h> #endif#ifdef __linux__ #define CURRENT_PLATFORM "Linux" #elifdef __APPLE__ #define CURRENT_PLATFORM "Apple" #elifdef _WIN32 #define CURRENT_PLATFORM "Windows" #else #define CURRENT_PLATFORM "Other" #endif

El código de ejemplo comprueba si se ha definido una macro __unix__. Si es así, <unistd.h>se incluye el archivo. De lo contrario, comprueba si _WIN32se ha definido otra macro. Si es así, <windows.h>también se incluye el archivo. Del mismo modo, dependiendo de la macro definida, se define CURRENT_PLATFORMcomo una cadena con el nombre de la plataforma del sistema.

Control de línea

Los valores de las macros predefinidas __FILE__se __LINE__pueden establecer para una línea posterior mediante la #linedirectiva. En el código siguiente, __LINE__se expande a 314 y __FILE__a " pi.c ".

#línea 314 "pi.c" printf ( "línea=%d archivo=%s \n " , __LINE__ , __FILE__ );

Operadores

El preprocesador es capaz de interpretar operadores y evaluar expresiones muy básicas, como constantes enteras, operadores aritméticos, operadores de comparación, operadores lógicos, operaciones bit a bit, el definedoperador y el #operador de conversión a cadena. Esto permite al preprocesador realizar evaluaciones como:

// si X es igual a 10, el preprocesador ve #if 10 == 10 #if X == 10

Operador definido

Si bien el operador definido , denotado por , definedno es una directiva por derecho propio, si se lee dentro de una directiva, el preprocesador lo interpreta y determina si se ha definido una macro.

Las siguientes son dos formas aceptadas de invocar al definedoperador.

#si está definido(MI_MACRO) #si está definido MI_MACRO

Operador de conversión de tokens a cadena

El operador de conversión a cadena (también conocido como operador de conversión a cadena), denotado por , #convierte un token en una cadena literal , escapando las comillas o barras invertidas según sea necesario. Para la definición:

#define STR(s) #s

STR(\n)se expande a "\n"y se expande a .STR(p="foo\n";)"p = \"foo\\n\";"

Si se desea convertir en cadena la expansión de un argumento macro, se deben usar dos niveles de macros. Para la definición:

#define XSTR(s) STR(s) #define STR(s) #s #define FOO 4

STR(FOO)se expande a "FOO"y se expande a .XSTR(FOO)"4"

Un argumento macro no se puede combinar con texto adicional y luego convertir a cadena. Sin embargo, el compilador de C concatena una serie de literales de cadena adyacentes y argumentos convertidos a cadena, que también son literales de cadena.

concatenación de tokens

El operador de pegado de tokens , denotado por ##, concatena dos tokens en uno. Por ejemplo, esto se puede usar para simplificar el código repetitivo generando métodos getter y setter , similares a los del Proyecto Lombok (en Java ). [ 13 ]

#define GETTER(type, method, name) \  type get##method() const noexcept { \  return this->name; \  }#define SETTER(type, method, name) \  void set##method(type value) noexcept { \  this->name = value; \  }usando std :: string ;clase Estudiante { privado : string nombre ; int edad ; double promedio de calificaciones ; público : // constructores...GETTER ( cadena , Nombre , nombre ) SETTER ( cadena , Nombre , nombre ) GETTER ( entero , Edad , edad ) SETTER ( entero , Edad , edad ) GETTER ( doble , Gpa , gpa ) SETTER ( doble , Gpa , gpa )// más métodos... };

Abortar

El procesamiento puede interrumpirse mediante la #errordirectiva. Por ejemplo:

#if RUBY_VERSION == 190 #error La versión 1.9.0 de Ruby no es compatible #endif

Advertencia

A partir de C23 [ 14 ] y C++23 , [ 15 ] se proporciona una directiva de advertencia, #warning, para imprimir un mensaje sin abortar. Algunos usos típicos son advertir sobre el uso de funcionalidad obsoleta . Por ejemplo:

Antes de C23 y C++23, esta directiva existía en muchos compiladores como una característica no estándar, como los compiladores C de GNU, Intel, Microsoft e IBM. Debido a que no era estándar, la macro de advertencia tenía diferentes formas:

// GNU, Intel e IBM #advertencia "No utilice ABC, que está obsoleto. Utilice XYZ en su lugar."// Microsoft #pragma message("No utilice ABC, que está obsoleto. Utilice XYZ en su lugar.")

Características no estándar

#pragma

La #pragmadirectiva está definida por lenguajes estándar, pero con pocos o ningún requisito de sintaxis después de su nombre, de modo que los compiladores tienen libertad para definir la sintaxis posterior y el comportamiento asociado. Por ejemplo, una directiva pragma se usa a menudo para permitir la supresión de mensajes de error, gestionar la depuración de la pila y el montón, etc.

C99 introdujo algunas pragmas estándar, con la forma #pragma STDC ..., que se utilizan para controlar la implementación de punto flotante. _Pragma(...)También se añadió la forma alternativa, similar a una macro.

Uno de los usos más populares de la #pragmadirectiva es #pragma once , que se comporta de la misma manera que una directiva #include , condensada en una sola directiva ubicada al principio del archivo. A pesar de no ser estándar, es compatible con la mayoría de los compiladores.

Trígrafos

Muchas implementaciones no admiten trigrafos o no los reemplazan por defecto.

Afirmación

Algunos preprocesadores de Unix proporcionaban una función de aserción , que tiene poca similitud con las aserciones de la biblioteca estándar. [ 16 ]

#include_next

GCC permite #include_nextencadenar encabezados con el mismo nombre. [ 17 ]

Por ejemplo, si se sobrescribe el archivo <stdio.h>, intentar incluir la biblioteca estándar <stdio.h>provocaría una recursión infinita de inclusión si se utiliza #include, ya que se volvería a incluir a sí mismo. #include_nextresuelve esto incluyendo el siguiente <stdio.h>encontrado.

// override_stdio/stdio.h #ifndef MY_STDIO_H #define MY_STDIO_H// Anulaciones personalizadas #define printf(...) my_custom_printf(__VA_ARGS__)// Incluir el siguiente stdio.h en la ruta de búsqueda #include_next <stdio.h> #endif

#import

A diferencia de C y C++, Objective-C incluye una #importdirectiva que es similar #includepero que hace que un archivo se incluya solo una vez , eliminando la necesidad de protecciones de inclusión y #pragma once. Es una parte estándar de Objective-C.

#importar <Foundation/Foundation.h> #importar "MyClass.h"

En Microsoft Visual C++ (MSVC), también existe una #importdirectiva de preprocesador, utilizada para importar bibliotecas de tipos. [ 18 ] Es una directiva no estándar.

#import "C:\\Archivos de programa\\Archivos comunes\\Sistema\\ado\\msado15.dll" no_namespace rename("EOF", "ADOEOF")

No deben confundirse con la palabra clave de C++ import, que se utiliza para importar módulos de C++ (desde C++20 ) y no es una directiva del preprocesador.

Directiva nula

La directiva nula, que consiste únicamente en el #carácter, solo en una sola línea, es una directiva no estándar en Microsoft Visual C++. No tiene ningún efecto. [ 19 ]

#nullable

La #nullabledirectiva en C# se utiliza para habilitar y deshabilitar tipos de referencia anulables. Para habilitarlos, use #nullable enable, y #nullable disablepara deshabilitarlos.

# habilitar anulablecadena? nombre = null ; // OK cadena nombrecompleto = null ; // Advertencia: posible asignación nula# deshabilitar anulablecadena de prueba = null ; // Sin advertencia

Esta directiva no existe en C/C++.

#region

Las directivas `expand` #regiony #endregion`contraer` en C# se utilizan para expandir/contraer secciones de código en entornos de desarrollo integrados (IDE) y no afectan la compilación del programa. Su uso principal es la organización y legibilidad del código.

usando el sistema ;#region Métodos auxiliaresvoid Log ( string mensaje ) { Console.WriteLine ( mensaje ) ; }#fin de la región

Si bien esta directiva no existe en C/C++, MSVC y Visual Studio tienen en su lugar #pragma regiony #pragma endregion. [ 20 ] Por lo tanto, el código C++ equivalente sería:

usando std :: string_view ;#pragma region Métodos auxiliaresvoid log ( string_view message ) { std :: println ( message ); }#pragma endregion

#using

C++/CLI tiene la #usingdirectiva, que se utiliza para importar metadatos a un programa desde un archivo de lenguaje intermedio de Microsoft (como un archivo .dll ). [ 21 ]

# usando < MyComponent.dll > # usando " AssemblyA.dll " # usando " AssemblyB.dll "usando el espacio de nombres Sistema ;public ref class B { public void Test ( A a ) { // ... } };int main ( array < String ^>^ args ) { A a ; B b ; B . Test ( a ); }

Otros usos

Tradicionalmente, el preprocesador de C era una herramienta de desarrollo independiente del compilador con el que se suele utilizar. En ese caso, puede usarse por separado del compilador. Ejemplos notables incluyen su uso con el sistema imake (obsoleto) y para el preprocesamiento de Fortran . Sin embargo, su uso como preprocesador de propósito general es limitado, ya que el lenguaje del código fuente debe ser relativamente similar a C para que el preprocesador pueda analizarlo. [ 2 ]

El compilador GNU Fortran ejecuta el preprocesador en "modo tradicional" antes de compilar el código Fortran si se utilizan ciertas extensiones de archivo. [ 22 ] Intel ofrece un preprocesador Fortran, fpp, para usar con el compilador ifort , que tiene capacidades similares. [ 23 ]

El preprocesador de C también funciona de forma aceptable con la mayoría de los lenguajes ensamblador y lenguajes tipo Algol. Esto requiere que la sintaxis del lenguaje no entre en conflicto con la sintaxis del preprocesador de C, lo que significa que no debe haber líneas que comiencen con #y que las comillas dobles, que el preprocesador de C interpreta como literales de cadena y, por lo tanto, ignora, no tengan otro significado sintáctico. El "modo tradicional" (que actúa como un preprocesador de C anterior a la ISO) es generalmente más permisivo y más adecuado para este uso. [ 24 ]

Algunos compiladores modernos, como el compilador GNU C, ofrecen el preprocesamiento como una característica del compilador, no como una herramienta independiente.

Limitaciones

Limitaciones de sustitución de texto

La sustitución de texto tiene un riesgo relativamente alto de causar un error de software en comparación con otras construcciones de programación. [ 25 ] [ 26 ]

En particular, las macros del preprocesador de C carecen de conciencia sintáctica y no logran preservar la estructura léxica, a diferencia de las macros sintácticas que se ven en lenguajes tipo Lisp , Scala [ 27 ] o Rust [ 28 ] .

Evaluación múltiple oculta

Consideremos la definición común de una MAXmacro:

#define MAX(a, b) (((a) > (b)) ? (a) : (b))

Las expresiones representadas por ay bse evalúan dos veces debido a la expansión de macros, pero este aspecto no es obvio en el código donde se hace referencia a la macro. Si las expresiones reales tienen un valor constante, la evaluación múltiple no es problemática desde un punto de vista lógico, aunque puede afectar el rendimiento en tiempo de ejecución. Pero si una expresión se evalúa a un valor diferente en evaluaciones posteriores, el resultado puede ser inesperado. Por ejemplo, dado , el resultado de es 2. Si y se evaluaran solo una vez, el resultado de sería el mismo, pero con la doble evaluación el resultado es 3.inti=1,j=2;MAX(i,j)abMAX(i++,j++)

Orden de operación oculta

No encerrar los argumentos entre paréntesis puede dar lugar a resultados inesperados. Por ejemplo, una macro para duplicar un valor podría escribirse como:

#define DOUBLE(x) 2 * x

Pero se expande a , que debido al orden de las operaciones se evalúa como 4 cuando el esperado es 6. Para mitigar este problema, una macro debería encerrar entre paréntesis todas las expresiones y variables de sustitución:DOUBLE(1+2)2*1+2

#define DOUBLE(x) (2 * (x))

No es de uso general.

El preprocesador de C no es Turing-completo , pero se acerca bastante. Se pueden especificar cálculos recursivos, pero con un límite superior fijo en la cantidad de recursión realizada. [ 29 ] 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. Dado que el preprocesador de C no tiene características de otros preprocesadores, como macros recursivas, expansión selectiva según las comillas y evaluación de cadenas en condicionales, es muy limitado en comparación con un procesador de macros más general como m4 .

Reducir progresivamente

Debido a sus limitaciones y a la falta de seguridad de tipos (ya que el preprocesador ignora por completo la gramática de C/C++ y solo realiza sustituciones de texto), a lo largo de los años se han añadido características a los lenguajes C y C++ para minimizar el valor y la necesidad del preprocesador.

Constante

Durante mucho tiempo, una macro de preprocesador fue la forma preferida de definir un valor constante. Una alternativa siempre ha sido definir una constvariable, pero esto conlleva el consumo de memoria en tiempo de ejecución. Una construcción de lenguaje más reciente (desde C++11 y C23), constexprpermite declarar un valor constante en tiempo de compilación que no necesita consumir memoria en tiempo de ejecución. [ 30 ]

// Con preprocesador: #define MAX_NUMBER_OF_RETRIES 3 #define EARTH_GRAVITATIONAL_ACCELERATION 9.8// Usando constexpr: constexpr int MAX_NUMBER_OF_RETRIES = 3 ; constexpr double EARTH_GRAVITATIONAL_ACCELERATION = 9.8 ;

Función en línea

Durante mucho tiempo, una macro con comportamiento de función fue la única forma de definir un comportamiento similar al de una función sin generar sobrecarga en las llamadas a funciones en tiempo de ejecución. Gracias a la inlinepalabra clave `inline` y a los compiladores optimizadores que insertan código automáticamente, algunas funciones pueden invocarse sin sobrecarga en las llamadas.

// Con preprocesador: #define MAX(a, b) ((a) > (b) ? (a) : (b))// Con inline: inline constexpr auto max ( auto a , auto b ) noexcept { return ( a > b ) ? a : b ; }

Importar

La directiva include limita la estructura del código ya que solo permite incluir el contenido de un archivo en otro. Los lenguajes más modernos admiten un concepto de módulo que tiene símbolos públicos que otros módulos importan , en lugar de incluir el contenido del archivo. Muchos sostienen que el código resultante tiene menos repetición y es más fácil de mantener ya que solo hay un archivo para un módulo, no un encabezado y un cuerpo. C++20 agrega módulos y una importinstrucción que no se maneja a través del preprocesamiento. [ 31 ] [ 32 ] Los módulos en C++ compilan más rápido y enlazan más rápido que los encabezados tradicionales, [ 33 ] y eliminan la necesidad de guardas #include o #pragma once . Hasta C++26, , , y las palabras clave eran manejadas parcialmente por el preprocesador.importexportmodule

// Con include: #include "wikipedia/examples/utils.h"// Con importación: import wikipedia . examples . utils ;

Para bases de código que no pueden migrar a módulos de inmediato, C++ también ofrece "unidades de encabezado" como una característica, que permite importar archivos de encabezado de la misma manera que un módulo. A diferencia de los módulos, las unidades de encabezado pueden generar macros, lo que ofrece una mínima ruptura durante la migración. Las unidades de encabezado están diseñadas como una solución transitoria antes de migrar completamente a módulos. [ 34 ] Por ejemplo, se puede escribir en lugar de , o en lugar de . Paradójicamente, la mayoría de los sistemas de compilación, como CMake , no admiten actualmente esta característica.import<string>;#include<string>import"MyHeader.hpp";#include"MyHeader.hpp"

En Clang , se ofrece una característica de módulo no estándar para C , que permite importar encabezados como módulos. [ 35 ]

Véase también

Referencias

  1. Preprocesamiento de texto de propósito general con el preprocesador C. Incluye JavaScript.
  2. 1 2 "El preprocesador de C: descripción general" . Consultado el 17 de julio de 2016 .
  3. "Directivas del preprocesador de C#" . Microsoft. 14 de enero de 2022.
  4. "Directivas del compilador" . learn.microsoft.com . Microsoft Learn. 30 de julio de 2025.
  5. Ritchie (1993)
  6. "Bell SAP – SAP con macros condicionales y recursivas" . HOPL: Enciclopedia histórica en línea de lenguajes de programación . Archivado del original el 14 de octubre de 2023. Consultado el 4 de octubre de 2020 .
  7. "WG14-N3017 : #embed – un mecanismo de inclusión de recursos binarios escaneable y compatible con herramientas" . open-std.org . 27 de junio de 2022. Archivado del original el 24 de diciembre de 2022. 
  8. "#embed - un mecanismo de inclusión de recursos binarios escaneable y compatible con herramientas" . open-std.org .
  9. Wirzenius, Lars. C "Truco del preprocesador para implementar tipos de datos similares". Consultado el 9 de enero de 2011.
  10. Meyers, Randy (mayo de 2001). "Las nuevas macros C: X" . Dr. Dobb's Journal . Consultado el 1 de mayo de 2008 .
  11. Beal, Stephan (agosto de 2004). "Supermacros" . Recuperado el 27 de octubre de 2008 .{{cite journal}}: Para citar una revista se requiere |journal=( ayuda )
  12. Lista de macros de implementación predefinidas de ANSI C y Microsoft C++.
  13. Proyecto Lombok. "@Getter y @Setter" . projectlombok.org . Proyecto Lombok . Consultado el 22 de abril de 2026 .
  14. "WG14-N3096 : Borrador para ISO/IEC 9899:2023" (PDF) . open-std.org . 1 de abril de 2023. Archivado (PDF) del original el 2 de abril de 2023. 
  15. "Borrador de trabajo, estándar para el lenguaje de programación C++" (PDF) . 22 de marzo de 2023.
  16. Características obsoletas de GCC
  17. "Encabezados de envoltura (El preprocesador de C)" .
  18. "Directiva #import (C++)" . learn.microsoft.com.
  19. "Directiva nula" . learn.microsoft.com.
  20. "pragma region and endregion" .
  21. "Directiva #using (C++/CLI)" . learn.microsoft.com. 29 de junio de 2022.
  22. "1.3 Preprocesamiento y compilación condicional" . Proyecto GNU.
  23. "Uso del preprocesador fpp" . Intel . Consultado el 14 de octubre de 2015 .
  24. "Descripción general (El preprocesador de C)" . gcc.gnu.org . Dicho esto, a menudo se puede usar cpp en cosas que no son C. Otros lenguajes de programación tipo Algol suelen ser seguros (Ada, etc.). También lo es el lenguaje ensamblador, con precaución. El modo -traditional-cpp conserva más espacio en blanco y, por lo demás, es más permisivo. Muchos de los problemas se pueden evitar escribiendo comentarios al estilo de C o C++ en lugar de comentarios del lenguaje nativo y manteniendo las macros simples.
  25. Gerard J. Holzmann. "El poder de diez: reglas para desarrollar código crítico para la seguridad" (PDF) . Seguridad de las macros . pág. 4. 
  26. Michael D. Ernst; Greg J. Badros ; David Notkin (diciembre de 2002). "An empirical analysis of c preprocessor use" . IEEE Transactions on Software Engineering . 28 (12): 1146–1170 . Bibcode : 2002ITSEn..28.1146E . doi : 10.1109/TSE.2002.1158288 .
  27. "Def Macros" . Documentación de Scala . Consultado el 5 de abril de 2021 .
  28. "Macros - El lenguaje de programación Rust" . doc.rust-lang.org . Consultado el 5 de abril de 2021 .
  29. "¿Es el preprocesador C99 Turing completo?" . Archivado del original el 24 de abril de 2016.
  30. Gabriel Dos Reis; Bjarne Stroustrup (22 de marzo de 2010). "Expresiones constantes generales para lenguajes de programación de sistemas, Actas SAC '10" (PDF) . Archivado (PDF) del original el 13 de junio de 2018. Recuperado el 8 de julio de 2024 .
  31. "N4720: Borrador de trabajo, Extensiones a C++ para módulos" (PDF) . Archivado (PDF) del original el 30 de abril de 2019.
  32. "P1857R1 – Detección de dependencias de módulos" .
  33. "Descripción general de los módulos en C++" . Microsoft. 12 de febrero de 2022.
  34. "Tutorial: Cómo crear e importar unidades de encabezado en Microsoft Visual C++" . Microsoft. 12 de abril de 2022.
  35. "Módulos" . clang.llvm.org .

Fuentes

  • Ritchie, Dennis M. (marzo de 1993). "El desarrollo del lenguaje C" . ACM SIGPLAN Notices . 28 (3). ACM: 201– 208. doi : 10.1145/155360.155580 .
  • Ritchie, Dennis M. (1993). «El desarrollo del lenguaje C» . 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-4Consultado el 4 de noviembre de 2014 .
  • ISO/IEC 9899. La última versión disponible públicamente de la norma C11 es el borrador final .
  • Manual en línea de GNU CPP
  • Referencia del preprocesador de Visual Studio .NET
  • Proyecto de macros de compilador C/C++ predefinidas : 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".