Notas de lectura de "C y punteros" (Capítulo 14 Preprocesador)

0 Introducción

La compilación de un programa en C implica muchos pasos, el primero de los cuales es la etapa de preprocesamiento ( preprocessing). El preprocesador de C realiza algunas operaciones textuales en el código fuente antes de compilarlo. Sus tareas principales incluyen eliminar comentarios, insertar #includingel contenido de archivos incluidos por directivas, definir y reemplazar símbolos definidos por directivas #define y determinar si partes del código deben compilarse de acuerdo con algunas directivas de compilación condicional.

Marco de contenidos de este número:
Insertar descripción de la imagen aquí

1 símbolos predefinidos

El preprocesador define algunos símbolos, lo que proporciona una gran comodidad para la depuración de nuestro programa y la generación de versiones. Por ejemplo:

#include <stdio.h>

int main()
{
    
    

	printf("This obj file name is:%s\n",__FILE__);
	printf("Current line is:%d\n", __LINE__);
	printf("-------------------------\n");
	printf("Today is:%s\n", __DATE__);
	printf("The time now is:%s\n", __TIME__);


	system("pause");
	return 0;
}

Salida de impresión:
Insertar descripción de la imagen aquí
puede ver que se imprimen el nombre del archivo que estamos ejecutando actualmente, el número de línea de la declaración de impresión y la fecha y hora de compilación.

Nota: La fecha y hora aquí son la fecha y hora en que se compiló el archivo, no la fecha y hora en que se ejecutó.

2 #definir

El Capítulo 2 es nuestra principal prioridad, que contiene el método, el significado y los aspectos a los que prestar atención al definir macros.

2.1 macros

El mecanismo #define incluye una disposición que permite sustituir parámetros en el texto. Esta implementación a menudo se denomina macro ( macro) o macro de definición ( defined macro).
Por ejemplo, usamos una macro para definir una operación cuadrada:

#include <stdio.h>
#define SQUARE(x) x*x
int main()
{
    
    
	int a = 5;
	printf("%d\n",SQUARE(a));

	system("pause");
	return 0;
}

Salida de impresión:
Insertar descripción de la imagen aquí
pero a veces habrá problemas con este método, por ejemplo:

#include <stdio.h>
#define SQUARE(x) x*x
int main()
{
    
    
	int a = 5;
	printf("%d\n",SQUARE(a + 1));

	system("pause");
	return 0;
}

Salida de impresión:
Insertar descripción de la imagen aquí
De acuerdo con nuestras expectativas, se debe generar el cuadrado de 6, que es 36. ¿A qué se debe esto?

A diferencia de la implementación de funciones, las macros solo realizan reemplazos simples, pero no realizan ningún trabajo como el paso de parámetros.
Puedes hacer una comparación:

#include <stdio.h>
#define SQUARE(x) x*x
int square(const int x)
{
    
    
	return x * x;
}
int main()
{
    
    
	int a = 5;
	printf("%d\n", SQUARE(a + 1));
	printf("%d\n", square(a + 1));

	system("pause");
	return 0;
}

Salida de impresión:
Insertar descripción de la imagen aquí
Se puede encontrar que si está en forma de función, el resultado de la ejecución es consistente con nuestra idea, esto se debe a que la macro solo realiza un trabajo de reemplazo simple, es decir, después de la ejecución, la ejecución natural el resultado 5+1*5+1es 11.

Pero también podemos usar definiciones de macros para lograr los mismos resultados esperados:

#include <stdio.h>
#define SQUARE(x) (x)*(x)
int square(const int x)
{
    
    
	return x * x;
}
int main()
{
    
    
	int a = 5;
	printf("%d\n", SQUARE(a + 1));
	printf("%d\n", square(a + 1));

	system("pause");
	return 0;
}

Impresión:
Insertar descripción de la imagen aquí
¡Problema resuelto!

2.2 #definir reemplazo

Esta parte habla principalmente sobre cómo insertar parámetros macro en constantes de cadena. Por ejemplo, necesitamos usar macros para redefinir una función de impresión:

#include <stdio.h>
#define PRINT(FORMAT, VALUE)  \
		printf("The value of "VALUE" is "FORMAT"\n",VALUE)
int main()
{
    
    
	int a = 5;
	PRINT("%d", a);
	PRINT("%d", a + 3);

	system("pause");
	return 0;
}

En este momento, el programa informará un error porque el valor no se puede pasar con precisión. En este momento, debemos realizar una conversión simple para convertir la expresión entrante en una cadena.

