[C language advanced] entorno de programa y preprocesamiento

contenido

El entorno de traducción y el entorno de ejecución del programa.   

Compilación detallada + enlace

Entorno de traducción

La compilación en sí también se divide en varias etapas.

Entorno operativo

 Preprocesamiento detallado

 símbolos predefinidos

 #definir

 #define definir identificador

 #define definir macro

 #define reglas de sustitución

 #y##

Argumentos macro con efectos secundarios

Comparación de macros y funciones

convenio de denominación

#undef

compilación condicional

Directivas comunes de compilación condicional

el archivo contiene

Cómo se incluyen los archivos de encabezado

El archivo anidado contiene


El entorno de traducción y el entorno de ejecución del programa.   

En cualquier implementación de ANSI C, hay dos entornos distintos.
El primero es un entorno de traducción, en el que el código fuente se traduce en instrucciones de máquina ejecutables.
El segundo es el entorno de ejecución, que se utiliza para ejecutar realmente el código.

Compilación detallada + enlace

Entorno de traducción

Cada archivo fuente que compone un programa se convierte en código objeto (object code) por separado a través del proceso de compilación (la compilación se divide en preprocesamiento (pre-compilation), compilación y ensamblaje).
El enlazador agrupa cada archivo de objeto para formar un programa ejecutable único y completo.
El enlazador también introduce cualquier función en la biblioteca de funciones C estándar que utiliza el programa, y ​​puede buscar en la biblioteca personal del programador y vincular las funciones que necesita en el programa.

La compilación en sí también se divide en varias etapas.

Entorno operativo

El proceso de ejecución del programa:

1. El programa debe cargarse en la memoria. En un entorno con sistema operativo: Normalmente esto lo hace el sistema operativo. En un entorno independiente, la carga de programas debe organizarse manualmente, posiblemente colocando el código ejecutable en la memoria de solo lectura.
2. Comienza la ejecución del programa. Luego llame a la función principal.
3. Comience a ejecutar el código del programa. En este punto, el programa utilizará una pila de tiempo de ejecución (stack) para almacenar las variables locales de la función y las direcciones de retorno. Los programas también pueden utilizar la memoria estática.Las variables almacenadas en la memoria estática conservan sus valores durante la ejecución del programa.
4. Finalice el programa. Termina la función principal normalmente; también puede terminar inesperadamente.

 Preprocesamiento detallado

 símbolos predefinidos

__FILE__ // el archivo fuente para compilar
__LINE__ // el número de línea actual del
archivo __DATE__ // la fecha
en que se compiló el archivo __TIME__ // la hora en que se compiló el archivo
__STDC__ // 1 si el compilador sigue ANSI C, de lo contrario, no definido ( No compatible con VS2019)
Estos símbolos predefinidos están integrados en el lenguaje.

Por ejemplo:
printf ("archivo:%s línea:%d\n", __FILE__, __LINE__);

 #definir

 #define definir identificador

Sintaxis:
#define name stuff
Por ejemplo:
#define MAX 1000
#define reg register // Crea un nombre corto para la palabra clave de registro
#define do_forever for(;;) // Reemplaza uno con una notación más vívida Implementa
#define CASE break; case // Escribir automáticamente break al escribir una declaración de caso.
// Si el material definido es demasiado largo, se puede dividir en varias líneas, excepto la última línea, cada línea va seguida de una barra invertida (carácter de continuación).
#define DEBUG_PRINT printf("archivo:%s\tline:%d\t \
                          date:%s\ttime:%s\n" ,\
                          __FILE__,__LINE__ , \
                          __DATE__,__TIME__ )  
Nota: Cuando se define #define, el siguiente Es mejor no agregar ";", es propenso a errores.
3.2.2 #define Definición de macros
El mecanismo #define incluye una disposición que permite la sustitución de parámetros en el texto, una implementación comúnmente conocida como macro o define macro.
 

 #define definir macro

El mecanismo #define incluye una disposición que permite la sustitución de parámetros en el texto, una implementación comúnmente conocida como macro o definir macro.

Así es como se declara la macro:

#define name( parament-list ) stuff

donde la lista de parámetros es una lista de símbolos separados por comas que pueden aparecer en las cosas.

Nota :

El paréntesis de apertura de la lista de parámetros debe estar inmediatamente junto al nombre.
Si hay algún espacio en blanco en el medio, la lista de parámetros se interpreta como parte del material.

Como:

#define SQUARE( x ) x * x

Esta macro toma un parámetro X.
Si después de la declaración anterior, pones

SQUARE ( 5 );

Colocado en un programa, el preprocesador reemplaza la expresión anterior con esta expresión:

//5 * 5

Advertencia:
hay un problema con esta macro:
observe el siguiente fragmento de código:

int a = 5 ;
printf ( "%d\n" , SQUARE ( a + 1 ) );

A primera vista, podría pensar que este código imprimirá el valor 36.
De hecho, imprimirá 11.
¿Por qué?
Al reemplazar texto, el parámetro x se reemplaza por un + 1, por lo que la declaración en realidad se convierte en:

printf ("%d\n",a + 1 * a + 1 );

Esto deja en claro que las expresiones resultantes de la sustitución no se evalúan en el orden esperado.

Este problema se resuelve fácilmente agregando dos paréntesis a la definición de la macro:

#define SQUARE(x) (x) * (x)

Este preprocesamiento produce el efecto esperado:

printf ( "%d\n" ,( a + 1 ) * ( a + 1 ) );

Aquí hay otra definición de macro:

#define DOUBLE(x) (x) + (x)

Usamos paréntesis en la definición para evitar el problema anterior, pero esta macro puede introducir nuevos errores.

int a = 5 ;
printf ( "%d\n" , 10 * DOUBLE ( a ));

¿Qué valor imprimirá esto?
Advertencia:
parece que imprime 100, pero en realidad imprime 55.
Descubrimos que después de reemplazar:

printf ( "%d\n" , 10 * ( 5 ) + ( 5 ));


La operación de multiplicación precede a la suma definida por la macro, por lo que hay
 

//55 .

La solución a este problema es agregar un par de paréntesis alrededor de la expresión de definición de macro.

#define DOUBLE( x)   ( ( x ) + ( x ) )

Sugerencia :
todas las definiciones de macro utilizadas para evaluar expresiones numéricas deben estar entre paréntesis de esta manera para evitar interacciones impredecibles entre operadores en argumentos u operadores adyacentes al usar la macro.

 #define reglas de sustitución

Hay varios pasos involucrados al expandir #define para definir símbolos y macros en un programa.
1. Al llamar a la macro, primero se verifican los parámetros para ver si contienen algún símbolo definido por #define. Si es así, se reemplazan primero.
2. Luego, el texto de reemplazo se inserta en el programa en lugar del texto original. Para las macros, los nombres de los parámetros se reemplazan por sus valores.
3. Finalmente, vuelva a escanear el archivo resultante para ver si contiene algún símbolo definido por #define. Si es así, repita el proceso anterior.

Nota:
1. Pueden aparecer otros símbolos definidos por #define en parámetros macro y definiciones de #define. Pero para las macros, la recursividad no puede ocurrir.
2. Cuando el preprocesador busca símbolos definidos por #define, no se busca el contenido de las constantes de cadena.

 # y ##

rol

Utilice # para convertir un parámetro de macro en la cadena correspondiente.
por ejemplo:

#include <stdio.h>
#define PRINT(FORMAT, VALUE) printf("the value of " #VALUE " is "FORMAT "\n", VALUE);

int main()
{
	int i = 10;

	PRINT("%d", i + 3);//产生了什么效果?
	return 0;
}

reemplazar el código posterior

#include <stdio.h>

int main()
{
	int i = 10;

	printf("the value of i + 3 is %d \n", i+3);
	return 0;
}

El rol de ##

## puede combinar los símbolos a cada lado en un solo símbolo.

Permite que las definiciones de macro creen identificadores a partir de fragmentos de texto independientes.

#include <stdio.h>
#define ADD_TO_SUM(num, value) sum##num += value;

int main()
{
	int sum5 = 20;
	ADD_TO_SUM(5, 10);//作用是:给sum5增加10

	return 0;
}

reemplazar el código posterior

#include <stdio.h>


int main()
{
	int sum5 = 20;
	sum5 += 10;
	return 0;
}

Argumentos macro con efectos secundarios

Cuando un parámetro de macro aparece más de una vez en la definición de macro, si el parámetro tiene efectos secundarios, entonces puede ser peligroso al usar la macro, lo que puede tener consecuencias impredecibles. Un efecto secundario es un efecto permanente que ocurre cuando se evalúa una expresión.
P.ej:

x + 1 ; // 不带副作用
x ++ ; // 带有副作用

La macro MAX puede demostrar problemas causados ​​por argumentos con efectos secundarios.

#include <stdio.h>
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )

int main()
{
	int x = 5;
	int y = 8;
	int z = MAX(x++, y++);
	printf("x=%d y=%d z=%d\n", x, y, z); // 输出的结果是什么?
	return 0;
}

Aquí tenemos que saber cuál es el resultado del procesamiento del preprocesador:

z = ( ( x ++ ) > ( y ++ ) ? ( x ++ ) : ( y ++ ));

Entonces la salida es:

x = 6 y = 10 z = 9

Comparación de macros y funciones

