Namespaces
Variants

Phases of translation

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Los archivos fuente de C++ son procesados por el compilador para producir programas C++.

Contenidos

Proceso de traducción

El texto de un programa C++ se mantiene en unidades llamadas source files .

Los archivos fuente de C++ se someten a traducción para convertirse en una unidad de traducción , que consta de los siguientes pasos:

  1. Asigna cada archivo fuente a una secuencia de caracteres.
  2. Convierte cada secuencia de caracteres en una secuencia de tokens de preprocesamiento, separados por espacios en blanco.
  3. Convierte cada token de preprocesamiento en un token, formando una secuencia de tokens.
  4. Convierte cada secuencia de tokens en una unidad de traducción.

Un programa en C++ puede formarse a partir de unidades de traducción traducidas. Las unidades de traducción traducidas y las unidades instanciadas (las unidades instanciadas se describen en la fase 8 a continuación) pueden guardarse individualmente o guardarse en una biblioteca. Múltiples unidades de traducción se comunican entre sí mediante (por ejemplo) símbolos con enlace externo o archivos de datos. Las unidades de traducción pueden traducirse por separado y luego enlazarse para producir un programa ejecutable.

El proceso anterior puede organizarse en 9 translation phases .

Tokens de preprocesamiento

Un token de preprocesamiento es el elemento léxico mínimo del lenguaje en las fases de traducción 3 a 6.

Las categorías de tokens de preprocesamiento son:

(desde C++20)
El programa está mal formado si el carácter que coincide con esta categoría es

Números de preprocesamiento

El conjunto de tokens de preprocesamiento de número de preprocesamiento es un superconjunto de la unión de los conjuntos de tokens de literales enteros y literales de punto flotante :

. (opcional) dígito pp-continue-seq  (opcional)
digit - uno de los dígitos 0-9
pp-continue-seq - una secuencia de pp-continue s

Cada pp-continue es uno de los siguientes:

identifier-continue (1)
exp-char sign-char (2)
. (3)
digit (4) (desde C++14)
nondigit (5) (desde C++14)
identifier-continue - cualquier carácter no inicial de un identificador válido
exp-char - uno de P , p , (desde C++11) E y e
sign-char - uno de + y -
digit - uno de los dígitos 0-9
nondigit - una de las letras latinas A/a-Z/z y guion bajo

Un número de preprocesamiento no tiene un tipo ni un valor; adquiere ambos después de una conversión exitosa a un token literal entero/de punto flotante.

Espacio en Blanco

Espacio en blanco consiste en comentarios , caracteres de espacio en blanco, o ambos.

Los siguientes caracteres son caracteres de espacio en blanco:

  • tabulación de caracteres (U+0009)
  • salto de línea / carácter de nueva línea (U+000A)
  • tabulación de línea (U+000B)
  • salto de página (U+000C)
  • espacio (U+0020)

El espacio en blanco generalmente se utiliza para separar tokens de preprocesamiento, con las siguientes excepciones:

  • No es un separador en el nombre de cabecera, literal de carácter y literal de cadena.
  • Los tokens de preprocesamiento separados por espacios en blanco que contienen caracteres de nueva línea no pueden formar directivas de preprocesamiento .
#include "my header"        // OK, usando un nombre de cabecera que contiene espacios en blanco
#include/*hello*/<iostream> // OK, usando un comentario como espacio en blanco
#include
<iostream> // Error: #include no puede extenderse a través de múltiples líneas
"str ing"  // OK, un único token de preprocesamiento (literal de cadena)
' '        // OK, un único token de preprocesamiento (literal de carácter)

Munch máximo

El maximal munch es la regla utilizada en la fase 3 al descomponer el archivo fuente en tokens de preprocesamiento.

Si la entrada ha sido analizada hasta un carácter dado (de lo contrario, el siguiente token de preprocesamiento no será analizado, lo que hace que el orden de análisis sea único), el siguiente token de preprocesamiento generalmente se toma como la secuencia más larga de caracteres que podría constituir un token de preprocesamiento, incluso si eso causara que el análisis posterior falle. Esto se conoce comúnmente como maximal munch .

int foo = 1;
int bar = 0xE+foo;   // Error: número de preprocesamiento no válido 0xE+foo
int baz = 0xE + foo; // Correcto