#include <stdio.h>
#define PRINT(FORMAT, VALUE)  \
		printf("The value of "#VALUE" is "FORMAT"\n",VALUE)
int main()
{
    
    
	int a = 5;
	PRINT("%d", a);
	PRINT("%d", a + 3);

	system("pause");
	return 0;
}

Salida de impresión
Insertar descripción de la imagen aquí
En este momento, se revelan las ventajas de la definición de macros y se descubre que para realizar dicha función, la encapsulación de la función es un poco débil.

2.3 Macros y funciones

De los ejemplos anteriores, podemos encontrar que en muchos casos, las macros y funciones tienen funciones similares y pueden reemplazarse entre sí. Pero hay diferencias entre los dos. Hay una tabla en el libro que parece muy clara:

Atributos #definir macro función
longitud del código Se insertará en el programa cada vez que se utilice, por lo que el contenido de la macro no debería ser demasiado. Cada llamada a función utiliza el mismo código, que es relativamente más amigable con la memoria.
Velocidad de ejecución más rápido Hay una sobrecarga adicional de llamadas/devoluciones de funciones.
precedencia del operador Los parámetros macro se evalúan en el contexto de todas las expresiones circundantes, por lo que se deben incluir paréntesis. El resultado de una expresión es más predecible.
Evaluación de parámetros Los parámetros se reevalúan cada vez que se utilizan en una definición de macro. Los parámetros con efectos secundarios pueden producir resultados impredecibles debido a múltiples evaluaciones. Los efectos secundarios de los parámetros no causan ningún problema especial.
Tipo de parámetro Las macros son independientes del tipo y pueden utilizar cualquier tipo de parámetro siempre que la operación sea legal. Los parámetros de una función están fuertemente relacionados con sus tipos. Si los tipos de parámetros son diferentes, es necesario definir diferentes funciones o convertir los parámetros reales.

Se puede ver que las macros y funciones tienen cada una sus propias ventajas y desventajas, por lo que elegir el método de implementación adecuado en la ocasión adecuada y adaptarlo es la mejor opción.

2.4 Parámetros macro con efectos secundarios

Cuando los parámetros tienen efectos secundarios, nuestras macros a menudo pueden producir resultados inesperados, lo que requiere especial atención en nuestro desarrollo real. Tomemos el siguiente ejemplo:

#include <stdio.h>
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main()
{
    
    
	int x = 2, y = 5, z = 0;
	z = MAX(x++, y++);
	printf("x = %d, y = %d, z = %d",x, y, z);
	system("pause");
	return 0;
}

¿Cuál debería ser el valor de z? Si esto maxse implementa usando una función, la respuesta definitivamente se puede obtener fácilmente, pero no parece tan simple en la definición de macro, porque la definición de macro requiere reemplazar la suma con dos expresiones muchas aveces b.

Pero después de pensarlo más, descubrí que no es difícil. x++Se ejecutó dos veces y y++tres veces, por lo que la respuesta es clara de un vistazo Impresión:
Insertar descripción de la imagen aquí
Entonces, ¿qué pasa si son ++x y ++y? Podemos explorarlo nosotros mismos. . .

2.5 Convención de nomenclatura

Muchas veces para un mejor desarrollo es necesario distinguir formalmente macros y funciones, una forma común es hacer el nombre de la macro en mayúsculas. Creo que mucha gente ha experimentado esto durante el desarrollo.

2.6 #undef

Esta directiva de preprocesamiento elimina una definición de macro.
Método de declaración:

#undef name

Si es necesario redefinir un nombre existente, primero se debe #undefeliminar su definición anterior.

2.7 Definición de línea de comando

Muchos compiladores de C brindan la capacidad de definir símbolos en la línea de comando que pueden usarse para iniciar el proceso de compilación.

En los compiladores comunes para Windows, el contenido relevante no se utiliza, por lo que se omite.

3 compilación condicional

3.1 Si está definido

3.2 Instrucciones anidadas

Incluso en el desarrollo de proyectos a gran escala, rara vez se utilizan instrucciones anidadas. Y no es difícil de entender incluso si lo encuentras. La forma básica se proporciona en el libro, por lo que no entraré en detalles en este artículo.

#ifdef OS_UNIX
	#ifdef OPTION1
		unix_version_of_option1();
	#endif
	#ifdef OPTION2
		unix_version_of_option2();
	#endif
#elif defined OS_MSDOS
	#ifdef OPTION2
		msdos_version_of_option2();
	#endif	
#endif

Es similar al anidamiento de declaraciones condicionales, por lo que es relativamente fácil de leer.

4 archivos contienen

En el desarrollo real, los proyectos más grandes adoptarán el desarrollo modular, lo que puede evitar muchos problemas. Hace que el desarrollo, la iteración y la lectura del código sean mucho más convenientes.

yEl archivo contieneEsta idea está bien explicada.

4.1 Los archivos de la biblioteca de funciones incluyen

Los llamados archivos de biblioteca de funciones son algunos programas (generalmente funciones) que se han escrito para nosotros antes de programar. A veces lo llamamos interfaz. Generalmente utilizamos corchetes angulares para la inclusión. Como en nuestro procedimiento anterior:

#include <stdio.h>

De esta manera, la computadora puede reconocer printfdichas declaraciones cuando el programa se compila y ejecuta.

4.2 Inclusión de archivos locales

La llamada inclusión de archivos locales generalmente se refiere a archivos locales escritos por nosotros mismos. Por ejemplo:
creamos un print.carchivo y escribimos la siguiente función:

#include <stdio.h>
#include "print.h"
void fun_print(char *x)
{
    
    
	if (x != NULL)
	{
    
    
		printf("%s",x);
	}
}

Luego crea print.hel archivo y declaralo:

#pragma once
void fun_print(char *x);

Finalmente, main.chaz la llamada en el archivo:

#include <stdio.h>
#include "print.h"
int main()
{
    
    
	fun_print("Hello world!");
	system("pause");
	return 0;
}

Esta es la inclusión de archivos locales. Para distinguirla de la inclusión de archivos del sistema, generalmente se utilizan comillas dobles .

4.3 Inclusión de archivos anidados

En el desarrollo de proyectos a gran escala, hay una gran cantidad de archivos fuente y archivos de encabezado, y obviamente hay relaciones de inclusión intrincadas. Las relaciones de inclusión anidadas se han convertido en un fenómeno común. Esto no es nada en sí mismo, pero si hay múltiples inclusiones, el fenómeno , el compilador informará un error durante la compilación y la compilación no podrá realizarse.

En otras palabras, múltiples inclusiones afectarán directamente la eficiencia de ejecución o el tamaño de la memoria del programa. Para contenido específico, consulte la información relevante. Entonces, ¿cómo evitar este problema?
Generalmente, para evitar múltiples inclusiones de archivos de encabezado y múltiples definiciones durante la compilación, podemos adoptar el siguiente método:

#ifndef __HEADERNAME__H
#define __HEADERNAME__H 1

o

#pragma once

#pragma once es una directiva de preprocesamiento utilizada para la protección de archivos de encabezado. Su función es garantizar que el mismo archivo no se incluya varias veces para evitar provocar errores de redefinición durante el proceso de compilación. Se puede decir que su efecto es similar a la combinación de #ifndef y #define. En comparación con el método #ifndef, #pragma once es más conciso y eficiente y puede proteger todo el archivo.

Sin embargo, cabe señalar que #pragma once es un método no estándar y, aunque la mayoría de los compiladores lo admiten ampliamente, también tiene problemas de compatibilidad. Es posible que algunos compiladores más antiguos no admitan #pragma una vez, por lo que debe considerar la compatibilidad del proyecto al elegir usarlo.

En resumen, la elección de qué método utilizar depende de la situación específica y puede acordarse de acuerdo con las especificaciones de desarrollo del equipo. Ambos enfoques son aceptables siempre que las desventajas puedan evitarse razonablemente.

5 otras instrucciones

#errorEs una de las instrucciones de preprocesamiento del lenguaje C. Comprueba si hay errores en el código fuente durante la fase de compilación y genera información de error durante la compilación. Cuando el compilador encuentra la directiva #error, detendrá la compilación y mostrará el mensaje de error después de #error.

#include <stdio.h>
#include "print.h"	
int main()
{
    
    
#ifdef PRINT
	fun_print("Hello world!");
#else
	#error Print is not defined!
#endif
	system("pause");
	return 0;
}

Haga clic en Ejecutar y descubra que no se puede realizar la compilación y que el contenido del error se genera directamente:
Insertar descripción de la imagen aquí
por supuesto, diferentes IDE pueden informar errores en diferentes formas.

#progmaLas directivas son otro mecanismo para admitir características específicas del compilador. Por ejemplo, lo que mencionamos anteriormente

#pragma once

Esto evita que los archivos de encabezado se vuelvan a incluir.

Así llamadoSoporte para características específicas del compiladorEs decir, la misma #pragmainstrucción puede producir efectos diferentes en diferentes compiladores, dependiendo de la situación específica.

6 Resumen

El primer paso para compilar un programa en C es preprocesarlo. El preprocesador admite un total de 5 símbolos.

La directiva #define se puede utilizar para "reescribir" el lenguaje C para que parezca otro lenguaje.

La compilación condicional puede ejecutar diferentes segmentos de código en diferentes condiciones, lo que es mucho más conveniente que los programas de enmascaramiento de área grande y se usa ampliamente en el desarrollo de grandes proyectos.

En el desarrollo real, intente evitar la inclusión múltiple de archivos de encabezado, aunque a veces el entorno de desarrollo no informa directamente errores o advertencias.

Supongo que te gusta

Origin blog.csdn.net/weixin_43719763/article/details/132798855
Recomendado
Clasificación