Atributos #define definir macro función
longitud del código El código de macro se inserta en el programa cada vez que se utiliza. Excepto para
macros muy pequeñas, la duración del programa puede crecer sustancialmente
El código de función aparece en un solo lugar; cada
vez que se usa la función, se llama al mismo código en ese
lugar
velocidad de ejecución más rápido Hay una sobrecarga adicional de llamada y retorno de función
, por lo que es relativamente lento
precedencia del operador Los argumentos de macro se evalúan en el contexto de todas las expresiones circundantes.
A menos que se agreguen paréntesis, la precedencia de los operadores adyacentes puede tener
consecuencias impredecibles, por lo que se recomienda que las macros se escriban con más paréntesis
.
Un parámetro de función se evalúa solo
una vez cuando se llama a la función y su valor resultante se pasa a la función
. Los resultados de la evaluación de expresiones son más
predecibles.
Parámetros con efectos secundarios Los argumentos se pueden sustituir en varios lugares del cuerpo de la macro, por lo que los efectos secundarios de la
evaluación de argumentos pueden producir resultados impredecibles.
Los parámetros de función solo se evalúan
una vez cuando se pasan, y el resultado es más fácil de controlar.
Tipo de parámetro Los parámetros de una macro son independientes del tipo y
se pueden usar con cualquier tipo de parámetro siempre que la operación en el parámetro sea legal.
Los parámetros de función dependen del tipo y,
si los parámetros son de tipos diferentes,
se requieren funciones diferentes, incluso si las tareas que realizan son
diferentes.
depuración Las macros son inconvenientes para depurar Las funciones se pueden depurar declaración por declaración
recursión Las macros no pueden ser recursivas Las funciones pueden ser recursivas.

convenio de denominación

En general, la sintaxis para usar macros para funciones es similar. Entonces, el lenguaje en sí mismo no puede ayudarnos a distinguir entre los dos.
Entonces, uno de nuestros hábitos habituales es: poner en
mayúscula los nombres de las macros
y no poner en mayúsculas los nombres de las funciones.

#undef

Esta directiva se utiliza para eliminar una definición de macro.

#undef NAME
// 如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。

compilación condicional

A la hora de compilar un programa, es conveniente si queremos compilar o descartar una sentencia (un grupo de sentencias). Porque tenemos directivas de compilación condicionales.

 Por ejemplo: código de depuración, eliminarlo es una lástima, mantenerlo en el camino, para que podamos compilar selectivamente.

#include <stdio.h>
#define __DEBUG__ 
int main()
{
	int i = 0;
	int arr[10] = { 0 };
	for (i = 0; i < 10; i++)
	{
		arr[i] = i;
#ifdef __DEBUG__
		printf("%d\n", arr[i]);//为了观察数组是否赋值成功。 
#endif //__DEBUG__
	}
	return 0;
}

Directivas comunes de compilación condicional

1.

#if 常量表达式
//...
#endif
// 常量表达式由预处理器求值。

Como:

#define __DEBUG__ 1
#if __DEBUG__
//..
#endif

2. Compilación condicional de múltiples ramas

#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif

3. Determinar si está definido

#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol

4. Directivas anidadas
 

#if defined(OS_UNIX)
        #ifdef OPTION1
                unix_version_option1 ();
        #endif
        #ifdef OPTION2
                unix_version_option2 ();
        #endif
#elif defined(OS_MSDOS)
        #ifdef OPTION2
                msdos_version_option2 ();
        #endif
#endif

el archivo contiene

Ya sabemos que la directiva #include puede hacer que se compile otro archivo. como si realmente apareciera en el lugar de la directiva #include.

 La forma en que funciona este reemplazo es simple: el preprocesador primero elimina esta directiva y la reemplaza con el contenido del archivo de inclusión. Dicho archivo fuente se incluye 10 veces, en realidad se compila 10 veces.

Cómo se incluyen los archivos de encabezado

el archivo local contiene

#incluye "nombre de archivo"

Estrategia de búsqueda: primero busque en el directorio donde se encuentra el archivo de origen. Si no se encuentra el archivo de encabezado, el compilador buscará el archivo de encabezado en la ubicación estándar al igual que el archivo de encabezado de la función de biblioteca.
Si no se encuentra, generará un error de compilación.

Ruta a los archivos de encabezado estándar para el entorno VS (2019): 

C:\Archivos de programa (x86)\Microsoft Visual Studio 12.0\VC\include

Preste atención para encontrarlo de acuerdo con su propia ruta de instalación.

El archivo de biblioteca contiene
#include <filename.h>
para encontrar el archivo de encabezado e ir directamente a la ruta estándar para encontrarlo. Si no se encuentra, se generará un error de compilación.
¿Es posible decir que los archivos de la biblioteca también se pueden incluir en forma de ""?

La respuesta es sí .
Sin embargo, la eficiencia de búsqueda es menor de esta manera y, por supuesto, no es fácil distinguir si se trata de un archivo de biblioteca o un archivo local .
 

El archivo anidado contiene

Si tal escenario ocurre:

comm.h y comm.c son módulos comunes.
test1.h y test1.c usan módulos comunes.
test2.h y test2.c usan módulos comunes.
test.h y test.c usan los módulos test1 y test2.

De esta forma, habrá dos copias de comm.h en el programa final. Esto da como resultado la duplicación del contenido del archivo.
¿Cómo resolver este problema?
Respuesta: compilación condicional .
Al comienzo de cada archivo de encabezado, escriba:

#ifndef __TEST_H__
#define __TEST_H__
// 头文件的内容

...

//结尾
#endif   //__TEST_H__

o:

//开头写
#pragma once

Esto evita la duplicación de archivos de encabezado.

Supongo que te gusta

Origin blog.csdn.net/qq_54880517/article/details/124380176
Recomendado
Clasificación