En otras palabras, la regla de maximal munch favorece a los operadores y puntuadores multi-carácter :

int foo = 1;
int bar = 2;
int num1 = foo+++++bar; // Error: se interpreta como "foo++ ++ +baz", no como "foo++ + ++baz"
int num2 = -----foo;    // Error: se interpreta como "-- -- -foo", no como "- -- --foo"

La regla de maximal munch tiene las siguientes excepciones:

  • Los tokens de preprocesamiento de nombre de cabecera solo se forman en los siguientes casos:
  • después del token de preprocesamiento include en una directiva #include
(desde C++17)
  • después del token de preprocesamiento import en una directiva import
(desde C++20)
std::vector<int> x; // CORRECTO, "int" no es un nombre de cabecera
  • Si los siguientes tres caracteres son < :: y el carácter subsiguiente no es ni : ni > , entonces el < se trata como un token de preprocesamiento independiente en lugar del primer carácter del token alternativo < : .
struct Foo { static const int v = 1; };
std::vector<::Foo> x;  // OK, <: no se interpreta como el token alternativo para [
extern int y<::>;      // OK, igual que "extern int y[];"
int z<:::Foo::value:>; // OK, igual que "int z[::Foo::value];"
  • Si los siguientes dos caracteres son >> y uno de los caracteres > puede completar un identificador de plantilla , el carácter se trata como un token de preprocesamiento individual en lugar de formar parte del token de preprocesamiento >> .
template<int i> class X { /* ... */ };
template<class T> class Y { /* ... */ };
Y<X<1>> x3;      // OK, declara una variable “x3” de tipo “Y<X<1> >”
Y<X<6>>1>> x4;   // Error de sintaxis
Y<X<(6>>1)>> x5; // OK
  • Si el siguiente carácter inicia una secuencia de caracteres que podría ser el prefijo y comilla doble inicial de un literal de cadena sin formato , el siguiente token de preprocesamiento es un literal de cadena sin formato. El literal consiste en la secuencia más corta de caracteres que coincide con el patrón de cadena sin formato.
#define R "x"
const char* s = R"y";         // literal de cadena sin formato mal formado, no "x" "y"
const char* s2 = R"(a)" "b)"; // un literal de cadena sin formato seguido de un literal de cadena normal
(desde C++11)

Tokens

Un token es el elemento léxico mínimo del lenguaje en la fase de traducción 7.

Las categorías de token son:

Fases de traducción

La traducción se realiza as if en el orden desde la fase 1 hasta la fase 9. Las implementaciones se comportan como si estas fases separadas ocurrieran, aunque en la práctica diferentes fases pueden estar combinadas.

Fase 1: Mapeo de caracteres fuente

1) Los bytes individuales del archivo de código fuente se mapean (de manera definida por la implementación) a los caracteres del juego de caracteres básico del código fuente . En particular, los indicadores de fin de línea dependientes del sistema operativo se reemplazan por caracteres de nueva línea.
2) El conjunto de caracteres de archivo fuente aceptados está definido por la implementación (since C++11) . Cualquier carácter de archivo fuente que no pueda mapearse a un carácter en el juego de caracteres básico del código fuente se reemplaza por su nombre de carácter universal (escapado con \u o \U ) o por alguna forma definida por la implementación que se maneje de manera equivalente.
3) Secuencias de trigrafos se reemplazan por representaciones de un solo carácter correspondientes.
(until C++17)
(until C++23)

Los archivos de entrada que son una secuencia de unidades de código UTF-8 (archivos UTF-8) están garantizados de ser compatibles. El conjunto de otros tipos de archivos de entrada compatibles está definido por la implementación. Si el conjunto no está vacío, el tipo de un archivo de entrada se determina de manera definida por la implementación que incluye un medio para designar archivos de entrada como archivos UTF-8, independientemente de su contenido (reconocer la marca de orden de bytes no es suficiente).

  • Si se determina que un archivo de entrada es un archivo UTF-8, entonces debe ser una secuencia de unidades de código UTF-8 bien formada y se decodifica para producir una secuencia de valores escalares Unicode. Una secuencia de elementos del juego de caracteres de traducción se forma luego mapeando cada valor escalar Unicode al elemento correspondiente del juego de caracteres de traducción. En la secuencia resultante, cada par de caracteres en la secuencia de entrada que consiste en retorno de carro (U+000D) seguido por avance de línea (U+000A), así como cada retorno de carro (U+000D) no seguido inmediatamente por un avance de línea (U+000A), se reemplaza por un único carácter de nueva línea.
  • Para cualquier otro tipo de archivo de entrada compatible con la implementación, los caracteres se mapean (de manera definida por la implementación) a una secuencia de elementos del juego de caracteres de traducción. En particular, los indicadores de fin de línea dependientes del sistema operativo se reemplazan por caracteres de nueva línea.
