[C Avanzado] Preprocesamiento

contenido

1. Preprocesamiento

    (1) Símbolos predefinidos

    (2) definir

             2.1, #define definición identificador

             2.2, #define macro de definición

             2.3, #definir reglas de reemplazo

             2.4, # y ##

             2.5 Parámetros macro con efectos secundarios

             2.6, comparación de macros y funciones

             2.7 Convenciones de nomenclatura

    (3) indefinido

    (4) Definición de línea de comando

    (5) Compilación condicional

    (6) El expediente contiene

              6.1, la forma en que se incluye el archivo de encabezado

              6.2, el archivo anidado contiene

2. Otras instrucciones de preprocesamiento


1. Preprocesamiento

(1) Símbolos predefinidos

Definición: este símbolo ha sido procesado y definido en la etapa de preprocesamiento, y estos símbolos se pueden usar directamente

  • P.ej:
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
__FUNCTION__ //函数所在的函数名
  • Utilizar de la siguiente manera:
#include<stdio.h>
int main()
{
	printf("%s\n", __FILE__);
	printf("%d\n", __LINE__);
	printf("%s\n", __DATE__);
	printf("%s\n", __TIME__);
	printf("%s\n", __FUNCTION__);
	//printf("%d\n", __STDC__); err VS编译器不遵循ANSI C,所以未定义,但是linux是支持的
	return 0;
}

  • efecto:

Para evitar que el proyecto sea demasiado grande al escribir código en el futuro y sea imposible comenzar la depuración, es necesario registrar cierta información de registro cuando el código se está ejecutando y usar esta información para analizar los problemas del código.

(2) definir

2.1, #define definición identificador

  • gramática:
#define name stuff
  • Ejemplo:
#define MAX 1000
#define reg register //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;) //用更形象的符号来替换一种实现
#define CASE break;case //在写case语句的时候自动把 break写上。

Pregunta: Al definir un identificador, ¿desea agregar ; al final?

  • por ejemplo:
#include<stdio.h>
#define M 1000;
int main()
{
	int m = M;
	//替换后上述代码就变成如下所示
	int m = 1000;
	; //空语句
	return 0;
}

Después de agregar un punto y coma, la gramática también puede pasar, pero no se recomienda, después de todo, es fácil cometer errores, de la siguiente manera:

#include<stdio>
#define M 1000;
int main()
{
	int a = 0;
	int b = 0;
	if (a > 10)
		b = M;
	else //报错
		b = -M;
	return 0;
}

En este momento, el compilador reporta un error, porque el if va seguido de dos declaraciones, que no se pueden conectar con else, de la siguiente manera:

	if (a > 10)
		b = 1000;
	    ;
	else //报错

Se recomienda no agregar ; , que fácilmente puede generar problemas.

2.2, #define macro de definición

El mecanismo #define incluye una estipulació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

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

  • Aviso:
  1. El paréntesis de apertura de la lista de parámetros debe estar inmediatamente junto al nombre.
  2. Si hay algún espacio en blanco en el medio, la lista de argumentos se interpreta como parte de cosas.
  • P.ej:
#define SQUARE(X) X*X

Esta macro toma un parámetro x, si después de la declaración anterior, pones

SQUARE(3);

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

printf("%d\n", 3 * 3);//9

Sin embargo: si se pasan tales parámetros:

SQUARE(3 + 1);

A primera vista, el resultado puede ser 16, pero no lo es, la respuesta es 7, ¿por qué?

Al reemplazar texto, el parámetro x se reemplaza por 3 + 1, por lo que esta declaración en realidad se convierte en:

printf("%d\n", 3 + 1 * 3 + 1); //7

Entonces: los parámetros de la macro se reemplazan directa y completamente, no se calculan y luego se reemplazan

  • Solución:
     
#define SQUARE(x) (x) * (x)

Este preprocesamiento produce el efecto esperado:

printf ("%d\n",(3 + 1) * (3 + 1) ); //16
  • 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?

Parece que el resultado es 100, pero no lo es, de hecho imprime 55.

Encontramos que después de la sustitución: la operación de multiplicación precede a la suma definida por la macro, por lo que hay 55

printf ("%d\n",10 * (5) + (5)); //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 ) )

En este punto está el resultado de la captación previa:

printf("%d\n", 10 * ((5) + (5))); //100
  • insinuación:

Por lo tanto, 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.

2.3, #definir reglas de reemplazo

Primero mira un fragmento de código:

#include<stdio.h>
#define M 100
#define MAX(X,Y) ((X)>(Y)?(X):(Y))
int main()
{
	int max = MAX(101, M);
    //等价于int max = MAX(101, 100); //M是由#define定义的符号,首先被替换
	return 0;
}

  • Hay varios pasos involucrados al expandir #define para definir símbolos y macros en un programa.
  1. Al llamar a la macro, primero se comprueban los argumentos 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, el archivo resultante se escanea nuevamente para ver si contiene algún símbolo definido por #define. Si es así, repita el proceso anterior.
  • Aviso:
  1. Otros símbolos definidos por #define pueden aparecer en parámetros de 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.
  • Tal como:
printf("M = %d\n", M);
//替换后即:
printf("M = %d\n", 100); //字符串常量的内容不会被搜索

2.4, # y ##

  • # (convertir un parámetro de macro en una cadena correspondiente)

¿Cómo insertar parámetros en cadenas?

Primero veamos este código:

#include<stdio.h>
int main()
{
	printf("hello world\n"); //hello world
	printf("hello " "world\n"); //hello world
	return 0;
}

Encontramos que las cadenas tienen las características de la concatenación automática. Veamos este fragmento de código:

Ahora supongamos que se definen tres variables a, b y c, y ahora quiero mostrar estas tres oraciones en la pantalla:

#include<stdio.h>
int main()
{
	int a = 10;
	//the value of a is 10
	int b = 20;
	//the value of b is 20
	int c = 30;
	//the value of c is 30
	return 0;
}

Puede usar printf para generar resultados directamente, pero de otra manera: (#)

#define PRINT(X) printf("the value of "#X" is %d\n", X);

Ahora echemos un vistazo al código total:

#include<stdio.h>
#define PRINT(X) printf("the value of "#X" is %d\n", X);
int main()
{
	int a = 10;
	PRINT(a);
	int b = 20;
	PRINT(b);
	int c = 30;
	PRINT(c);
	return 0;
}

 #X es la cadena correspondiente al parámetro X , por lo que es muy inteligente implementar estas tres oraciones. Después del reemplazo, es:

printf("the value of ""a"" is %d\n", a);

Los tres pares de " " representan cada uno una cadena de cadenas. Por supuesto, se puede optimizar de nuevo para que pueda imprimir números de coma flotante u otros tipos, y solo se necesita pasar un parámetro más, de la siguiente manera:

#include<stdio.h>
#define PRINT(X, FORMAT) printf("the value of "#X" is "FORMAT"\n", X);
int main()
{
	int c = 30;
	PRINT(c, "%d");//the value of c is 30
	float f = 5.5f;
	PRINT(f, "%f");//the value of f is 5.500000
	return 0;
}

Después del reemplazo anterior es:

printf("the value of ""f"" is ""%f""\n", f); //5.500000
  • ##

## puede combinar los símbolos de ambos lados en un solo símbolo. Permite que las definiciones de macro creen identificadores a partir de fragmentos de texto independientes.

P.ej:

#include<stdio.h>
#define CAT(X,Y) X##Y
int main()
{
	int class101 = 100;
	printf("%d\n", CAT(class, 101)); //100
	return 0;
}

¿Por qué sale 100? Después de reemplazar el código anterior, en realidad se puede transformar en:

class##101

Y ## es combinar dos símbolos, por lo que en realidad es:

printf("%d\n", class101);//100

2.5 Parámetros 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:
int a = 1;
int b = a + 1; //b=2 , a=1
//int b = ++a; //b=2 , a=2

Escribir ++a aquí tiene efectos secundarios. Quiero cambiar b por 2, pero nunca quiero cambiar a por 2.

  • Los parámetros de las macros también tienen efectos secundarios:
#include<stdio.h>
#define MAX(X,Y) ((X)>(Y)?(X):(Y))
int main()
{
	int a = 5;
	int b = 8;
	int m = MAX(a++, b++);
	printf("m=%d\n", m);//9
	return 0;
}

¿Cómo se calcula aquí el valor de m?

  • Analizar gramaticalmente:
int m = ((a++) > (b++) ? (a++) : (b++));
后置++,先使用,后++。此时a自增变成6,b自增变成9
5 > 8 ? (a++) : (b++)
5<8,执行后面的b++,因为先使用所以m直接是9,而b自增变成10
综上:a = 6
      b = 10
      m = 9

2.6, comparación de macros y funciones

Las macros se utilizan normalmente para realizar operaciones sencillas. Por ejemplo, encuentra el mayor de dos números.

//宏
#define MAX(a, b) ((a)>(b)?(a):(b))
//函数
int Add(int x, int y)
{
	return x > y ? x : y;
}

Entonces, ¿por qué no usar funciones para realizar esta tarea?

  • Ventajas de las macros:
  1. El código para llamar y regresar de la función puede tomar más tiempo que hacer este pequeño trabajo computacional. Por lo tanto, las macros superan a las funciones en términos de tamaño y velocidad del programa.
  2. Más importante aún, los parámetros de la función deben declararse como tipos específicos. Por lo tanto, las funciones solo se pueden usar en expresiones del tipo apropiado. Por el contrario, ¿cómo se puede aplicar esta macro a enteros, enteros largos, tipos de coma flotante, etc. que se pueden usar para comparar tipos? Las macros son independientes del tipo.
  • Desventajas de las macros: Por supuesto, las macros también tienen desventajas en comparación con las funciones:
  1. Cada vez que se usa 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 aumentar significativamente la duración del programa.
  2. Las macros no se pueden depurar.
  3. Las macros no son lo suficientemente rigurosas porque son independientes del tipo.
  4. Las macros pueden causar problemas con la precedencia de los operadores, lo que hace que los procedimientos sean propensos a errores.

Las macros a veces pueden hacer cosas que las funciones no pueden. Por ejemplo: los parámetros de macro pueden tener tipos, pero las funciones no.

#define MALLOC(num, type) (type *)malloc(num * sizeof(type))
...
//使用
MALLOC(10, int);//类型作为参数
//预处理器替换之后:
(int*)malloc(10 * sizeof(int));
  • Una 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 parámetros 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 de resultado se pasa a la función. Los resultados de la evaluación de expresiones son más predecibles.
Parámetros con efectos secundarios Los parámetros se pueden sustituir en varios lugares del cuerpo de la macro, por lo que la evaluación de parámetros con efectos secundarios puede producir resultados impredecibles. Los parámetros de función solo se evalúan una vez cuando se pasan los parámetros 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 la función están relacionados con el tipo, si los tipos de los parámetros son diferentes, se necesitan 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.

En resumen: si la lógica de funcionamiento es relativamente sencilla, utilice macros, en caso contrario, utilice funciones.

2.7 Convenciones de nomenclatura

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 nuestro hábito habitual es:

  1. poner en mayúscula los nombres de las macros
  2. No use todos los nombres de funciones en mayúsculas

(3) indefinido

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

  • P.ej:
#include<stdio.h>
#define M 100
int main()
{
#undef M
	printf("%d\n", M); //编译器报错,未声明的标识符
	return 0;
}

(4) Definición de línea de comando

Muchos compiladores de C brindan la capacidad de definir símbolos en la línea de comandos. Se utiliza para iniciar el proceso de compilación.

Por ejemplo, esta característica es útil cuando queremos compilar diferentes versiones de un programa basado en el mismo archivo fuente. (Supongamos que un programa declara una matriz de cierta longitud. Si la memoria de la máquina es limitada, necesitamos una matriz pequeña, pero otra memoria de máquina está en mayúsculas y necesitamos una matriz que pueda estar en mayúsculas).

  • Instrucciones de compilación:
//linux 环境演示
gcc -D M=10 programe.c
#include<stdio.h>
int main()
{
	int arr[M] = { 0 };
	int i = 0;
	for (i = 0; i < M; i++)
	{
		arr[i] = i;
	}
	for (i = 0; i < M; i++)
	{
		printf("%d ", i); //0 1 2 3 4 5 6 7 8 9 
	}
	return 0;
}

En este punto, el programa genera 0-9 como se esperaba

(5) 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.

Directivas comunes de compilación condicional:

  • 1. Compilación condicional de rama única
#if 常量表达式
   //...
#endif
#include<stdio.h>
int main()
{
#if 1
	printf("hehe\n"); //hehe
#endif
	return 0;
}

Si la expresión constante después de #if es verdadera, participará en la compilación; de lo contrario, no participará en la compilación. 

  • 2. Compilación condicional de múltiples ramas
#if 1==1
	printf("hehe\n"); //hehe
#elif 1==2
	printf("haha\n");
#else
	printf("heihei\n");
#endif
  • 3. Determinar si está definido
#include<stdio.h>
#define TEST 0
int main()
{
	//如果TEST定义了,下面参与编译
#ifdef TEST
	printf("test1\n"); //test1
#endif
	//等价于
#if defined(TEST)
	printf("test2\n"); //test2
#endif

	//如果HEHE不定义,下面参与编译
#ifndef HEHE
	printf("hehe1\n"); //hehe1
#endif
	//等价于
#if !defined(HEHE)
	printf("hehe2\n"); //hehe2
#endif
	return 0;
}
  • 4. Instrucciones 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

(6) El expediente 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.

6.1, la forma en que se incluye el archivo de encabezado

//库文件包含,C语言库中提供的函数的头文件使用<>
#include<stdio.h>
//本地文件包含,自定义的函数的头文件使用""
#include"add.h"

La diferencia esencial entre <> y " "incluye archivos de encabezado: la diferencia en la estrategia de búsqueda

  • " " Encontrar estrategia:

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.

  • <> Encontrar estrategia:

Busque el archivo de encabezado y vaya 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í, sí.

Sin embargo, la eficiencia de la 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.

6.2, el archivo anidado contiene

Si tal escenario ocurre:

  1. comm.h y comm.c son módulos comunes.
  2. test1.h y test1.c usan módulos comunes.
  3. test2.h y test2.c usan módulos comunes.
  4. test.h y test.c usan el módulo test1 y el módulo 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.

2. Otras instrucciones de preprocesamiento

Anteriormente hemos aprendido las siguientes directivas de preprocesamiento:

#include 、 #define 、 #if 、 #ifdef 、 #endif 、 #ifndef 、 #else 、 #elif 、 #undef

Y #error , #pragma , #linea…

Supongo que te gusta

Origin blog.csdn.net/bit_zyx/article/details/122933174
Recomendado
Clasificación