Compartir tecnología

[Lenguaje C] - Explicación detallada del preprocesamiento (Parte 1)

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

1. Símbolos predefinidos

Hay algunas configuraciones en lenguaje C.Símbolos predefinidos, se puede utilizar directamente, los símbolos predefinidos son在预处理期间处理的

__FILE__    //进行编译的源文件
__LINE__	//文件当前的行号
__DATE__	//文件被编译日期
__TIME__	//文件被编译那一瞬的时间
__STDC__	//如果编译器遵循ANSI C(标准C),其值为1,否则未定义(报错)
  • 1
  • 2
  • 3
  • 4
  • 5

  
Ejemplo:

#include<stdio.h>

int main()
{
	printf("%sn", __FILE__);
	printf("%dn", __LINE__);
	printf("%sn", __DATE__);
	printf("%sn", __TIME__);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

  
resultado de la operación:

Insertar descripción de la imagen aquí

Me gustaría mencionar aquí que VS no es totalmente compatible con ANSI C (estándar C); CCG CCGgramoC.C. Soporta ANSI C (Estándar C)
  

dos,# definir definirdmiFenmi Definir constantes (símbolos)

Sintaxis básica:

# define name stuff
  • 1

Por ejemplo:

#define MAX 100
#define reg register  // 为 register 这个关键字创建一个简短的名字
#define do_forever for(;;)  //用更形象的符号来替换一种实现
#define CASE break;case		//在写 case 语句的时候自动吧 break 写上

//如果定义的 stuff 过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)
#define DEBUG_PRINT printf("file:%stline:%dt 
							date:%sttime:%sn" ,
							__FILE__,__LINE__ , 
							__DATE__,__TIME__ )
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • La segunda frase es pereza, ¿por qué?
  • la tercera frase paraFo Circular初始化、判断、调整都可以省略, pero si se omite el juicio, significa que la condición del juicio siempre es verdadera, es decirBucle infinito
  • Es mejor no hacer esto en el cuarto juego, ya que puede causar problemas fácilmente.
  • La quinta frasecarácter de continuación de líneaEs para prevenir problemas después de la ramificación. Su esencia es.转义Atrás回车符 , de modo que el retorno de carro ya no sea un retorno de carro.No puede haber nada después del carácter de continuación de línea, presione“” Simplemente presione Enter; de lo contrario, la continuación no será la siguiente línea de código.

  
Ahora viene el problema: usa # definir definirdmiFenmi Al definir un identificador, ¿debería agregarlo al final?

Por ejemplo:

#define MAX 1000
#define MAX 1000;