(since C++23)

Fase 2: Empalme de líneas

1) Si el primer carácter de traducción es la marca de orden de bytes (U+FEFF), se elimina. (since C++23) Siempre que aparezca una barra invertida ( \ ) al final de una línea (seguida inmediatamente por cero o más caracteres de espacio en blanco distintos del salto de línea seguidos por (since C++23) el carácter de nueva línea), estos caracteres se eliminan, combinando dos líneas físicas de código fuente en una línea lógica de código fuente. Esta es una operación de una sola pasada; una línea que termina con dos barras invertidas seguida de una línea vacía no combina tres líneas en una.
2) Si un archivo fuente no vacío no termina con un carácter de nueva línea después de este paso (las barras invertidas de fin de línea ya no son empalmes en este punto), se añade un carácter de nueva línea final.

Fase 3: Lexing

1) El archivo fuente se descompone en tokens de preprocesamiento y espacios en blanco :
// The following #include directive can de decomposed into 5 preprocessing tokens:
//     punctuators (#, < and >)
//          │
// ┌────────┼────────┐
// │        │        │
   #include <iostream>
//     │        │
//     │        └── header name (iostream)
//     │
//     └─────────── identifier (include)
Si un archivo fuente termina en un token de preprocesamiento parcial o en un comentario parcial, el programa está mal formado:
// Error: partial string literal
"abc
// Error: partial comment
/* comment
A medida que los caracteres del archivo fuente se consumen para formar el siguiente token de preprocesamiento (es decir, que no se consumen como parte de un comentario u otras formas de espacio en blanco), los nombres de caracteres universales se reconocen y se reemplazan por el elemento designado del juego de caracteres de traducción , excepto cuando coinciden con una secuencia de caracteres en uno de los siguientes tokens de preprocesamiento:
  • un literal de carácter ( c-char-sequence )
  • un literal de cadena ( s-char-sequence y r-char-sequence ), excluyendo delimitadores ( d-char-sequence )
  • un nombre de cabecera ( h-char-sequence y q-char-sequence )
(desde C++23)


2) Cualquier transformación realizada durante la fase 1 y (hasta C++23) la fase 2 entre las comillas dobles inicial y final de cualquier literal de cadena sin formato es revertida.
(desde C++11)
3) El espacio en blanco se transforma:
  • Cada comentario se reemplaza por un carácter de espacio.
  • Los caracteres de nueva línea se conservan.
  • Si cada secuencia no vacía de caracteres de espacio en blanco, excepto nueva línea, se conserva o se reemplaza por un carácter de espacio no está especificado.

Fase 4: Preprocesamiento

1) El preprocessor se ejecuta.
2) Cada archivo introducido con la directiva #include pasa por las fases 1 a 4, de forma recursiva.
3) Al final de esta fase, todas las directivas del preprocesador son eliminadas del código fuente.

Fase 5: Determinación de codificaciones comunes de literales de cadena

1) Todos los caracteres en literales de carácter y literales de cadena se convierten del conjunto de caracteres fuente a la codificación (que puede ser una codificación de caracteres multibyte como UTF-8, siempre que los 96 caracteres del conjunto de caracteres básico tengan representaciones de un solo byte).
2) Secuencias de escape y nombres de caracteres universales en literales de carácter y literales de cadena no raw se expanden y convierten a la codificación literal.

Si el carácter especificado por un nombre de carácter universal no puede codificarse como un único punto de código en la codificación literal correspondiente, el resultado está definido por la implementación, pero se garantiza que no será un carácter nulo (ancho).

(until C++23)

Para una secuencia de dos o más tokens de literal de cadena adyacentes, se determina un prefijo de codificación común como se describe aquí . Cada uno de estos tokens de literal de cadena se considera entonces que tiene ese prefijo de codificación común. (La conversión de caracteres se mueve a la fase 3)

(since C++23)

Fase 6: Concatenación de literales de cadena

Los literales de cadena adyacentes se concatenan.

Fase 7: Compilación

La compilación tiene lugar: cada token de preprocesamiento se convierte en un token . Los tokens se analizan sintáctica y semánticamente y se traducen como una unidad de traducción .

Fase 8: Instanciación de plantillas

Cada unidad de traducción se examina para producir una lista de instanciaciones de plantilla requeridas, incluyendo las solicitadas por explicit instantiations . Las definiciones de las plantillas se localizan, y las instanciaciones requeridas se llevan a cabo para producir instantiation units .

Fase 9: Enlazado

Las unidades de traducción, unidades de instanciación y componentes de biblioteca necesarios para satisfacer referencias externas se recopilan en una imagen de programa que contiene la información necesaria para su ejecución en su entorno de ejecución.

Notas

Los archivos fuente, las unidades de traducción y las unidades de traducción traducidas no necesariamente deben almacenarse como archivos, ni debe haber una correspondencia uno a uno entre estas entidades y cualquier representación externa. La descripción es solo conceptual y no especifica ninguna implementación particular.

La conversión realizada en la fase 5 puede ser controlada mediante opciones de línea de comandos en algunas implementaciones: gcc y clang utilizan - finput - charset para especificar la codificación del conjunto de caracteres fuente, - fexec - charset y - fwide - exec - charset para especificar las codificaciones de literales ordinarios y anchos respectivamente, mientras que Visual Studio 2015 Update 2 y posteriores utilizan / source - charset y / execution - charset para especificar el conjunto de caracteres fuente y la codificación de literales respectivamente.

(hasta C++23)

Algunos compiladores no implementan unidades de instanciación (también conocidas como template repositories o template registries ) y simplemente compilan cada instanciación de plantilla en la fase 7, almacenando el código en el archivo objeto donde se solicita implícita o explícitamente, y luego el enlazador colapsa estas instanciaciones compiladas en una durante la fase 9.

Informes de defectos

Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares de C++ publicados anteriormente.

DR Aplicado a Comportamiento publicado Comportamiento correcto
CWG 787 C++98 el comportamiento era indefinido si un archivo fuente no vacío
no terminaba con un carácter de nueva línea al final de la fase 2
agregar un carácter de nueva línea
terminante en este caso
CWG 1104 C++98 el token alternativo < : causaba que std:: vector < :: std:: string >
fuera tratado como std:: vector [ : std:: string >
se agregó una regla adicional de análisis léxico
para abordar este caso
CWG 1775 C++11 formar un nombre de carácter universal dentro de un literal
de cadena sin formato en la fase 2 resultaba en comportamiento indefinido
se hizo bien definido
CWG 2747 C++98 la fase 2 verificaba el empalme de fin-de-archivo después del empalme, esto es innecesario se eliminó la verificación
P2621R3 C++98 no se permitía formar nombres de caracteres universales
mediante empalme de líneas o concatenación de tokens
permitido

Referencias

  • Estándar C++23 (ISO/IEC 14882:2024):
  • 5.2 Fases de traducción [lex.phases]
  • Estándar C++20 (ISO/IEC 14882:2020):
  • 5.2 Fases de traducción [lex.phases]
  • Estándar C++17 (ISO/IEC 14882:2017):
  • 5.2 Fases de traducción [lex.phases]
  • Estándar C++14 (ISO/IEC 14882:2014):
  • 2.2 Fases de traducción [lex.phases]
  • Estándar C++11 (ISO/IEC 14882:2011):
  • 2.2 Fases de traducción [lex.phases]
  • Estándar C++03 (ISO/IEC 14882:2003):
  • 2.1 Fases de traducción [lex.phases]
  • Estándar C++98 (ISO/IEC 14882:1998):
  • 2.1 Fases de traducción [lex.phases]

Véase también

Documentación de C para Fases de traducción