Continuará》》》
Por favor espere~~~
Directorio de artículos
- 1. Fase de compilación
-
- 1.1 Preprocesamiento del programa
- 1.2 Análisis léxico
-
- 1.2.1 Unidad léxica (token)
- 1.2.2 Tipo de token (c_token::type)
- 1.2.3 Tipo de identificador (c_token::id_kind)
- 1.2.4 Identificación de palabras clave (c_token::keyword)
- 1.2.5 Tipo PRAGMA (c_token::pragma_kind)
- 1.2.6 Puntero de nodo de árbol (valor)
- 1.2.7 Información de ubicación (ubicación)
- 1.2.8 Ejemplos
- 1.3 Análisis de sintaxis y análisis semántico
- 1.4 Generación de código intermedio (GIMPLE)
- 1.5 Generación de código ensamblador
- 1.6 Generación de código de objeto
- 2. Fase de vinculación
- 3. Fase de reubicación
Cuando estabas en la escuela, ¿escuchaste un curso llamado "Principios de compilación" y el profesor te preguntó en clase: "¿Sabes cómo el código que escribiste se convierte en un archivo ejecutable para ejecutarlo?" mucha "compilación", "preprocesamiento", "expansión de definición de macros", "árbol de sintaxis abstracta", "ensamblaje", "desensamblaje", etc. Una gran cantidad de contenido embriagador disuade la motivación para aprenderlo.
Repasemos el proceso de compilación de programas.La compilación que solemos decir generalmente se refiere al proceso de generar archivos ejecutables a partir de uno o más archivos de código fuente a través de un compilador. El proceso de compilación del programa se puede dividir en tres etapas:
- (1) Etapa de compilación
- (2) Etapa de vinculación
- (3) Fase de reubicación
Dirección de descarga de mapas de alta definición PNG
: https://download.csdn.net/download/qq_36393978/85329800 Dirección de descarga de archivos
de ultra alta definición : https://download.csdn.net/download/qq_36393978/85329905PDF
1. Fase de compilación
La cadena de herramientas de compilación de uso común gcc
necesita llamar al "preprocesador", "compilador" y "ensamblador" en la cadena de herramientas durante la fase de compilación. El proceso de procesamiento incluye el siguiente flujo.
[注]:GIMPLE代码也可称为中间代码,寄存器传输语言(Register Transfer Language,RTL)
1.1 Preprocesamiento del programa
Es decir, el procesamiento de cada comando de preprocesamiento en el archivo fuente (inclusión de archivos de encabezado, expansión de macros personalizadas y macros integradas, compilación condicional, instrucciones de control para compilación de programas), eliminación de comentarios, adición de números de línea y nombre de archivo identificadores, qué macros integradas se incluyen específicamente y qué comandos de preprocesamiento deben procesarse, podemos ver en el código fuente;
gcc-12.1.0
las macros integradas proporcionadas son las siguientes:
//# gcc-12.1.0/libcpp/init.cc ##------## struct builtin_macro builtin_array[]
struct builtin_macro
{
const uchar *const name;
const unsigned short len;
const unsigned short value;
const bool always_warn_if_redefined;
};
#define B(n, t, f) {
DSC(n), t, f }
static const struct builtin_macro builtin_array[] =
{
B("__TIMESTAMP__", BT_TIMESTAMP, false),
B("__TIME__", BT_TIME, false),
B("__DATE__", BT_DATE, false),
B("__FILE__", BT_FILE, false),
B("__FILE_NAME__", BT_FILE_NAME, false),
B("__BASE_FILE__", BT_BASE_FILE, false),
B("__LINE__", BT_SPECLINE, true),
B("__INCLUDE_LEVEL__", BT_INCLUDE_LEVEL, true),
B("__COUNTER__", BT_COUNTER, true),
/* Make sure to update the list of built-in
function-like macros in traditional.cc:
fun_like_macro() when adding more following */
B("__has_attribute", BT_HAS_ATTRIBUTE, true),
B("__has_c_attribute", BT_HAS_STD_ATTRIBUTE, true),
B("__has_cpp_attribute", BT_HAS_ATTRIBUTE, true),
B("__has_builtin", BT_HAS_BUILTIN, true),
B("__has_include", BT_HAS_INCLUDE, true),
B("__has_include_next",BT_HAS_INCLUDE_NEXT, true),
/* Keep builtins not used for -traditional-cpp at the end, and
update init_builtins() if any more are added. */
B("_Pragma", BT_PRAGMA, true),
B("__STDC__", BT_STDC, true),
};
#undef B
gcc-12.1.0
Los comandos de preprocesamiento que deben procesarse son los siguientes, y las funciones de procesamiento correspondientes se colocan en btable
esta matriz de estructura:
//# gcc12.1.0/libcpp/directives.cc
typedef struct directive directive;
struct directive
{
directive_handler handler; /* Function to handle directive. */
const uchar *name; /* Name of directive. */
unsigned short length; /* Length of name. */
unsigned char origin; /* Origin of directive. */
unsigned char flags; /* Flags describing this directive. */
};
/* This is the table of directive handlers. All extensions other than
#warning, #include_next, and #import are deprecated. The name is
where the extension appears to have come from. */
#define DIRECTIVE_TABLE \
D(define, T_DEFINE = 0, KANDR, IN_I) \
D(include, T_INCLUDE, KANDR, INCL | EXPAND) \
D(endif, T_ENDIF, KANDR, COND) \
D(ifdef, T_IFDEF, KANDR, COND | IF_COND) \
D(if, T_IF, KANDR, COND | IF_COND | EXPAND) \
D(else, T_ELSE, KANDR, COND) \
D(ifndef, T_IFNDEF, KANDR, COND | IF_COND) \
D(undef, T_UNDEF, KANDR, IN_I) \
D(line, T_LINE, KANDR, EXPAND) \
D(elif, T_ELIF, STDC89, COND | EXPAND) \
D(elifdef, T_ELIFDEF, STDC2X, COND | ELIFDEF) \
D(elifndef, T_ELIFNDEF, STDC2X, COND | ELIFDEF) \
D(error, T_ERROR, STDC89, 0) \
D(pragma, T_PRAGMA, STDC89, IN_I) \
D(warning, T_WARNING, EXTENSION, 0) \
D(include_next, T_INCLUDE_NEXT, EXTENSION, INCL | EXPAND) \
D(ident, T_IDENT, EXTENSION, IN_I) \
D(import, T_IMPORT, EXTENSION, INCL | EXPAND) /* ObjC */ \
D(assert, T_ASSERT, EXTENSION, DEPRECATED) /* SVR4 */ \
D(unassert, T_UNASSERT, EXTENSION, DEPRECATED) /* SVR4 */ \
D(sccs, T_SCCS, EXTENSION, IN_I) /* SVR4? */
#define D(name, t, origin, flags) \
{
do_##name, (const uchar *) #name, \
sizeof #name - 1, origin, flags },
static const directive dtable[] =
{
DIRECTIVE_TABLE
};
#undef D
1.1.1 Pequeño conocimiento (macro polimorfismo)
B
Los pequeños amigos talentosos aquí pueden definir las macros aquí , como D
estas, ¿por qué #define
después de definirlas, quedan #undef
indefinidas? ?
Esto es para lograr polimorfismo de macros, es decir, cuando se referencian en diferentes lugares y en diferentes momentos, pueden tener diferentes propiedades y producir diferentes resultados. Si aún no lo entiende, puede mirar el ejemplo simple a continuación y quedará claro de un vistazo.
#include <stdio.h>
#define NUM_COUNT \
NUM(1) \
NUM(2) \
NUM(3) \
NUM(4)
void print_num(int num)
{
printf("%d\n", num);
return ;
}
void num_increase(int num)
{
printf("%d\n", num + 1);
return ;
}
int main(int argc, char **argv)
{
#define NUM(num) print_num(num);
NUM_COUNT
#undef NUM
printf("==============\n");
#define NUM(num) num_increase(num);
NUM_COUNT
#undef NUM
return 0;
}
Los resultados de su ejecución son los siguientes:
imaginemiracle@ubuntu:define$ ./a.out
1
2
3
4
==============
2
3
4
5
1.2 Análisis léxico
1.2.1 Unidad léxica (token)
El analizador léxico lee el código fuente, identifica cada símbolo léxico en el código fuente y construye una unidad léxica token
para guardar el símbolo. Este proceso se _cpp_lex_token()
completa principalmente con la función (la función se encuentra gcc12.1.0/libcpp/lex.cc
en ), y luego cada uno token
se verificará y generar token
una secuencia de . En el código fuente del analizador léxico, token
en realidad representado por una estructura. En la gcc-12.1.0
actualidad definición dec
la unidad léxica del lenguaje es que la estructura se utiliza para almacenar una unidad léxica básica. Existe una definición de la estructura , que consiste en agregar una representación , que en sí misma es una macro definición. Se utiliza principalmente en el mecanismo de gestión de basura posterior y no se tratará aquí;token
struct GTY(()) c_token
GTY(())
GTY
//# gcc-12.1.0/gcc/c/c-parser.h ##------## struct c_token
/* A single C token after string literal concatenation and conversion
of preprocessing tokens to tokens. */
struct GTY (()) c_token {
/* The kind of token. */
ENUM_BITFIELD (cpp_ttype) type : 8;
/* If this token is a CPP_NAME, this value indicates whether also
declared as some kind of type. Otherwise, it is C_ID_NONE. */
ENUM_BITFIELD (c_id_kind) id_kind : 8;
/* If this token is a keyword, this value indicates which keyword.
Otherwise, this value is RID_MAX. */
ENUM_BITFIELD (rid) keyword : 8;
/* If this token is a CPP_PRAGMA, this indicates the pragma that
was seen. Otherwise it is PRAGMA_NONE. */
ENUM_BITFIELD (pragma_kind) pragma_kind : 8;
/* The location at which this token was found. */
location_t location;
/* The value associated with this token, if any. */
tree value;
/* Token flags. */
unsigned char flags;
source_range get_range () const
{
return get_range_from_loc (line_table, location);
}
location_t get_finish () const
{
return get_range ().m_finish;
}
};
c++
Y token
la definición de la unidad léxica del lenguaje es struct GTY(()) cpp_token
. Este artículo utiliza principalmente c
el lenguaje como ejemplo para analizar todo el proceso de compilación.
//# gcc-12.1.0/libcpp/include/cpplib.h ##------## struct cpp_token
/* A preprocessing token. This has been carefully packed and should
occupy 16 bytes on 32-bit hosts and 24 bytes on 64-bit hosts. */
struct GTY(()) cpp_token {
/* Location of first char of token, together with range of full token. */
location_t src_loc;
ENUM_BITFIELD(cpp_ttype) type : CHAR_BIT; /* token type */
unsigned short flags; /* flags - see above */
union cpp_token_u
{
/* An identifier. */
struct cpp_identifier GTY ((tag ("CPP_TOKEN_FLD_NODE"))) node;
/* Inherit padding from this token. */
cpp_token * GTY ((tag ("CPP_TOKEN_FLD_SOURCE"))) source;
/* A string, or number. */
struct cpp_string GTY ((tag ("CPP_TOKEN_FLD_STR"))) str;
/* Argument no. (and original spelling) for a CPP_MACRO_ARG. */
struct cpp_macro_arg GTY ((tag ("CPP_TOKEN_FLD_ARG_NO"))) macro_arg;
/* Original token no. for a CPP_PASTE (from a sequence of
consecutive paste tokens in a macro expansion). */
unsigned int GTY ((tag ("CPP_TOKEN_FLD_TOKEN_NO"))) token_no;
/* Caller-supplied identifier for a CPP_PRAGMA. */
unsigned int GTY ((tag ("CPP_TOKEN_FLD_PRAGMA"))) pragma;
} GTY ((desc ("cpp_token_val_index (&%1)"))) val;
};
struct c_token
La estructura incluye principalmente tipo de símbolo ( type
), tipo de identificador ( id_kind
), palabra clave ( keyword
), PRAGMA
tipo ( pragma_kind
), valor de símbolo ( value
) y ( ) utilizados para describir la posición del símbolo en el archivo de origen location
.
1.2.2 Tipo de token (c_token::type)
gcc
Una serie de símbolos de operación se definen en , como operadores regulares =
, !
, +
, y -
, etc>
. , separadores , , , , etc . Las definiciones y símbolos de tales símbolos de operación se especifican mediante macros , y algunos otros tipos de símbolos se especifican por la macro , incluye principalmente , , , , etc. Todos estos símbolos están definidos en las variables de enumeración el archivo ./libcpp/iclude/cpplib.h .<
:
,
;
{
}
value
OP
TK
EOF
NAME
CHAR
STRING
PRAGMA
enum cpp_ttype
1.2.3 Tipo de identificador (c_token::id_kind)
Del comentario a la definición de struct c_tokent
la estructura , podemos entender que cuando type
el valor de no es CPP_NAME
, el valor de id_kind
es C_IN_NONE
, indicando que el tipo no es un identificador; por el contrario, cuando type
el valor de CPP_NAME
es, id_kind
será ser válido (es decir, se le asignará un valor de significado), lo que significa que el tipo es un tipo identificador. CPP_NAME
Los tipos de incluyen identificadores ordinarios ( C_ID_ID
), nombres de tipos ( C_ID_TYPENAME
), nombres de objetos en Objective-C ( C_ID_CLASSNAME
) y tipos de espacio de direcciones ( C_ID_ADDRSPACE
).
//# gcc-12.1.0/gcc/c/c-parser.h ##------## enum c_id_kind
/* More information about the type of a CPP_NAME token. */
enum c_id_kind {
/* An ordinary identifier. */
/* 普通标识符 */
C_ID_ID,
/* An identifier declared as a typedef name. */
/* 描述类型的标识符,即用 typedef 定义的类型 */
C_ID_TYPENAME,
/* An identifier declared as an Objective-C class name. */
/* Objective-C 中的对象名称标识符 */
C_ID_CLASSNAME,
/* An address space identifier. */
/* 地址空间标识符 */
C_ID_ADDRSPACE,
/* Not an identifier. */
/* 不是标识符 */
C_ID_NONE
};
1.2.4 Identificación de palabras clave (c_token::keyword)
Cuando sea necesario utilizar un identificador para representar una palabra clave ( ) en el código Key Workds
, se deben determinar los valores específicos de estas palabras clave, a fin de determinar el significado específico representado por la palabra clave. c
Todas las palabras clave y los valores correspondientes utilizados por el idioma se definen en las variables de enumeración en el archivo ./gcc/c-family/c-common.h .enum rid
//# gcc-12.1.0/gcc/c-family/c-common.h ##------## enum rid
/* Reserved identifiers. This is the union of all the keywords for C,
C++, and Objective-C. All the type modifiers have to be in one
block at the beginning, because they are used as mask bits. There
are 28 type modifiers; if we add many more we will have to redesign
the mask mechanism. */
enum rid
{
/* Modifiers: */
/* C, in empirical order of frequency. */
RID_STATIC = 0,
RID_UNSIGNED, RID_LONG, RID_CONST, RID_EXTERN,
RID_REGISTER, RID_TYPEDEF, RID_SHORT, RID_INLINE,
RID_VOLATILE, RID_SIGNED, RID_AUTO, RID_RESTRICT,
RID_NORETURN, RID_ATOMIC,
/* C extensions */
RID_COMPLEX, RID_THREAD, RID_SAT,
/* C++ */
RID_FRIEND, RID_VIRTUAL, RID_EXPLICIT, RID_EXPORT, RID_MUTABLE,
/* ObjC ("PQ" reserved words - they do not appear after a '@' and
are keywords only in specific contexts) */
RID_IN, RID_OUT, RID_INOUT, RID_BYCOPY, RID_BYREF, RID_ONEWAY,
/* ObjC ("PATTR" reserved words - they do not appear after a '@'
and are keywords only as property attributes) */
RID_GETTER, RID_SETTER,
RID_READONLY, RID_READWRITE,
RID_ASSIGN, RID_RETAIN, RID_COPY,
RID_PROPATOMIC, RID_NONATOMIC,
/* ObjC nullability support keywords that also can appear in the
property attribute context. These values should remain contiguous
with the other property attributes. */
RID_NULL_UNSPECIFIED, RID_NULLABLE, RID_NONNULL, RID_NULL_RESETTABLE,
/* C (reserved and imaginary types not implemented, so any use is a
syntax error) */
RID_IMAGINARY,
/* C */
RID_INT, RID_CHAR, RID_FLOAT, RID_DOUBLE, RID_VOID,
RID_ENUM, RID_STRUCT, RID_UNION, RID_IF, RID_ELSE,
RID_WHILE, RID_DO, RID_FOR, RID_SWITCH, RID_CASE,
RID_DEFAULT, RID_BREAK, RID_CONTINUE, RID_RETURN, RID_GOTO,
RID_SIZEOF,
/* C extensions */
RID_ASM, RID_TYPEOF, RID_ALIGNOF, RID_ATTRIBUTE, RID_VA_ARG,
RID_EXTENSION, RID_IMAGPART, RID_REALPART, RID_LABEL, RID_CHOOSE_EXPR,
RID_TYPES_COMPATIBLE_P, RID_BUILTIN_COMPLEX, RID_BUILTIN_SHUFFLE,
RID_BUILTIN_SHUFFLEVECTOR, RID_BUILTIN_CONVERTVECTOR, RID_BUILTIN_TGMATH,
RID_BUILTIN_HAS_ATTRIBUTE, RID_BUILTIN_ASSOC_BARRIER,
RID_DFLOAT32, RID_DFLOAT64, RID_DFLOAT128,
/* TS 18661-3 keywords, in the same sequence as the TI_* values. */
RID_FLOAT16,
RID_FLOATN_NX_FIRST = RID_FLOAT16,
RID_FLOAT32,
RID_FLOAT64,
RID_FLOAT128,
RID_FLOAT32X,
RID_FLOAT64X,
RID_FLOAT128X,
#define CASE_RID_FLOATN_NX \
case RID_FLOAT16: case RID_FLOAT32: case RID_FLOAT64: case RID_FLOAT128: \
case RID_FLOAT32X: case RID_FLOAT64X: case RID_FLOAT128X
RID_FRACT, RID_ACCUM, RID_AUTO_TYPE, RID_BUILTIN_CALL_WITH_STATIC_CHAIN,
/* "__GIMPLE", for the GIMPLE-parsing extension to the C frontend. */
RID_GIMPLE,
/* "__PHI", for parsing PHI function in GIMPLE FE. */
RID_PHI,
/* "__RTL", for the RTL-parsing extension to the C frontend. */
RID_RTL,
/* C11 */
RID_ALIGNAS, RID_GENERIC,
/* This means to warn that this is a C++ keyword, and then treat it
as a normal identifier. */
RID_CXX_COMPAT_WARN,
/* GNU transactional memory extension */
RID_TRANSACTION_ATOMIC, RID_TRANSACTION_RELAXED, RID_TRANSACTION_CANCEL,
/* Too many ways of getting the name of a function as a string */
RID_FUNCTION_NAME, RID_PRETTY_FUNCTION_NAME, RID_C99_FUNCTION_NAME,
/* C++ (some of these are keywords in Objective-C as well, but only
if they appear after a '@') */
RID_BOOL, RID_WCHAR, RID_CLASS,
RID_PUBLIC, RID_PRIVATE, RID_PROTECTED,
RID_TEMPLATE, RID_NULL, RID_CATCH,
RID_DELETE, RID_FALSE, RID_NAMESPACE,
RID_NEW, RID_OFFSETOF, RID_OPERATOR,
RID_THIS, RID_THROW, RID_TRUE,
RID_TRY, RID_TYPENAME, RID_TYPEID,
RID_USING, RID_CHAR16, RID_CHAR32,
/* casts */
RID_CONSTCAST, RID_DYNCAST, RID_REINTCAST, RID_STATCAST,
/* C++ extensions */
RID_ADDRESSOF, RID_BASES,
RID_BUILTIN_LAUNDER, RID_DIRECT_BASES,
RID_HAS_NOTHROW_ASSIGN, RID_HAS_NOTHROW_CONSTRUCTOR,
RID_HAS_NOTHROW_COPY, RID_HAS_TRIVIAL_ASSIGN,
RID_HAS_TRIVIAL_CONSTRUCTOR, RID_HAS_TRIVIAL_COPY,
RID_HAS_TRIVIAL_DESTRUCTOR, RID_HAS_UNIQUE_OBJ_REPRESENTATIONS,
RID_HAS_VIRTUAL_DESTRUCTOR, RID_BUILTIN_BIT_CAST,
RID_IS_ABSTRACT, RID_IS_AGGREGATE,
RID_IS_BASE_OF, RID_IS_CLASS,
RID_IS_EMPTY, RID_IS_ENUM,
RID_IS_FINAL, RID_IS_LAYOUT_COMPATIBLE,
RID_IS_LITERAL_TYPE,
RID_IS_POINTER_INTERCONVERTIBLE_BASE_OF,
RID_IS_POD, RID_IS_POLYMORPHIC,
RID_IS_SAME_AS,
RID_IS_STD_LAYOUT, RID_IS_TRIVIAL,
RID_IS_TRIVIALLY_ASSIGNABLE, RID_IS_TRIVIALLY_CONSTRUCTIBLE,
RID_IS_TRIVIALLY_COPYABLE,
RID_IS_UNION, RID_UNDERLYING_TYPE,
RID_IS_ASSIGNABLE, RID_IS_CONSTRUCTIBLE,
RID_IS_NOTHROW_ASSIGNABLE, RID_IS_NOTHROW_CONSTRUCTIBLE,
/* C++11 */
RID_CONSTEXPR, RID_DECLTYPE, RID_NOEXCEPT, RID_NULLPTR, RID_STATIC_ASSERT,
/* C++20 */
RID_CONSTINIT, RID_CONSTEVAL,
/* char8_t */
RID_CHAR8,
/* C++ concepts */
RID_CONCEPT, RID_REQUIRES,
/* C++ modules. */
RID__MODULE, RID__IMPORT, RID__EXPORT, /* Internal tokens. */
/* C++ coroutines */
RID_CO_AWAIT, RID_CO_YIELD, RID_CO_RETURN,
/* C++ transactional memory. */
RID_ATOMIC_NOEXCEPT, RID_ATOMIC_CANCEL, RID_SYNCHRONIZED,
/* Objective-C ("AT" reserved words - they are only keywords when
they follow '@') */
RID_AT_ENCODE, RID_AT_END,
RID_AT_CLASS, RID_AT_ALIAS, RID_AT_DEFS,
RID_AT_PRIVATE, RID_AT_PROTECTED, RID_AT_PUBLIC, RID_AT_PACKAGE,
RID_AT_PROTOCOL, RID_AT_SELECTOR,
RID_AT_THROW, RID_AT_TRY, RID_AT_CATCH,
RID_AT_FINALLY, RID_AT_SYNCHRONIZED,
RID_AT_OPTIONAL, RID_AT_REQUIRED, RID_AT_PROPERTY,
RID_AT_SYNTHESIZE, RID_AT_DYNAMIC,
RID_AT_INTERFACE,
RID_AT_IMPLEMENTATION,
/* Named address support, mapping the keyword to a particular named address
number. Named address space 0 is reserved for the generic address. If
there are more than 254 named addresses, the addr_space_t type will need
to be grown from an unsigned char to unsigned short. */
RID_ADDR_SPACE_0, /* generic address */
RID_ADDR_SPACE_1,
RID_ADDR_SPACE_2,
RID_ADDR_SPACE_3,
RID_ADDR_SPACE_4,
RID_ADDR_SPACE_5,
RID_ADDR_SPACE_6,
RID_ADDR_SPACE_7,
RID_ADDR_SPACE_8,
RID_ADDR_SPACE_9,
RID_ADDR_SPACE_10,
RID_ADDR_SPACE_11,
RID_ADDR_SPACE_12,
RID_ADDR_SPACE_13,
RID_ADDR_SPACE_14,
RID_ADDR_SPACE_15,
RID_FIRST_ADDR_SPACE = RID_ADDR_SPACE_0,
RID_LAST_ADDR_SPACE = RID_ADDR_SPACE_15,
/* __intN keywords. The _N_M here doesn't correspond to the intN
in the keyword; use the bitsize in int_n_t_data_t[M] for that.
For example, if int_n_t_data_t[0].bitsize is 13, then RID_INT_N_0
is for __int13. */
/* Note that the range to use is RID_FIRST_INT_N through
RID_FIRST_INT_N + NUM_INT_N_ENTS - 1 and c-parser.cc has a list of
all RID_INT_N_* in a case statement. */
RID_INT_N_0,
RID_INT_N_1,
RID_INT_N_2,
RID_INT_N_3,
RID_FIRST_INT_N = RID_INT_N_0,
RID_LAST_INT_N = RID_INT_N_3,
RID_MAX,
RID_FIRST_MODIFIER = RID_STATIC,
RID_LAST_MODIFIER = RID_ONEWAY,
RID_FIRST_CXX11 = RID_CONSTEXPR,
RID_LAST_CXX11 = RID_STATIC_ASSERT,
RID_FIRST_CXX20 = RID_CONSTINIT,
RID_LAST_CXX20 = RID_CONSTINIT,
RID_FIRST_AT = RID_AT_ENCODE,
RID_LAST_AT = RID_AT_IMPLEMENTATION,
RID_FIRST_PQ = RID_IN,
RID_LAST_PQ = RID_ONEWAY,
RID_FIRST_PATTR = RID_GETTER,
RID_LAST_PATTR = RID_NULL_RESETTABLE
};
Como se puede ver en el código anterior, se escribe una enumeración GCC
para todas las palabras clave admitidas para representar el valor específico de la palabra clave correspondiente, y la declaración de la palabra clave se coloca en ./gcc/c-family/c -En la matriz c_common_reswords
de estructura archivo common.cc .
//# gcc-12.1.0/gcc/c-family/c-common.cc ##------## const struct c_common_resword c_common_reswords[]
/* Reserved words. The third field is a mask: keywords are disabled
if they match the mask.
Masks for languages:
C --std=c89: D_C99 | D_CXXONLY | D_OBJC | D_CXX_OBJC
C --std=c99: D_CXXONLY | D_OBJC
ObjC is like C except that D_OBJC and D_CXX_OBJC are not set
C++ --std=c++98: D_CONLY | D_CXX11 | D_CXX20 | D_OBJC
C++ --std=c++11: D_CONLY | D_CXX20 | D_OBJC
C++ --std=c++20: D_CONLY | D_OBJC
ObjC++ is like C++ except that D_OBJC is not set
If -fno-asm is used, D_ASM is added to the mask. If
-fno-gnu-keywords is used, D_EXT is added. If -fno-asm and C in
C89 mode, D_EXT89 is added for both -fno-asm and -fno-gnu-keywords.
In C with -Wc++-compat, we warn if D_CXXWARN is set.
Note the complication of the D_CXX_OBJC keywords. These are
reserved words such as 'class'. In C++, 'class' is a reserved
word. In Objective-C++ it is too. In Objective-C, it is a
reserved word too, but only if it follows an '@' sign.
*/
const struct c_common_resword c_common_reswords[] =
{
{
"_Alignas", RID_ALIGNAS, D_CONLY },
{
"_Alignof", RID_ALIGNOF, D_CONLY },
{
"_Atomic", RID_ATOMIC, D_CONLY },
{
"_Bool", RID_BOOL, D_CONLY },
{
"_Complex", RID_COMPLEX, 0 },
{
"_Imaginary", RID_IMAGINARY, D_CONLY },
{
"_Float16", RID_FLOAT16, D_CONLY },
{
"_Float32", RID_FLOAT32, D_CONLY },
{
"_Float64", RID_FLOAT64, D_CONLY },
{
"_Float128", RID_FLOAT128, D_CONLY },
{
"_Float32x", RID_FLOAT32X, D_CONLY },
{
"_Float64x", RID_FLOAT64X, D_CONLY },
{
"_Float128x", RID_FLOAT128X, D_CONLY },
{
"_Decimal32", RID_DFLOAT32, D_CONLY },
{
"_Decimal64", RID_DFLOAT64, D_CONLY },
{
"_Decimal128", RID_DFLOAT128, D_CONLY },
{
"_Fract", RID_FRACT, D_CONLY | D_EXT },
{
"_Accum", RID_ACCUM, D_CONLY | D_EXT },
{
"_Sat", RID_SAT, D_CONLY | D_EXT },
{
"_Static_assert", RID_STATIC_ASSERT, D_CONLY },
{
"_Noreturn", RID_NORETURN, D_CONLY },
{
"_Generic", RID_GENERIC, D_CONLY },
{
"_Thread_local", RID_THREAD, D_CONLY },
{
"__FUNCTION__", RID_FUNCTION_NAME, 0 },
{
"__PRETTY_FUNCTION__", RID_PRETTY_FUNCTION_NAME, 0 },
{
"__alignof", RID_ALIGNOF, 0 },
{
"__alignof__", RID_ALIGNOF, 0 },
{
"__asm", RID_ASM, 0 },
{
"__asm__", RID_ASM, 0 },
{
"__attribute", RID_ATTRIBUTE, 0 },
{
"__attribute__", RID_ATTRIBUTE, 0 },
{
"__auto_type", RID_AUTO_TYPE, D_CONLY },
{
"__bases", RID_BASES, D_CXXONLY },
{
"__builtin_addressof", RID_ADDRESSOF, D_CXXONLY },
{
"__builtin_bit_cast", RID_BUILTIN_BIT_CAST, D_CXXONLY },
{
"__builtin_call_with_static_chain",
RID_BUILTIN_CALL_WITH_STATIC_CHAIN, D_CONLY },
{
"__builtin_choose_expr", RID_CHOOSE_EXPR, D_CONLY },
{
"__builtin_complex", RID_BUILTIN_COMPLEX, D_CONLY },
{
"__builtin_convertvector", RID_BUILTIN_CONVERTVECTOR, 0 },
{
"__builtin_has_attribute", RID_BUILTIN_HAS_ATTRIBUTE, 0 },
{
"__builtin_launder", RID_BUILTIN_LAUNDER, D_CXXONLY },
{
"__builtin_assoc_barrier", RID_BUILTIN_ASSOC_BARRIER, 0 },
{
"__builtin_shuffle", RID_BUILTIN_SHUFFLE, 0 },
{
"__builtin_shufflevector", RID_BUILTIN_SHUFFLEVECTOR, 0 },
{
"__builtin_tgmath", RID_BUILTIN_TGMATH, D_CONLY },
{
"__builtin_offsetof", RID_OFFSETOF, 0 },
{
"__builtin_types_compatible_p", RID_TYPES_COMPATIBLE_P, D_CONLY },
{
"__builtin_va_arg", RID_VA_ARG, 0 },
{
"__complex", RID_COMPLEX, 0 },
{
"__complex__", RID_COMPLEX, 0 },
{
"__const", RID_CONST, 0 },
{
"__const__", RID_CONST, 0 },
{
"__constinit", RID_CONSTINIT, D_CXXONLY },
{
"__decltype", RID_DECLTYPE, D_CXXONLY },
{
"__direct_bases", RID_DIRECT_BASES, D_CXXONLY },
{
"__extension__", RID_EXTENSION, 0 },
{
"__func__", RID_C99_FUNCTION_NAME, 0 },
{
"__has_nothrow_assign", RID_HAS_NOTHROW_ASSIGN, D_CXXONLY },
{
"__has_nothrow_constructor", RID_HAS_NOTHROW_CONSTRUCTOR, D_CXXONLY },
{
"__has_nothrow_copy", RID_HAS_NOTHROW_COPY, D_CXXONLY },
{
"__has_trivial_assign", RID_HAS_TRIVIAL_ASSIGN, D_CXXONLY },
{
"__has_trivial_constructor", RID_HAS_TRIVIAL_CONSTRUCTOR, D_CXXONLY },
{
"__has_trivial_copy", RID_HAS_TRIVIAL_COPY, D_CXXONLY },
{
"__has_trivial_destructor", RID_HAS_TRIVIAL_DESTRUCTOR, D_CXXONLY },
{
"__has_unique_object_representations", RID_HAS_UNIQUE_OBJ_REPRESENTATIONS,
D_CXXONLY },
{
"__has_virtual_destructor", RID_HAS_VIRTUAL_DESTRUCTOR, D_CXXONLY },
{
"__imag", RID_IMAGPART, 0 },
{
"__imag__", RID_IMAGPART, 0 },
{
"__inline", RID_INLINE, 0 },
{
"__inline__", RID_INLINE, 0 },
{
"__is_abstract", RID_IS_ABSTRACT, D_CXXONLY },
{
"__is_aggregate", RID_IS_AGGREGATE, D_CXXONLY },
{
"__is_base_of", RID_IS_BASE_OF, D_CXXONLY },
{
"__is_class", RID_IS_CLASS, D_CXXONLY },
{
"__is_empty", RID_IS_EMPTY, D_CXXONLY },
{
"__is_enum", RID_IS_ENUM, D_CXXONLY },
{
"__is_final", RID_IS_FINAL, D_CXXONLY },
{
"__is_layout_compatible", RID_IS_LAYOUT_COMPATIBLE, D_CXXONLY },
{
"__is_literal_type", RID_IS_LITERAL_TYPE, D_CXXONLY },
{
"__is_pointer_interconvertible_base_of",
RID_IS_POINTER_INTERCONVERTIBLE_BASE_OF, D_CXXONLY },
{
"__is_pod", RID_IS_POD, D_CXXONLY },
{
"__is_polymorphic", RID_IS_POLYMORPHIC, D_CXXONLY },
{
"__is_same", RID_IS_SAME_AS, D_CXXONLY },
{
"__is_same_as", RID_IS_SAME_AS, D_CXXONLY },
{
"__is_standard_layout", RID_IS_STD_LAYOUT, D_CXXONLY },
{
"__is_trivial", RID_IS_TRIVIAL, D_CXXONLY },
{
"__is_trivially_assignable", RID_IS_TRIVIALLY_ASSIGNABLE, D_CXXONLY },
{
"__is_trivially_constructible", RID_IS_TRIVIALLY_CONSTRUCTIBLE, D_CXXONLY },
{
"__is_trivially_copyable", RID_IS_TRIVIALLY_COPYABLE, D_CXXONLY },
{
"__is_union", RID_IS_UNION, D_CXXONLY },
{
"__label__", RID_LABEL, 0 },
{
"__null", RID_NULL, 0 },
{
"__real", RID_REALPART, 0 },
{
"__real__", RID_REALPART, 0 },
{
"__restrict", RID_RESTRICT, 0 },
{
"__restrict__", RID_RESTRICT, 0 },
{
"__signed", RID_SIGNED, 0 },
{
"__signed__", RID_SIGNED, 0 },
{
"__thread", RID_THREAD, 0 },
{
"__transaction_atomic", RID_TRANSACTION_ATOMIC, 0 },
{
"__transaction_relaxed", RID_TRANSACTION_RELAXED, 0 },
{
"__transaction_cancel", RID_TRANSACTION_CANCEL, 0 },
{
"__typeof", RID_TYPEOF, 0 },
{
"__typeof__", RID_TYPEOF, 0 },
{
"__underlying_type", RID_UNDERLYING_TYPE, D_CXXONLY },
{
"__volatile", RID_VOLATILE, 0 },
{
"__volatile__", RID_VOLATILE, 0 },
{
"__GIMPLE", RID_GIMPLE, D_CONLY },
{
"__PHI", RID_PHI, D_CONLY },
{
"__RTL", RID_RTL, D_CONLY },
{
"alignas", RID_ALIGNAS, D_CXXONLY | D_CXX11 | D_CXXWARN },
{
"alignof", RID_ALIGNOF, D_CXXONLY | D_CXX11 | D_CXXWARN },
{
"asm", RID_ASM, D_ASM },
{
"auto", RID_AUTO, 0 },
{
"bool", RID_BOOL, D_CXXONLY | D_CXXWARN },
{
"break", RID_BREAK, 0 },
{
"case", RID_CASE, 0 },
{
"catch", RID_CATCH, D_CXX_OBJC | D_CXXWARN },
{
"char", RID_CHAR, 0 },
{
"char8_t", RID_CHAR8, D_CXX_CHAR8_T_FLAGS | D_CXXWARN },
{
"char16_t", RID_CHAR16, D_CXXONLY | D_CXX11 | D_CXXWARN },
{
"char32_t", RID_CHAR32, D_CXXONLY | D_CXX11 | D_CXXWARN },
{
"class", RID_CLASS, D_CXX_OBJC | D_CXXWARN },
{
"const", RID_CONST, 0 },
{
"consteval", RID_CONSTEVAL, D_CXXONLY | D_CXX20 | D_CXXWARN },
{
"constexpr", RID_CONSTEXPR, D_CXXONLY | D_CXX11 | D_CXXWARN },
{
"constinit", RID_CONSTINIT, D_CXXONLY | D_CXX20 | D_CXXWARN },
{
"const_cast", RID_CONSTCAST, D_CXXONLY | D_CXXWARN },
{
"continue", RID_CONTINUE, 0 },
{
"decltype", RID_DECLTYPE, D_CXXONLY | D_CXX11 | D_CXXWARN },
{
"default", RID_DEFAULT, 0 },
{
"delete", RID_DELETE, D_CXXONLY | D_CXXWARN },
{
"do", RID_DO, 0 },
{
"double", RID_DOUBLE, 0 },
{
"dynamic_cast", RID_DYNCAST, D_CXXONLY | D_CXXWARN },
{
"else", RID_ELSE, 0 },
{
"enum", RID_ENUM, 0 },
{
"explicit", RID_EXPLICIT, D_CXXONLY | D_CXXWARN },
{
"export", RID_EXPORT, D_CXXONLY | D_CXXWARN },
{
"extern", RID_EXTERN, 0 },
{
"false", RID_FALSE, D_CXXONLY | D_CXXWARN },
{
"float", RID_FLOAT, 0 },
{
"for", RID_FOR, 0 },
{
"friend", RID_FRIEND, D_CXXONLY | D_CXXWARN },
{
"goto", RID_GOTO, 0 },
{
"if", RID_IF, 0 },
{
"inline", RID_INLINE, D_EXT89 },
{
"int", RID_INT, 0 },
{
"long", RID_LONG, 0 },
{
"mutable", RID_MUTABLE, D_CXXONLY | D_CXXWARN },
{
"namespace", RID_NAMESPACE, D_CXXONLY | D_CXXWARN },
{
"new", RID_NEW, D_CXXONLY | D_CXXWARN },
{
"noexcept", RID_NOEXCEPT, D_CXXONLY | D_CXX11 | D_CXXWARN },
{
"nullptr", RID_NULLPTR, D_CXXONLY | D_CXX11 | D_CXXWARN },
{
"operator", RID_OPERATOR, D_CXXONLY | D_CXXWARN },
{
"private", RID_PRIVATE, D_CXX_OBJC | D_CXXWARN },
{
"protected", RID_PROTECTED, D_CXX_OBJC | D_CXXWARN },
{
"public", RID_PUBLIC, D_CXX_OBJC | D_CXXWARN },
{
"register", RID_REGISTER, 0 },
{
"reinterpret_cast", RID_REINTCAST, D_CXXONLY | D_CXXWARN },
{
"restrict", RID_RESTRICT, D_CONLY | D_C99 },
{
"return", RID_RETURN, 0 },
{
"short", RID_SHORT, 0 },
{
"signed", RID_SIGNED, 0 },
{
"sizeof", RID_SIZEOF, 0 },
{
"static", RID_STATIC, 0 },
{
"static_assert", RID_STATIC_ASSERT, D_CXXONLY | D_CXX11 | D_CXXWARN },
{
"static_cast", RID_STATCAST, D_CXXONLY | D_CXXWARN },
{
"struct", RID_STRUCT, 0 },
{
"switch", RID_SWITCH, 0 },
{
"template", RID_TEMPLATE, D_CXXONLY | D_CXXWARN },
{
"this", RID_THIS, D_CXXONLY | D_CXXWARN },
{
"thread_local", RID_THREAD, D_CXXONLY | D_CXX11 | D_CXXWARN },
{
"throw", RID_THROW, D_CXX_OBJC | D_CXXWARN },
{
"true", RID_TRUE, D_CXXONLY | D_CXXWARN },
{
"try", RID_TRY, D_CXX_OBJC | D_CXXWARN },
{
"typedef", RID_TYPEDEF, 0 },
{
"typename", RID_TYPENAME, D_CXXONLY | D_CXXWARN },
{
"typeid", RID_TYPEID, D_CXXONLY | D_CXXWARN },
{
"typeof", RID_TYPEOF, D_ASM | D_EXT },
{
"union", RID_UNION, 0 },
{
"unsigned", RID_UNSIGNED, 0 },
{
"using", RID_USING, D_CXXONLY | D_CXXWARN },
{
"virtual", RID_VIRTUAL, D_CXXONLY | D_CXXWARN },
{
"void", RID_VOID, 0 },
{
"volatile", RID_VOLATILE, 0 },
{
"wchar_t", RID_WCHAR, D_CXXONLY },
{
"while", RID_WHILE, 0 },
{
"__is_assignable", RID_IS_ASSIGNABLE, D_CXXONLY },
{
"__is_constructible", RID_IS_CONSTRUCTIBLE, D_CXXONLY },
{
"__is_nothrow_assignable", RID_IS_NOTHROW_ASSIGNABLE, D_CXXONLY },
{
"__is_nothrow_constructible", RID_IS_NOTHROW_CONSTRUCTIBLE, D_CXXONLY },
/* C++ transactional memory. */
{
"synchronized", RID_SYNCHRONIZED, D_CXX_OBJC | D_TRANSMEM },
{
"atomic_noexcept", RID_ATOMIC_NOEXCEPT, D_CXXONLY | D_TRANSMEM },
{
"atomic_cancel", RID_ATOMIC_CANCEL, D_CXXONLY | D_TRANSMEM },
{
"atomic_commit", RID_TRANSACTION_ATOMIC, D_CXXONLY | D_TRANSMEM },
/* Concepts-related keywords */
{
"concept", RID_CONCEPT, D_CXX_CONCEPTS_FLAGS | D_CXXWARN },
{
"requires", RID_REQUIRES, D_CXX_CONCEPTS_FLAGS | D_CXXWARN },
/* Modules-related keywords, these are internal unspellable tokens,
created by the preprocessor. */
{
"module ", RID__MODULE, D_CXX_MODULES_FLAGS | D_CXXWARN },
{
"import ", RID__IMPORT, D_CXX_MODULES_FLAGS | D_CXXWARN },
{
"export ", RID__EXPORT, D_CXX_MODULES_FLAGS | D_CXXWARN },
/* Coroutines-related keywords */
{
"co_await", RID_CO_AWAIT, D_CXX_COROUTINES_FLAGS | D_CXXWARN },
{
"co_yield", RID_CO_YIELD, D_CXX_COROUTINES_FLAGS | D_CXXWARN },
{
"co_return", RID_CO_RETURN, D_CXX_COROUTINES_FLAGS | D_CXXWARN },
/* These Objective-C keywords are recognized only immediately after
an '@'. */
{
"compatibility_alias", RID_AT_ALIAS, D_OBJC },
{
"defs", RID_AT_DEFS, D_OBJC },
{
"encode", RID_AT_ENCODE, D_OBJC },
{
"end", RID_AT_END, D_OBJC },
{
"implementation", RID_AT_IMPLEMENTATION, D_OBJC },
{
"interface", RID_AT_INTERFACE, D_OBJC },
{
"protocol", RID_AT_PROTOCOL, D_OBJC },
{
"selector", RID_AT_SELECTOR, D_OBJC },
{
"finally", RID_AT_FINALLY, D_OBJC },
{
"optional", RID_AT_OPTIONAL, D_OBJC },
{
"required", RID_AT_REQUIRED, D_OBJC },
{
"property", RID_AT_PROPERTY, D_OBJC },
{
"package", RID_AT_PACKAGE, D_OBJC },
{
"synthesize", RID_AT_SYNTHESIZE, D_OBJC },
{
"dynamic", RID_AT_DYNAMIC, D_OBJC },
/* These are recognized only in protocol-qualifier context
(see above) */
{
"bycopy", RID_BYCOPY, D_OBJC },
{
"byref", RID_BYREF, D_OBJC },
{
"in", RID_IN, D_OBJC },
{
"inout", RID_INOUT, D_OBJC },
{
"oneway", RID_ONEWAY, D_OBJC },
{
"out", RID_OUT, D_OBJC },
/* These are recognized inside a property attribute list */
{
"assign", RID_ASSIGN, D_OBJC },
{
"atomic", RID_PROPATOMIC, D_OBJC },
{
"copy", RID_COPY, D_OBJC },
{
"getter", RID_GETTER, D_OBJC },
{
"nonatomic", RID_NONATOMIC, D_OBJC },
{
"readonly", RID_READONLY, D_OBJC },
{
"readwrite", RID_READWRITE, D_OBJC },
{
"retain", RID_RETAIN, D_OBJC },
{
"setter", RID_SETTER, D_OBJC },
/* These are Objective C implementation of nullability, accepted only in
specific contexts. */
{
"null_unspecified", RID_NULL_UNSPECIFIED, D_OBJC },
{
"nullable", RID_NULLABLE, D_OBJC },
{
"nonnull", RID_NONNULL, D_OBJC },
{
"null_resettable", RID_NULL_RESETTABLE, D_OBJC },
};
1.2.5 Tipo PRAGMA (c_token::pragma_kind)
Cuando un símbolo léxico CPP_PRAGMA
es , significa que el símbolo léxico es un indicador de guía de compilación, que se utiliza para guiar la compilación. gcc
Los símbolos de guía de compilación y los valores de símbolo para todos c
los idiomas se definen en las variables de enumeración en el archivo ./gcc/c-family/c-pragma.h .enum pragma_kind
//# gcc-12.1.0/gcc/c-family/c-pragma.h ##------## enum pragma_kind
/* Pragma identifiers built in to the front end parsers. Identifiers
for ancillary handlers will follow these. */
enum pragma_kind {
PRAGMA_NONE = 0,
PRAGMA_OACC_ATOMIC,
PRAGMA_OACC_CACHE,
PRAGMA_OACC_DATA,
PRAGMA_OACC_DECLARE,
PRAGMA_OACC_ENTER_DATA,
PRAGMA_OACC_EXIT_DATA,
PRAGMA_OACC_HOST_DATA,
PRAGMA_OACC_KERNELS,
PRAGMA_OACC_LOOP,
PRAGMA_OACC_PARALLEL,
PRAGMA_OACC_ROUTINE,
PRAGMA_OACC_SERIAL,
PRAGMA_OACC_UPDATE,
PRAGMA_OACC_WAIT,
/* PRAGMA_OMP__START_ should be equal to the first PRAGMA_OMP_* code. */
PRAGMA_OMP_ALLOCATE,
PRAGMA_OMP__START_ = PRAGMA_OMP_ALLOCATE,
PRAGMA_OMP_ATOMIC,
PRAGMA_OMP_BARRIER,
PRAGMA_OMP_CANCEL,
PRAGMA_OMP_CANCELLATION_POINT,
PRAGMA_OMP_CRITICAL,
PRAGMA_OMP_DECLARE,
PRAGMA_OMP_DEPOBJ,
PRAGMA_OMP_DISTRIBUTE,
PRAGMA_OMP_ERROR,
PRAGMA_OMP_END_DECLARE_TARGET,
PRAGMA_OMP_FLUSH,
PRAGMA_OMP_FOR,
PRAGMA_OMP_LOOP,
PRAGMA_OMP_NOTHING,
PRAGMA_OMP_MASKED,
PRAGMA_OMP_MASTER,
PRAGMA_OMP_ORDERED,
PRAGMA_OMP_PARALLEL,
PRAGMA_OMP_REQUIRES,
PRAGMA_OMP_SCAN,
PRAGMA_OMP_SCOPE,
PRAGMA_OMP_SECTION,
PRAGMA_OMP_SECTIONS,
PRAGMA_OMP_SIMD,
PRAGMA_OMP_SINGLE,
PRAGMA_OMP_TARGET,
PRAGMA_OMP_TASK,
PRAGMA_OMP_TASKGROUP,
PRAGMA_OMP_TASKLOOP,
PRAGMA_OMP_TASKWAIT,
PRAGMA_OMP_TASKYIELD,
PRAGMA_OMP_THREADPRIVATE,
PRAGMA_OMP_TEAMS,
/* PRAGMA_OMP__LAST_ should be equal to the last PRAGMA_OMP_* code. */
PRAGMA_OMP__LAST_ = PRAGMA_OMP_TEAMS,
PRAGMA_GCC_PCH_PREPROCESS,
PRAGMA_IVDEP,
PRAGMA_UNROLL,
PRAGMA_FIRST_EXTERNAL
};
1.2.6 Puntero de nodo de árbol (valor)
Para algunos símbolos léxicos, debemos prestar atención no solo a su tipo, sino también a su valor. Por ejemplo, el símbolo de constante de cadena de caracteres tiene un tipo de símbolo léxico de CPP_STRING
, y el valor de la constante de cadena de caracteres viene dado por el nodo del árbol de constantes de cadena value
al que apunta .
1.2.7 Información de ubicación (ubicación)
location
Se utiliza para describir la ubicación de la unidad léxica en el código fuente, lo que permite a los depuradores localizar rápidamente la ubicación del error. La estructura para obtener las posiciones de los símbolos se define en el archivogcc
./libcpp/include/line-map.h y la función en ./gcc/input.hexpanded_location
extern expanded_location expand_location (location_t);
//# gcc-12.1.0/libcpp/include/line-map.h ##------## expanded_location
typedef struct
{
/* The name of the source file involved. */
const char *file;
/* The line-location in the source file. */
int line;
int column;
void *data;
/* In a system header?. */
bool sysp;
} expanded_location;
//# gcc-12.1.0/gcc/input.h
extern expanded_location expand_location (location_t);
1.2.8 Ejemplos
A continuación se proporciona un ejemplo de procesamiento de analizador léxico simple.
1.3 Análisis de sintaxis y análisis semántico
De hecho, el análisis gramatical se lleva a cabo con el análisis léxico, token
cuando , obtendrá una nueva tokent
secuencia de unidades léxicas del analizador léxico, confirmará que la secuencia puede ser generada por la gramática, y si la gramática es correcta, generar un análisis gramatical El nodo del árbol finalmente genera un árbol de sintaxis completo, es decir, el árbol de sintaxis abstracta ( , Abstract Syntax Tree
) AST
, gcc
el proceso de generar un árbol de análisis de sintaxis y convertirlo a código ensamblador se describirá en detalle a continuación , y aquí hay un punto mencionado primero. gcc
El análisis gramatical del par medio se gcc-12.1.0/gcc/c/c-parser.cc
completa principalmente con cada función en el archivo, y struct c_parser
la estructura se usa para guardar el estado del análisis gramatical y la información de contexto, los símbolos léxicos preleídos actuales (hasta dos) y la función de entrada de el análisis gramatical c_parse_file()
.
c_parser
La información específica de la estructura es la siguiente:
//# gcc-12.1.0/gcc/c/c-parser.cc ##------## struct c_parser
/* A parser structure recording information about the state and
context of parsing. Includes lexer information with up to two
tokens of look-ahead; more are not needed for C. */
struct GTY(()) c_parser {
/* The look-ahead tokens. */
c_token * GTY((skip)) tokens;
/* Buffer for look-ahead tokens. */
c_token tokens_buf[4];
/* How many look-ahead tokens are available (0 - 4, or
more if parsing from pre-lexed tokens). */
unsigned int tokens_avail;
/* Raw look-ahead tokens, used only for checking in Objective-C
whether '[[' starts attributes. */
vec<c_token, va_gc> *raw_tokens;
/* The number of raw look-ahead tokens that have since been fully
lexed. */
unsigned int raw_tokens_used;
/* True if a syntax error is being recovered from; false otherwise.
c_parser_error sets this flag. It should clear this flag when
enough tokens have been consumed to recover from the error. */
BOOL_BITFIELD error : 1;
/* True if we're processing a pragma, and shouldn't automatically
consume CPP_PRAGMA_EOL. */
BOOL_BITFIELD in_pragma : 1;
/* True if we're parsing the outermost block of an if statement. */
BOOL_BITFIELD in_if_block : 1;
/* True if we want to lex a translated, joined string (for an
initial #pragma pch_preprocess). Otherwise the parser is
responsible for concatenating strings and translating to the
execution character set as needed. */
BOOL_BITFIELD lex_joined_string : 1;
/* True if, when the parser is concatenating string literals, it
should translate them to the execution character set (false
inside attributes). */
BOOL_BITFIELD translate_strings_p : 1;
/* Objective-C specific parser/lexer information. */
/* True if we are in a context where the Objective-C "PQ" keywords
are considered keywords. */
BOOL_BITFIELD objc_pq_context : 1;
/* True if we are parsing a (potential) Objective-C foreach
statement. This is set to true after we parsed 'for (' and while
we wait for 'in' or ';' to decide if it's a standard C for loop or an
Objective-C foreach loop. */
BOOL_BITFIELD objc_could_be_foreach_context : 1;
/* The following flag is needed to contextualize Objective-C lexical
analysis. In some cases (e.g., 'int NSObject;'), it is
undesirable to bind an identifier to an Objective-C class, even
if a class with that name exists. */
BOOL_BITFIELD objc_need_raw_identifier : 1;
/* Nonzero if we're processing a __transaction statement. The value
is 1 | TM_STMT_ATTR_*. */
unsigned int in_transaction : 4;
/* True if we are in a context where the Objective-C "Property attribute"
keywords are valid. */
BOOL_BITFIELD objc_property_attr_context : 1;
/* Whether we have just seen/constructed a string-literal. Set when
returning a string-literal from c_parser_string_literal. Reset
in consume_token. Useful when we get a parse error and see an
unknown token, which could have been a string-literal constant
macro. */
BOOL_BITFIELD seen_string_literal : 1;
/* Location of the last consumed token. */
location_t last_token_location;
};
La función de entrada del análisis de sintaxis c_parse_file()
es la siguiente:
//#gcc-12.1.0/gcc/c/c-parse.cc ##------## c_parse_file()
void
c_parse_file (void)
{
/* Use local storage to begin. If the first token is a pragma, parse it.
If it is #pragma GCC pch_preprocess, then this will load a PCH file
which will cause garbage collection. */
c_parser tparser;
memset (&tparser, 0, sizeof tparser);
tparser.translate_strings_p = true;
tparser.tokens = &tparser.tokens_buf[0];
the_parser = &tparser;
/* 这里预读取一个测法符号,如果是预处理符号,则进行编译的预处理 */
if (c_parser_peek_token (&tparser)->pragma_kind == PRAGMA_GCC_PCH_PREPROCESS)
c_parser_pragma_pch_preprocess (&tparser); /* 预处理 */
else
c_common_no_more_pch ();
the_parser = ggc_alloc<c_parser> (); /* 创建 c_parser 结构体 */
*the_parser = tparser;
if (tparser.tokens == &tparser.tokens_buf[0])
the_parser->tokens = &the_parser->tokens_buf[0];
/* Initialize EH, if we've been told to do so. */
if (flag_exceptions)
using_eh_for_cleanups ();
c_parser_translation_unit (the_parser); /* 这里从C语言的语法中的翻译单元 (translation unit) 非终结符开始语法分析 */
the_parser = NULL;
}
1.3.1 Cómo generar AST (árbol de sintaxis abstracta)
Aquí hay una pieza de código extremadamente simple, y puede ver de un vistazo que será result
reasignada 12
.
imaginemiracle@ubuntu:abstract_syntax_tree$ cat ast.c
int main(int argc, char **argv)
{
int a = 10;
int b = 20;
int result = 0;
result = a + b / a;
return 0;
}
Primero gcc
use el archivoAST
generado por la herramienta, es decir, el siguiente archivo, y ejecute el comando de la siguiente manera:dump
ast.c.004t.original
imaginemiracle@ubuntu:abstract_syntax_tree$ gcc -fdump-tree-original-raw ast.c
imaginemiracle@ubuntu:abstract_syntax_tree$ ls
a.out ast.c ast.c.004t.original
De hecho, el archivo generado aquí ya es el archivo de árbol de sintaxis abstracta analizado por el analizador, pero no es conveniente para nosotros leerlo. Los siguientes pasos son solo para generar resultados gráficos que sean más fáciles de entender para nosotros. Vamos a comprobarlo primero. Contenido de este archivo:
imaginemiracle@ubuntu:abstract_syntax_tree$ cat ast.c.004t.original
;; Function main (null)
;; enabled by -tree-original
@1 statement_list 0 : @2 1 : @3
@2 bind_expr type: @4 vars: @5 body: @6
@3 return_expr type: @4 expr: @7
@4 void_type name: @8 algn: 8
@5 var_decl name: @9 type: @10 scpe: @11
srcp: ast.c:3 init: @12
size: @13 algn: 32 used: 1
@6 statement_list 0 : @14 1 : @15 2 : @16
3 : @17 4 : @18
@7 modify_expr type: @10 op 0: @19 op 1: @20
@8 type_decl name: @21 type: @4
@9 identifier_node strg: a lngt: 1
@10 integer_type name: @22 size: @13 algn: 32
prec: 32 sign: signed min : @23
max : @24
@11 function_decl name: @25 type: @26 srcp: ast.c:1
args: @27 link: extern
@12 integer_cst type: @10 int: 10
@13 integer_cst type: @28 int: 32
@14 decl_expr type: @4
@15 decl_expr type: @4
@16 decl_expr type: @4
@17 modify_expr type: @10 op 0: @29 op 1: @30
@18 return_expr type: @4 expr: @31
@19 result_decl type: @10 scpe: @11 srcp: ast.c:1
note: artificial size: @13
algn: 32
@20 integer_cst type: @10 int: 0
@21 identifier_node strg: void lngt: 4
@22 type_decl name: @32 type: @10
@23 integer_cst type: @10 int: -2147483648
@24 integer_cst type: @10 int: 2147483647
@25 identifier_node strg: main lngt: 4
@26 function_type size: @33 algn: 8 retn: @10
prms: @34
@27 parm_decl name: @35 type: @10 scpe: @11
srcp: ast.c:1 argt: @10
size: @13 algn: 32 used: 0
@28 integer_type name: @36 size: @37 algn: 128
prec: 128 sign: unsigned min : @38
max : @39
@29 var_decl name: @40 type: @10 scpe: @11
srcp: ast.c:6 init: @20
size: @13 algn: 32 used: 1
@30 plus_expr type: @10 op 0: @41 op 1: @5
@31 modify_expr type: @10 op 0: @19 op 1: @20
@32 identifier_node strg: int lngt: 3
@33 integer_cst type: @28 int: 8
@34 tree_list valu: @10 chan: @42
@35 identifier_node strg: argc lngt: 4
@36 identifier_node strg: bitsizetype lngt: 11
@37 integer_cst type: @28 int: 128
@38 integer_cst type: @28 int: 0
@39 integer_cst type: @28 int: -1
@40 identifier_node strg: result lngt: 6
@41 trunc_div_expr type: @10 op 0: @43 op 1: @5
@42 tree_list valu: @44 chan: @45
@43 var_decl name: @46 type: @10 scpe: @11
srcp: ast.c:4 init: @47
size: @13 algn: 32 used: 1
@44 pointer_type size: @48 algn: 64 ptd : @49
@45 tree_list valu: @4
@46 identifier_node strg: b lngt: 1
@47 integer_cst type: @10 int: 20
@48 integer_cst type: @28 int: 64
@49 pointer_type size: @48 algn: 64 ptd : @50
@50 integer_type name: @51 size: @33 algn: 8
prec: 8 sign: signed min : @52
max : @53
@51 type_decl name: @54 type: @50
@52 integer_cst type: @50 int: -128
@53 integer_cst type: @50 int: 127
@54 identifier_node strg: char lngt: 4
Prepare dos archivos de script a continuación:
pre.awk
archivo, el contenido es el siguiente:
imaginemiracle@ubuntu:abstract_syntax_tree$ cat pre.awk
#! /usr/bin/gawk -f
/^[^;]/{
gsub(/^@/, "~@", $0);
gsub(/( *):( *)/, ":", $0);
print;
}
graphviz.awk
archivo, el contenido es el siguiente:
imaginemiracle@ubuntu:abstract_syntax_tree$ cat graphviz.awk
#! /usr/bin/gawk -f
BEGIN {
RS = "~@"; printf "digraph G {
\n node [shape = record];";}
/^[0-9]/{
s = sprintf("%s [label = \"{%s | {", $1, $1);
for(i = 2; i < NF - 1; i++)
s = s sprintf("%s | ", $i);
s = s sprintf("%s}}\"];\n", $i);
$0 = s;
while (/([a-zA-Z]+):@([0-9]+)/){
format = sprintf("\\1 \\3\n %s:\\1 -> \\2;", $1);
$0 = gensub(/([a-zA-Z]+):@([0-9]+)(.*)$/, format, "g");
};
printf " %s\n", $0;
}
END {
print "}"}
Escriba otro script run.sh
para llamar a estos dos scripts pre.awk
y tree.awk
, su contenido es el siguiente:
imaginemiracle@ubuntu:abstract_syntax_tree$ cat run.sh
./pre.awk $1.* | ./graphviz.awk > $1.dot
Una vez que el archivo esté listo, debe otorgar permisos de ejecución a estos scripts. El comando es el siguiente:
imaginemiracle@ubuntu:abstract_syntax_tree$ chmod 755 *.awk
imaginemiracle@ubuntu:abstract_syntax_tree$ chmod 755 run.sh
Para evitar el pánico causado por algunos amigos que no han instalado gawk
y no pueden ejecutar el script debido a errores, a continuación se proporcionan los comandos de instalación de los dos paquetes de software:graphviz
imaginemiracle@ubuntu:abstract_syntax_tree$ sudo apt install gawk
imaginemiracle@ubuntu:abstract_syntax_tree$ sudo apt-get install graphviz
Comience a ejecutar run.sh
el script y *.dot
se generará el archivo, aquí está ast.c.dot
:
imaginemiracle@ubuntu:abstract_syntax_tree$ bash run.sh ast.c
imaginemiracle@ubuntu:abstract_syntax_tree$ ls
a.out ast.c ast.c.004t.original ast.c.dot pre.awk run.sh tree.awk
Luego use dot
la herramienta para exportarlo como PNG
una imagen o PDF
como un archivo:
imaginemiracle@ubuntu:abstract_syntax_tree$ dot -Tpng ast.c.dot -o ast.png
imaginemiracle@ubuntu:abstract_syntax_tree$ ls
a.out ast.c ast.c.004t.original ast.c.dot ast.png pre.awk run.sh tree.awk
El siguiente es el diagrama de árbol de sintaxis abstracta ( ) correspondiente al código fuente anterior AST
. Puede comparar ast.c.004t.original
el contenido del archivo y encontrará que el contenido interno se describe en una forma más clara de diagramas de bloques y flechas, lo que facilita que las personas interpretar. Aunque el código fuente es muy simple, si los amigos "sin fundamento" y "no listo" todavía se deslumbran cuando ven el árbol de sintaxis abstracta correspondiente.
No importa, el autor intentará estudiar y analizar este deslumbrante árbol de sintaxis abstracta ( AST
) con todos. Analizamos ast.c.004t.original
el archivo y el árbol de sintaxis anterior al mismo tiempo por analogía, para que podamos entender ambos.
1.3.2 Interpretación simple de AST (Abstract Syntax Tree)
Si necesita averiguar qué significa cada nodo en el árbol de sintaxis abstracta, es decir, qué representa cada cuadro en la figura anterior, debe comenzar con el código gcc
fuente )gcc
para describe De hecho, esto se hace para implementar un nombre descriptivo común para cada nodo, y cada estructura agregada dentro se usa como la estructura de almacenamiento de características real, que es un nombre colectivo para todas estas estructuras de almacenamiento de características. Echemos un vistazo a los nodos que representan diferentes significados contenidos en el árbol de sintaxis abstracta ( ) definido en .tree_node
union
AST
AST
tree_node
tree_node
gcc
AST
[注]:一时没有找到 tree_node 定义位置的读者也不要慌张。新版本的 union tree_node 的定义被写在 ./gcc/tree-core.h中,而旧版本的 tree_node 的定义写在 ./gcc/tree.h里。
//# gcc-12.1.0/gcc/tree-core.h ##------## union tree_node
/* Define the overall contents of a tree node.
It may be any of the structures declared above
for various types of node. */
union GTY ((ptr_alias (union lang_tree_node),
desc ("tree_node_structure (&%h)"), variable_size)) tree_node {
/* 树节点的基类结构体 */
struct tree_base GTY ((tag ("TS_BASE"))) base;
/* 类型节点 */
struct tree_typed GTY ((tag ("TS_TYPED"))) typed;
/* 树节点的共有基本信息 */
struct tree_common GTY ((tag ("TS_COMMON"))) common;
/* 整型常量节点 */
struct tree_int_cst GTY ((tag ("TS_INT_CST"))) int_cst;
struct tree_poly_int_cst GTY ((tag ("TS_POLY_INT_CST"))) poly_int_cst;
/* 实数常量节点 */
struct tree_real_cst GTY ((tag ("TS_REAL_CST"))) real_cst;
/* 定点数常量节点 */
struct tree_fixed_cst GTY ((tag ("TS_FIXED_CST"))) fixed_cst;
/* 向量常量节点 */
struct tree_vector GTY ((tag ("TS_VECTOR"))) vector;
/* 字符串常量节点 */
struct tree_string GTY ((tag ("TS_STRING"))) string;
/* 复数常量节点 */
struct tree_complex GTY ((tag ("TS_COMPLEX"))) complex;
/* 标识符节点 */
struct tree_identifier GTY ((tag ("TS_IDENTIFIER"))) identifier;
/* 声明的基类 */
struct tree_decl_minimal GTY((tag ("TS_DECL_MINIMAL"))) decl_minimal;
struct tree_decl_common GTY ((tag ("TS_DECL_COMMON"))) decl_common;
/* 具有rtl属性的声明 */
struct tree_decl_with_rtl GTY ((tag ("TS_DECL_WRTL"))) decl_with_rtl;
/* 非一般声明的基类 */
struct tree_decl_non_common GTY ((tag ("TS_DECL_NON_COMMON")))
decl_non_common;
/* 参数声明的基类 */
struct tree_parm_decl GTY ((tag ("TS_PARM_DECL"))) parm_decl;
/* 具有可见性声明的基类 */
struct tree_decl_with_vis GTY ((tag ("TS_DECL_WITH_VIS"))) decl_with_vis;
/* 变量声明 */
struct tree_var_decl GTY ((tag ("TS_VAR_DECL"))) var_decl;
/* 字段声明 */
struct tree_field_decl GTY ((tag ("TS_FIELD_DECL"))) field_decl;
/* 标签声明节点 */
struct tree_label_decl GTY ((tag ("TS_LABEL_DECL"))) label_decl;
/* 返回值声明节点 */
struct tree_result_decl GTY ((tag ("TS_RESULT_DECL"))) result_decl;
/* 常量声明节点 */
struct tree_const_decl GTY ((tag ("TS_CONST_DECL"))) const_decl;
/* 类型声明节点 */
struct tree_type_decl GTY ((tag ("TS_TYPE_DECL"))) type_decl;
/* 函数声明节点 */
struct tree_function_decl GTY ((tag ("TS_FUNCTION_DECL"))) function_decl;
/* 翻译单元声明节点 */
struct tree_translation_unit_decl GTY ((tag ("TS_TRANSLATION_UNIT_DECL")))
translation_unit_decl;
struct tree_type_common GTY ((tag ("TS_TYPE_COMMON"))) type_common;
struct tree_type_with_lang_specific GTY ((tag ("TS_TYPE_WITH_LANG_SPECIFIC")))
type_with_lang_specific;
struct tree_type_non_common GTY ((tag ("TS_TYPE_NON_COMMON")))
type_non_common;
/* 列表节点 */
struct tree_list GTY ((tag ("TS_LIST"))) list;
/* 向量节点 */
struct tree_vec GTY ((tag ("TS_VEC"))) vec;
/* 表达式节点 */
struct tree_exp GTY ((tag ("TS_EXP"))) exp;
/* 静态单赋值 SSA_NAME 节点 */
struct tree_ssa_name GTY ((tag ("TS_SSA_NAME"))) ssa_name;
/* 块信息节点 */
struct tree_block GTY ((tag ("TS_BLOCK"))) block;
/* 块信息节点 */
struct tree_binfo GTY ((tag ("TS_BINFO"))) binfo;
/* 语句列表节点 */
struct tree_statement_list GTY ((tag ("TS_STATEMENT_LIST"))) stmt_list;
struct tree_constructor GTY ((tag ("TS_CONSTRUCTOR"))) constructor;
struct tree_omp_clause GTY ((tag ("TS_OMP_CLAUSE"))) omp_clause;
struct tree_optimization_option GTY ((tag ("TS_OPTIMIZATION"))) optimization;
struct tree_target_option GTY ((tag ("TS_TARGET_OPTION"))) target_option;
};
Arriba, el autor dio algunas notas sobre el significado de los nodos. A continuación, enumeramos los nodos necesarios para este análisis, que se pueden ver de manera más intuitiva.
Tipo de estructura en tree_node | nombre de la estructura | describir |
---|---|---|
struct tree_identifier GTY ((etiqueta ("TS_IDENTIFIER"))) | identificador | nodo identificador |
estructura árbol_función_decl GTY ((etiqueta ("TS_FUNCTION_DECL"))) | function_decl | nodo de declaración de función |
estructura tree_var_decl GTY ((etiqueta ("TS_VAR_DECL"))) | var_decl | nodo de declaración de variables |
estructura tree_exp GTY ((etiqueta ("TS_EXP"))) | Exp | nodo de expresión |
estructura tree_result_decl GTY ((etiqueta ("TS_RESULT_DECL"))) | result_decl | nodo de valor de retorno |
Primero vuelve al ast.c
código fuente :
int main(int argc, char **argv)
{
int a = 10;
int b = 20;
int result = 0;
result = a + b / a;
return 0;
}
El código fuente es muy simple, solo hay una main
función , que define tres int
variables de tipo a
, b
y result
asigna un valor inicial y una declaración de asignación de expresión de línea de operación result = a + b / a
. Como se puede ver en la tabla anterior, a
las definiciones y expresiones en el código fuente se utilizan principalmente en el árbol de b
sintaxis abstracta. Los nodos del árbol almacenan los nodos de declaración de variables y los nodos utilizados para almacenar expresiones . Si los conoce, puede verlos a través de Genere el árbol de sintaxis abstracta correspondiente .result
result = a + b / a
struct tree_var_decl
struct tree_exp
gcc -fdump-tree-original-raw
ast.c.004t.original
1.3.2.1 Estructura del nodo AST
En primer lugar, necesitamos saber qué significa cada campo en una línea ast.c.004t.original
en . Cada nodo está compuesto por tres unidades de la última versión. La información básica de un nodo (una línea o varias líneas debido a la longitud) es el siguiente .
campo | Estructura de almacenamiento de características correspondiente | describir |
---|---|---|
etiqueta ( TREE_CODE ) |
enumerar código_árbol | Enumeración del ID de nodo de árbol TREE_CODE |
nombre ( NAME ) |
const char * const tree_code_name[] | Matriz de cadenas de nombres de nodos de árbol |
tipo ( TREE_CODE_CLASS ) |
enum tree_code_class const char * const tree_code_class_strings[] const enum tree_code_class tree_code_type[] |
Enumeración de tipos de nodos de árboles Matriz de cadenas de nombres de tipos de nodos de árboles Matriz de tipos de nodos de árboles indexados por TREE_CODE |
longitud del operando ( len ) |
const char sin firmar tree_code_length[] | Número de operandos de nodo de árbol |
1.3.2.2 Análisis de cada nodo de AST
@1 nodo
Borre el contenido anterior, ahora analícelo desde el primer nodo del ast.c.004t.original
archivo .
@1 statement_list 0 : @2 1 : @3
# 与该节点对应的存储结构为:
# struct tree_statement_list GTY ((tag ("TS_STATEMENT_LIST"))) stmt_list;
Se puede ver que la etiqueta del nodo TREE_CODE
es @1
, es decir, es el nodo 1
No. , y el nombre del nodo NAME
es statement_list
.De esto, se puede ver que la estructura de almacenamiento característica del nodo del árbol de sintaxis correspondiente al nodo es tree_node.tree_statement_list
, y el nodo describe una lista de sentencias. Se puede ver que hay dos sentencias 0
sentencia y 1
sentencia en la lista de sentencias, 0
la sentencia apunta @2
al nodo y 1
la sentencia apunta @3
al nodo . La estructura de su diagrama es la siguiente.
@2 nodos
@2 bind_expr type: @4 vars: @5 body: @6
# 与该节点对应的存储结构为:
# struct tree_exp GTY ((tag ("TS_EXP"))) exp;
Primero @1
mire 0
la declaración en la lista de declaraciones descrita por el nodo. Puede ver que la etiqueta del nodo TREE_CODE
es @2
el nombre del nodo NAME
es bind_expr
. A partir de esto, se puede ver que el nodo describe una expresión y su característica correspondiente la estructura de almacenamiento es tree_node.tree_exp
, la estructura Puede encontrar más tipos de expresiones que se pueden describir en el archivo ./gcc/tree.def en el directorio del código fuente.
En primer lugar, hemos dejado en claro que el nodo describe una expresión de 1, type
el campo describe el tipo de valor de la expresión y type
apunta al @4
nodo y este último describe los operandos de la expresión. total Hay dos operandos que vars
apuntan al @5
nodo y body
apuntan al @6
nodo. La estructura de su diagrama es la siguiente.
Podemos verificar el tipo de operando del nodo en el archivo ./gcc/tree.def :bind_expr
- BIND_EXPR_VARS: apunta al nodo que describe la variable;
- BIND_EXPR_BODY: apunta al cuerpo de la función de expresión utilizado para calcular la variable;
- BIND_EXPR_BLOCK: el operando de bloque generalmente se usa para la depuración y no se analizará aquí.
Viendo aquí ya podemos corresponder al contenido del nodo.
campo de nodo | nodo señalador | describir |
---|---|---|
tipo | @4 | el tipo de valor de la expresión |
vars | @5 | operando de expresión, variable ( BIND_EXPR_VARS ) |
cuerpo | @6 | el cuerpo de la expresión ( BIND_EXPR_BODY ) |
@4 nodo
A continuación, analice los elementos de este nodo uno por uno, primero mire @4
el nodo ,
@4 void_type name: @8 algn: 8
# 与该节点对应的存储结构为:
# struct tree_typed GTY ((tag ("TS_TYPED"))) typed;
El nombre del nodo NAME
es void_type
, se puede ver que el nodo describe un tipo (de hecho, ya se ve por el nombre que la descripción es void
un tipo ), y a continuación se hace un breve análisis del mismo.
campo de nodo | nodo señalador | describir |
---|---|---|
nombre | @8 | el nombre del tipo |
alguno | - | Generalmente, este campo apunta a un nodo de constante entera.Aquí, el valor 8 significa que la precisión es 8bit , y está 8bit alineado |
@8 nodo y @21 nodo
@8 type_decl name: @21 type: @4
# 与该节点对应的存储结构为:
# struct tree_type_decl GTY ((tag ("TS_TYPE_DECL"))) type_decl;
Mirando de nuevo @8
el nodo NAME
, type_decl
es un nodo de un tipo declarado, y su name
campo indica que el nombre de este tipo apunta @21
al nodo, y type
el campo apunta al nodo de tipo de este tipo, que puede apuntar de nuevo @4
al nodo . Mirando @21
el nodo , puede ver intuitivamente que el nodo es un nodo identificador y el nombre del carácter del identificador es void
. La estructura de su diagrama es la siguiente.
# 标识符节点
@21 identifier_node strg: void lngt: 4
# 与该节点对应的存储结构为:
# struct tree_identifier GTY ((tag ("TS_IDENTIFIER"))) identifier;
Poco conocimiento: aquí hay una pequeña explicación para el nodo identificador.
campo nodo_identificador | describir |
---|---|
control | string Representa el nombre del identificador como una cadena ( ) |
largo | longitud del identificador ( length ) |
El nodo @5
luego retrocede y analiza el siguiente campo del @2
nodo , que es su primer operando vars: @5
.
@5 var_decl name: @9 type: @10 scpe: @11
srcp: ast.c:3 init: @12
size: @13 algn: 32 used: 1
# 与该节点对应的存储结构为:
# struct tree_var_decl GTY ((tag ("TS_VAR_DECL"))) var_decl;
Poco conocimiento: Aquí está el resultado del análisis detallado del @5
nodo var_decl
, que es el nodo. Los socios interesados también pueden obtener el resultado basado en el análisis del árbol de sintaxis abstracta.
campo var_decl | describir | @5 valor de nodo |
---|---|---|
nombre | El nombre de la variable descrita por este nodo. | a |
tipo | el tipo de variable descrito por este nodo | int |
ámbito | Alcance ( scope ) |
main() alcance de la función |
srcp | programa fuente ( source progarm ) |
en ast.c fuente 3 línea línea |
en eso | valor inicial ( initial ) |
El valor inicial es10 |
tamaño | tamaño variable | 32bit |
alguno | bits de alineación ( align ) |
Presione para 32bit alinear |
usado | Citas | 1 de segunda categoría |
Por el nombre del nodo var_decl
, sabemos que el nodo describe la declaración de una variable. Aunque el nodo tiene muchos campos, de hecho, cuando analizamos la declaración de una variable, a menudo solo nos importan tres cosas, a saber, el nombre de la variable, el tipo y valor inicial de la variable. , es decir, el contenido de los tres campos de name
, type
y en init
este nodo.
@10 nodo
De acuerdo con los hábitos habituales, primero veamos el tipo de la variable type: @10
. Dado que el análisis del tipo se ha seguido y verificado en detalle anteriormente, no lo explicaré en detalle aquí. El método de análisis es el mismo que La siguiente lista incluye información de varios nodos clave relacionados con ella.
# 类型节点——声明整型类型节点
@10 integer_type name: @22 size: @13 algn: 32
prec: 32 sign: signed min : @23
max : @24
# 与该节点对应的存储结构为:
# struct tree_typed GTY ((tag ("TS_TYPED"))) typed;
@22 type_decl name: @32 type: @10
# 与该节点对应的存储结构为:
# struct tree_type_decl GTY ((tag ("TS_TYPE_DECL"))) type_decl;
@32 identifier_node strg: int lngt: 3
# 与该节点对应的存储结构为:
# struct tree_identifier GTY ((tag ("TS_IDENTIFIER"))) identifier;
Poco conocimiento: Aquí está el resultado del análisis detallado del @10
nodo integer_type
, que es el nodo. Los socios interesados también pueden obtener el resultado basado en el análisis del árbol de sintaxis abstracta.
campo tipo_entero | describir | @10 valor de nodo |
---|---|---|
nombre | El nombre de la descripción del nodo. | int |
tamaño | el tamaño del tipo descrito por este nodo | 32bit |
alguno | bits de alineación ( align ) |
Presione para 32bit alinear |
perc | La precisión del tipo. | El tipo es 32bit de precisión. |
firmar | Con o sin signo | signed tipo firmado |
min | valor mínimo | -2^31 = -2147483648 |
máximo | valor máximo | 2^31 = 2147483648 |
Se puede ver en los nodos anteriores que la variable de este @5
nodo es int
una variable de tipo. Veamos el nombre de la variable de la variable name: @9
.Se puede ver intuitivamente que el nombre de la variable debe ser a
.
# 标识符节点
@9 identifier_node strg: a lngt: 1
# 与该节点对应的存储结构为:
# struct tree_identifier GTY ((tag ("TS_IDENTIFIER"))) identifier;
Veamos el valor inicial de esta variable init: @12
, que es el 12
nodo . Se puede ver claramente que el nodo es un nodo constante entero integer_cst
cuyo valor es int: 10
. La estructura de su diagrama es la siguiente.
# 整型常量节点
@12 integer_cst type: @10 int: 10
# 与该节点对应的存储结构为:
# struct tree_int_cst GTY ((tag ("TS_INT_CST"))) int_cst;
到这里我们就清楚了 @5
节点描述的是一个类型为 int
型,名为 a
,初始值为 10
的整型变量,也就是源码中的 int a = 10;
# C语言定义
int a = 10;
# ===========================分隔符===========================
# 抽象语法树对其的描述
@5 var_decl name: @9 type: @10 scpe: @11
srcp: ast.c:3 init: @12
size: @13 algn: 32 used: 1
@9 identifier_node strg: a lngt: 1
@10 integer_type name: @22 size: @13 algn: 32
prec: 32 sign: signed min : @23
max : @24
@11 function_decl name: @25 type: @26 srcp: ast.c:1
args: @27 link: extern
@12 integer_cst type: @10 int: 10
@13 integer_cst type: @28 int: 32
@22 type_decl name: @32 type: @10
@32 identifier_node strg: int lngt: 3
到这里我们分析的内容,笔者在抽象语法树的图中摘了出来,可以直观的梳理分析过的思路。
@6 节点
继续来分析 @2
节点的下一个操作数 body: @6
,可以看得出 @6
节点是描述一段语句的节点,其语句包含节点 0: @14
、1: @15
、2: @16
、3: @17
、4: @18
。
@6 statement_list 0 : @14 1 : @15 2 : @16
3 : @17 4 : @18
# 与该节点对应的存储结构为:
# struct tree_statement_list GTY ((tag ("TS_STATEMENT_LIST"))) stmt_list;
从 ast.c.004t.original
文件里可以看到节点 @14
、@15
、@16
这三个几点是同一类型节点,且类型指向也相同 @4
,三个节点均是描述的是一个空的表达式。因此这里这三个节点是没什么实际意义的。
@14 decl_expr type: @4
@15 decl_expr type: @4
@16 decl_expr type: @4
@17 节点
@17 modify_expr type: @10 op 0: @29 op 1: @30
# 与该节点对应的存储结构为:
# struct tree_exp GTY ((tag ("TS_EXP"))) exp;
从该节点名 modify_expr
可以看出该节点描述的是一段赋值表达式,type
字段表示该表达式的取值类型指向节点 @10
,由于上文已经对其类似的分析过了,这里就不在赘述,该表达式的取值类型为 int
,其它字段含义可以在 ./gcc/tree.def 文件中索引找到,如下。
在该文件中指出,MODIFY_EXPR
表达式有两个操作数,其中 Operand 0
是需要被赋值的操作数,Operand 1
就是将要赋值给 Operand 0
的具体值。用 c
语言表达就相当于是 op_0 = op_1;
,这样子的一条简单的赋值语句。图示结构如下。
@29 节点
@29 var_decl name: @40 type: @10 scpe: @11
srcp: ast.c:6 init: @20
size: @13 algn: 32 used: 1
# 与该节点对应的存储结构为:
# struct tree_var_decl GTY ((tag ("TS_VAR_DECL"))) var_decl;
那么首先来看 op 0: @29
。该节点 NAME
为 var_decl
,可以看出它是描述一个变量的节点,其分析方法可参考上文 @5
节点的分析过程,这里不再赘述,直接给出分析结果。@29
节点描述的是一个名为 result
,类型 int
型,初始值为 0
的一个整型变量。即 ast.c
源码中对 result
变量的定义, int result = 0;
@30 节点
@30 plus_expr type: @10 op 0: @41 op 1: @5
# 与该节点对应的存储结构为:
# struct tree_exp GTY ((tag ("TS_EXP"))) exp;
从该节点名 plus_expr
可以看出该节点描述的是一段简单的加法运算表达式,type
字段同样描述的是该表达式的取值类型为 int
,其后跟的两个操作数为两个加数,即 op 0
和 op 1
需要相加(op_0 + op_1
)。首先来看 op 0: @41
。
@41 节点
@41 trunc_div_expr type: @10 op 0: @43 op 1: @5
# 与该节点对应的存储结构为:
# struct tree_exp GTY ((tag ("TS_EXP"))) exp;
该节点名为 trunc_div_expr
,是描述一段整数的除法运算(即只保留商的整数结果的除法运算),其中 op 0
为被除数,op 1
为除数 (op_0 / op_1
)。其图示结构如下。
@43 节点
@43 var_decl name: @46 type: @10 scpe: @11
srcp: ast.c:4 init: @47
size: @13 algn: 32 used: 1
# 与该节点对应的存储结构为:
# struct tree_var_decl GTY ((tag ("TS_VAR_DECL"))) var_decl;
@41
节点的 op0
指向的节点 @43
节点名 NAME
为 var_decl
,同样是描述一个变量定义,同 @5
节点的分析过程一样此处直接给出分析结果,该节点事实上是描述的源码 ast.c
中的 b
变量的定义以及初始化,int b = 20;
而@41
节点的 op0
指向的节点 @5
,我们已经分析过了,是int a = 10;
,因此 @41
节点描述的表达式即为 (int) b / (int) a;
即 @30
节点的 op 0
的值为 (int)20 / (int)10 = (int)2
。
而 @30
节点的 op 1
指向 @5
节点,因此 @30
节点描述的表达是则为 (op_0 + op_1) == ((int)2 + (int)10)
,因此 @30
节点的值为 (int) 12
。
到这里我们就清楚了 @17
节点表达式描述的是,(int) result = (int) 20 / (int) 10 + (int) 10 = 12;
,此时 result
变量应被赋值为 12
。
@18 节点 && @31 节点
而这时候 @6
节点描述的语句列表也被我们分析的只剩 @18
节点。该节点较为简单,下面将其有关的几个节点均列出来。
@18 return_expr type: @4 expr: @31
# 与该节点对应的存储结构为:
# struct tree_exp GTY ((tag ("TS_EXP"))) exp;
@31 modify_expr type: @10 op 0: @19 op 1: @20
# 与该节点对应的存储结构为:
# struct tree_exp GTY ((tag ("TS_EXP"))) exp;
@19 result_decl type: @10 scpe: @11 srcp: ast.c:1
note: artificial size: @13
algn: 32
# 与该节点对应的存储结构为:
# struct tree_result_decl GTY ((tag ("TS_RESULT_DECL"))) result_decl;
@20 integer_cst type: @10 int: 0
# 与该节点对应的存储结构为:
# struct tree_int_cst GTY ((tag ("TS_INT_CST"))) int_cst;
经过上面对一系列的节点分析,相信才华横溢的大家已经可以很轻松的读懂该节点的内容,首先该节点描述的是取值类型为 @4
节点即 void
类型,也就是该表达式无返回值,表达式主体指向的是 @31
节点,一段赋值一句,将一个整型常量 0
赋值给定义的函数返回值,即表示源码 ast.c
中的最后一条语句 return 0;
,其图示结构如下。
@3 节点
现在来观察第三个节点 return_expr
。
@3 return_expr type: @4 expr: @7
# 与该节点对应的存储结构为:
# struct tree_exp GTY ((tag ("TS_EXP"))) exp;
可以看的出来该节点与我们刚刚分析过的 @18
节点所描述的表达式是完全相同的,只是对应节点不同而已,其含义同样是 return 0;
,其描述的事实上是 int main()
函数的默认返回值是 0
,有好奇心的小伙伴可以将 ast.c
中最后一条 return 0;
改为 return 666;
(数字随意),再生成抽象语法树 AST
查看就会更加清楚了。下面笔者附上修改后生成 AST
文件,作为对比学习。
// 修改后的 ast.c
imaginemiracle@ubuntu:test$ cat ast.c
int main(int argc, char **argv)
{
int a = 10;
int b = 20;
int result = 0;
result = a + b / a;
return 666;
}
这里只附上关键几个节点信息,足够用来明确两个 return_expr
节点分别描述的内容。
// 修改后的 ast.c.004t.original
@1 statement_list 0 : @2 1 : @3
@2 bind_expr type: @4 vars: @5 body: @6
@3 return_expr type: @4 expr: @7
@6 statement_list 0 : @14 1 : @15 2 : @16
3 : @17 4 : @18
@7 modify_expr type: @10 op 0: @19 op 1: @20
@18 return_expr type: @4 expr: @31
@19 result_decl type: @10 scpe: @11 srcp: test.c:1
note: artificial size: @13
algn: 32
@20 integer_cst type: @10 int: 0
@31 modify_expr type: @10 op 0: @19 op 1: @42
@42 integer_cst type: @10 int: 666
到此整个 ast.c
源码的代码主体部分已经完全分析完成,只剩下一个 main
函数的声明节点 @ 26
未作分析,事实上对该节点分析并不难,感兴趣的小伙伴可以自行进行分析,参考源码和 gcc
目录下 ./gcc/tree-core.h中定义的 tree_node
,以及 ./gcc/tree.def 文件来分析。综合以上分析的内容以及 main
函数的定义节点即构成下面这副完整的 ast.c
源码的抽象语法树(AST
)图示结果。
1.4. 中间代码生成 (GIMPLE)
在语法&语义分析后的下一个步骤就是生成中间代码,语法分析阶段输出的表达式或程序语句是以语法树 (AST/GENERIC
) 的形式存储,编译器并无法直接将其编译生成汇编程序,此时需要将其转换为一种中间代码。中间代码 (GIMPLE
) 是编译过程中的一种临时代码,中间代码是 gcc
为了处理不同的前端语言而引入的一种与平台无关的代码。
在 gcc
中将完成对抽象语法树 (AST/GENERIC
) 转换为中间代码 (GIMPLE
) 这一过程命名为 pass
,gcc
就是利用 pass
这一个抽象的名词代表了这一转换过程中的各种具体方法。在 gcc-12.1.0
中将 pass
的核心结构定义在了 gcc-12.1.0/gcc/tree-pass.h 中的 class opt_pass
类中。
/* An instance of a pass. This is also "pass_data" to minimize the
changes in existing code. */
class opt_pass : public pass_data
{
public:
virtual ~opt_pass () {
}
/* Create a copy of this pass.
Passes that can have multiple instances must provide their own
implementation of this, to ensure that any sharing of state between
this instance and the copy is "wired up" correctly.
The default implementation prints an error message and aborts. */
virtual opt_pass *clone ();
virtual void set_pass_param (unsigned int, bool);
/* This pass and all sub-passes are executed only if the function returns
true. The default implementation returns true. */
virtual bool gate (function *fun);
/* This is the code to run. If this is not overridden, then there should
be sub-passes otherwise this pass does nothing.
The return value contains TODOs to execute in addition to those in
TODO_flags_finish. */
virtual unsigned int execute (function *fun);
protected:
opt_pass (const pass_data&, gcc::context *);
public:
/* A list of sub-passes to run, dependent on gate predicate. */
opt_pass *sub;
/* Next in the list of passes to run, independent of gate predicate. */
opt_pass *next;
/* Static pass number, used as a fragment of the dump file name. */
int static_pass_number;
protected:
gcc::context *m_ctxt;
};
为了说明问题,我们为 ast.c
代码增加一点内容,下面我们看一下 ast.c
生成的语法树 (AST/GENERIC
),即上文看到 ast.c.004t.original
文件([注]:此处的该文件是语法树的 GENERIC 表达形式
),和所生成的中间代码 (GIMPLE
),此处的中间代码形式为三地址码形式。
添加内容后的 ast.c
文件:
imaginemiracle:ast$ cat ast.c
int main(int argc, char **argv)
{
int a = 10;
int b = 20;
int result = 0;
result = a + b / a;
if (result > (a + b)) {
result -= 1;
} else {
result += 1;
}
return 0;
}
1.4.1. 语法树的GENERIC形式
ast.c
生成的语法树 (AST/GENERIC
),GENERIC
表达形式:
imaginemiracle:ast$ gcc -fdump-tree-original-all ast.c
imaginemiracle:ast$ ls
ast.c.004t.original a.out ast.c
imaginemiracle:ast$ cat ast.c.004t.original
;; Function main (null)
;; enabled by -tree-original
{
int a = 10;
int b = 20;
int result = 0;
int a = 10;
int b = 20;
int result = 0;
result = b / a + a;
if (a + b < result)
{
result = result + -1;
}
else
{
result = result + 1;
}
return 0;
}
return 0;
1.4.2. 中间代码 (GIMPLE)——三地址码形式
ast.c
生成的中间代码——三地址码形式:
imaginemiracle:ast$ gcc -fdump-tree-gimple ast.c
imaginemiracle:ast$ ls
ast.c.004t.original ast.c.005t.gimple a.out ast.c
imaginemiracle:ast$ cat ast.c.005t.gimple
int main (int argc, char * * argv)
{
int D.1953;
{
int a;
int b;
int result;
a = 10;
b = 20;
result = 0;
_1 = b / a;
result = a + _1;
_2 = a + b;
if (result > _2) goto <D.1950>; else goto <D.1951>;
<D.1950>:
result = result + -1;
goto <D.1952>;
<D.1951>:
result = result + 1;
<D.1952>:
D.1953 = 0;
return D.1953;
}
D.1953 = 0;
return D.1953;
}
1.4.3. AST/GENERIC 与 GIMPLE 的区别
中间代码 (GIMPLE
) 和语法树 (AST/GENERIC
) 的区别(也可以说是优点):
- 与语言无关的中间表示
语法树 (AST
) 形式与前端的编程语言是高度相关的,即每种语言通过对应的词法&语法分析后生成的AST
是异构的(即不同语言生成的语法树结构是不同的,如Java、C++两种语言即便描述的是同一个表达式、语句或者是同功能的代码,但使用其对应的编译器所生成的语法树也是不同的,即异构)。而中间代码 (GIMPLE
) 表示形式则是与语言无关的,任何语言再转换成中间代码 (GIMPLE
) 形式时,都需遵从GIMPLE
规范,从而最终将转换为同构的中间代码。 - 具有线性序列特征
语法树 (AST
) 是树形表达结构,难以进行编译处理([注]: 其树形结构只是难处理的一个原因,还包括语法树本身的异构性等原因并不能直接对语法树直接编译生成汇编代码
),而中间代码 (GIMPLE
) 本质上是一种线性的表达序列,编译器能够更加方便、高效做后续的编译处理,即编译成目标代码。
[注]:虽然中间代码是一种线性表达序列,但依然会使用树形结构来描述表达式中的操作符、操作数等元素。但其宏观上讲还是一种线性结构,可以说是利用了树节点的便利性。
1.4.4. GIMPLE 的特点
A través de la comparación anterior, el código intermedio ( GIMPLE
) es un tipo de código temporal con isomorfismo que es más fácil de manejar para el compilador. Los códigos intermedios comunes incluyen "código de tres direcciones", "código P", etc.
GIMPLE
Introduzca una variable temporal para guardar el resultado intermedio y dividaAST
la expresión en una tupla de no más de tres operandos (Tuples
);- Dado que
GIMPLE
la forma es esencialmente una secuencia de código lineal, todas las expresiones computacionales se expresan como una serie de operaciones básicas.AST
Las estructuras de control en (p. ej.,if-else
,for
,while
etc.GIMPLE
) se convierten en instrucciones de salto condicional (goto
completadas con ) en la representación; AST
Se suprime el alcance léxico (Lexical Scopes
)GIMPLE
en .