int main()
{
	int n = MAX;
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Insertar descripción de la imagen aquí

El código anterior más, parece un poco redundante, pero no tiene ningún impacto en la ejecución del programa.
parece agregar O no agregar ¿Todo saldrá bien?
¿Es realmente así?
  
Veamos el siguiente ejemplo:

//例一
int main()
{
	printf("%dn", MAX);
	return 0;
}

//例二
int mian()
{
	int max = 0;
	if (1)
		max = MAX;
	else
		max = 1;
	return 0
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

Después del reemplazo:

printf("%dn", 1000;);
  • 1

Imprimir1000;¿Que significa?

if (1)
	max = 1000;
	;
else
	max = 1;
  • 1
  • 2
  • 3
  • 4
  • 5

otra cosa otra cosamiyo ¿A quién emparejar?
  
Verás, hay algo mal en esto, así queusar # definir definirdmiFenmi Al definir un indicador, no lo agregue después

  

tres,# definir definirdmiFenmi Definir macro

  # definir definirdmiFenmi El mecanismo incluye una disposición,允许把参数替换到文本中, esta implementación a menudo se llamaMacro (marco) (marco)mamámámárcoo Definir macro (definirmacro) (definir macro)dmiFenmimamámámácro
La diferencia entre macros y los identificadores de definición de macro anteriores es:La macro tiene parámetros.
  
Así es como se declara la macro:

#define name(parament - list) stuff
  • 1

uno de ellos paramento paramentopagagaasoyminorteortea - lista listayoisa(lista de argumentos) es una lista de símbolos separados por comas que pueden aparecer en Cosas, cosassaadj medio
Nota: paramento paramentopagagaasoyminorteortea - lista listayoisa(lista de parámetros)paréntesis izquierdodebe ser con nombre nombrenombremi junto a, si existe algún espacio en blanco entre ellos, la lista de argumentos se interpreta como Cosas, cosassaadj una parte de.
  

Ejemplo:

//实现一个宏,计算一个数的平方
#define SQUARE(x) x*x

int main()
{
	int a = 5;
	int ret = SQUARE(a);
	printf("%dn", ret);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

  
resultado de la operación:

Insertar descripción de la imagen aquí

Como puedes ver, el cuadrado de 5 está calculado correctamente.

Pero, de hecho, hay un problema con el código anterior. Consulte el siguiente fragmento de código:

int a = 5;
printf("%dn", SQUARE(a + 1));
  • 1
  • 2

  
resultado de la operación:

Insertar descripción de la imagen aquí

¿Por qué esto es tan? El resultado de 5+1 es 36 y 6 ∗ * 6 debería ser 36. ¿Cómo obtuviste 11?

El problema son las macros, lo sabemos.宏是直接替换, entonces el resultado de reemplazar directamente el código anterior es:

printf("%dn", a+1*a+1);
  • 1

5 + 6 + 1, el resultado es naturalmente 11

Podemos resolver fácilmente este problema agregando paréntesis a ambos lados de la definición de macro.

#define SQUARE(x) (x)*(x)
  • 1

Entonces, ¿no hay ningún problema con esta definición?Echemos un vistazo a la siguiente definición de macro.

#define DOUBLE(X) (X)+(X)
  • 1

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

int a = 5;
printf("%dn", 10 * DOUBLE(a));
  • 1
  • 2

  
resultado de la operación:

Insertar descripción de la imagen aquí

El resultado de salida no es 100 sino 55. El motivo es similar al anterior. Pregunta prioritaria
  
Solución:

#define DOUBLE(X) ((X)+(X))
  • 1

En resumen, al utilizar macrosNunca escatimes en paréntesispara evitar interacciones inesperadas entre operadores en parámetros u operadores adyacentes cuando se utilizan macros.

  

4. Parámetros macro con efectos secundarios

Cuando un parámetro de macro aparece más de una vez en la definición de una macro, si el parámetro tieneefecto secundario, entonces puede estar en peligro al utilizar esta macro, lo que resultará en不可预测 el resultado de. Los efectos secundarios son efectos permanentes cuando se evalúa la expresión.
  
Por ejemplo:

x + 1; //不带副作用
x++//带副作用
  • 1
  • 2

La siguiente macro MAX demuestra los problemas causados ​​por parámetros con efectos secundarios.

#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=%dn", x, y, z);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

  
resultado de la operación:

Insertar descripción de la imagen aquí

¿Por qué esto es tan?Analicémoslo juntos

z = ((X++) > (y++) ? (x++) : (y++))
  • 1
  • Primero haga un juicio: xxX++ con yyy++ juicio, porque es postfijo ++, al juzgar xxX es 5, yyy es 8, 8 &gt; 5
  • despues de juzgar xxX es 6, yyy por 9
  • Luego ejecuta yyy ++, debido a que tiene posfijo ++, el resultado es 9
  • Entonces continúa yyy Realizar incremento automático, yyy El resultado final es 10.

Lo haremos xxX y yyy Cuando se pasa a la macro, los resultados han cambiado, especialmente yyy, después de dos cambios, dijiste que no es terrible.
Cuando un parámetro con efectos secundarios se pasa a una macro y el parámetro aparece más de una vez en la macro, el efecto secundario del parámetro también aparece más de una vez.
  

5. Reglas para el reemplazo de macros

Ampliar en programa # definir definirdmiFenmi Hay varios pasos a seguir al definir símbolos y macros.

  • Al llamar a una macro, primero参数进行检查, mira si contiene # definir definirdmiFenmi Definido标识符 .Si es así, se reemplazan primero.
  • El texto de reemplazo es el siguiente被插入a la ubicación original en el programa, con los nombres de los parámetros macro reemplazados por sus valores
#define MAX(a,b) ((a) > (b) ? (a):(b))
#define M 10

int main()
{
	int x = 5;
	int z = MAX(x, M);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • MÁXIMO ( x , M ) MÁXIMO (x, M)METROETROETROETROETROETROAX(X,METROETROETROETROETROETRO) medio M.M.METROETROETROETROETROETRO Primero se reemplaza con 10 y 10 se inserta en el original. M.M.METROETROETROETROETROETRO ubicación
  • Finalmente, los resultados se escanean nuevamente para ver si contienen algo causado por # definir definirdmiFenmi símbolo definido, si es así, repita el proceso anterior

MAX en el código anterior también está representado por # definir definirdmiFenmi DefinidoMacro .En la última inspección, su parámetro M ha sido reemplazado. Esta vez es hora de reemplazarlo.
   MÁXIMO ( x , 10 ) MÁXIMO (x, 10)METROETROETROETROETROETROAX(X,10) es reemplazado por ( ( x ) &gt; ( 10 ) ((x) &gt; (10)((X)>(10) ? ? ? ( x ) : ( 10 ) ) (x):(10))(X):(10))
  
ciertamente,宏里面嵌套宏también está bien

MAX(x, MAX(2, 3))
  • 1

En este momento, primero reemplace la macro en el parámetro y luego reemplace la macro completa
Pero cabe señalar que esto不是递归, que es solo una macro que sirve como argumento para otra macro.递归是宏内部又调用了宏本身
Además, cuando el preprocesador busca # definir definirdmiFenmi Al definir un símbolo,No se busca el contenido de las constantes de cadena.
¿Qué significa eso?Sólo da una castaña y lo entenderás.

#define M 10

int main()
{
	int x = 5;
	int z = MAX(x, MAX(2, 3));
	printf("M = %d", x);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

En el código anteriorprintf("M = %d", x);medio M.M.METROETROETROETROETROETRO no será reemplazado por 10

  

6. Comparación entre macros y funciones

La macro anterior se utiliza para encontrar el valor mayor de dos números. Podemos escribirla completamente como una función.

int Max(int x, int y)
{
	return x > y ? x : y;
}
  • 1
  • 2
  • 3
  • 4

Descubrimos que todos cumplen la misma función.Pero para la función de "encontrar el mayor de dos números", escribir la macro更有优势Alguno
  
Hay dos razones:

  • usado para调用函数y函数返回decódigopuede llevar más tiempo que realizar este pequeño cálculo (al llamar a la funciónCrear marco de pila ).Por lo tanto, la función de macro ratio está en el programa.规模y速度Mejor que nunca
  • Más importante函数的参数必须声明类型 , lo que hace que la función se use solo en expresiones del tipo apropiado.Por el contrario, esta macro se puede aplicar a muchos tipos: se pueden utilizar enteros, enteros largos, coma flotante, etc.
    > Comparar.Los parámetros de la macro sonTipo independientede

¿Usaremos siempre macros en el futuro?De hecho, las macros sólo se utilizan paraCálculo sencillo, no adecuado para operaciones y funciones complejas y grandes en comparación con las macros Desventajas

  1. Cada vez que se utiliza una macro, se inserta una copia del código de definición de la macro en el programa.A menos que la macro sea relativamente corta, puede大幅度增加程序的长度
  2. macro es没法调试de
  3. Las macros son independientes del tipo, es decir,不够严谨
  4. Las macros pueden causar problemas de prioridad de operación, haciendo que el programa容易出错

Pero a veces las macros pueden hacer cosas que las funciones no pueden hacer.
  
Por ejemplo:

int* p = (int*)malloc(10 * sizeof * (int));
  • 1

No nos gusta escribirlo así. malloc mallocmamámámátodosjefe La función es demasiado engorrosa, quiero大小y类型Pásalo para abrir espacio.

Malloc(10, int);
  • 1

¿Pueden las funciones hacer esto?No porqueLas funciones no pueden pasar tipos.
Y las macros pueden hacerlo, porque las macros no lo hacen.no comprobartus parámetrostipode

#define Malloc(n, type) (type*)malloc(n * sizeof(type))

int main()
{
	int* p = Malloc(10, int);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

  
Una comparación de macros y funciones:

Atributos# definir definirdmiFenmiDefinir macrofunción
longitud del códigoCada vez que se utiliza, se insertará código de macro en el programa. Excepto en el caso de macros muy pequeñas, la duración del programa aumentará significativamente.El valor del código de función aparece en un lugar y cada vez que se usa la función, se llama al mismo código en ese lugar.
Velocidad de ejecuciónmás rápidoExiste la sobrecarga adicional de llamadas y retornos de funciones (abriendo marcos de pila), así que comencemos con las más lentas.
precedencia del operadorLos parámetros de macro se evalúan en el contexto de todas las expresiones circundantes, a menos que se incluyan paréntesis, la precedencia de operadores adyacentes puede tener consecuencias impredecibles. Por lo tanto, se recomienda escribir las macros con más paréntesis.Los parámetros de la función solo se evalúan una vez cuando se llama a la función y su valor resultante se pasa a la función, lo que hace que el resultado de la evaluación de la expresión sea más predecible.
Parámetros con efectos secundarios.Los parámetros se pueden sustituir en varias posiciones de la macro. Si los parámetros de la macro se evalúan varias veces, la evaluación de parámetros con efectos secundarios puede producir resultados impredecibles.Los parámetros de la función solo se llaman una vez al pasar parámetros y los resultados son más fáciles de predecir.
Tipo de parámetroLos parámetros de la macro no tienen nada que ver con el tipo. Siempre que la operación sobre los parámetros sea legal, se puede utilizar para cualquier tipo de parámetro.Los parámetros de una función están relacionados con el tipo. Si los parámetros son de diferentes tipos, se requieren diferentes funciones, incluso si realizan la misma tarea.
depurarLas macros son incómodas de depurarLas funciones se pueden depurar paso a paso.
recursividadLas macros no pueden ser recursivasLas funciones pueden ser recursivas.

¿Hay alguna forma de combinar sus ventajas?
Introducido en C++función en línea en línea en líneaenyoenmi ——Tiene las ventajas tanto de macros como de funciones.
Se ejecuta tan rápido como una macro pero tiene el mismo efecto que una función.