Explicación detallada de #define y preprocesamiento en lenguaje C

Tabla de contenido

#definir

Cómo usar #define 

 #define reglas de sustitución

 El papel de # y ##

Argumentos macro con efectos secundarios

Macros frente a funciones 

#undef

el archivo contiene 

 La forma en que se incluyen los archivos de encabezado:

El archivo anidado contiene


En lenguaje C, se realizan cuatro pasos cuando se ejecuta un programa: precompilación, compilación, ensamblaje y enlace. Cuando el programa está precompilado, el programa será preprocesado y algunas instrucciones de preprocesamiento se ejecutarán y reemplazarán.

Símbolos predefinidos:

__FILE__ //archivo fuente para la compilación

__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__ //Si el compilador sigue ANSI C, su valor es 1, de lo contrario, no está definido

Estos símbolos predefinidos están integrados en el lenguaje.

Estos se pueden generar en el programa para comprender la diversa información del archivo:

#include<stdio.h>

int main(void)
{
	printf("file:%s line:%d\n", __FILE__, __LINE__);
}

¡Pero en lo que nos vamos a centrar hoy es en la directiva de preprocesamiento #define! !

#definir

#define identificador de definición : Sintaxis: #define nombre(nombre de macro) cosas(valor de macro)

Toma una castaña:

#definir MAX 1000

#define registro de registro //Crea un nombre corto para el registro de palabras clave

#define do_forever for(;;) //Reemplazar una implementación con un símbolo más vívido

#define CASE break;case //Escribe break automáticamente al escribir una declaración de caso. 

#define DEBUG_PRINT printf("archivo:%s\tline:%d\t \

fecha:%s\thora:%s\n" ,\

__ARCHIVO__,__LINEA__ , \

__FECHA Y HORA__ )   

 // Si el material definido es demasiado largo, se puede escribir en varias líneas, excepto la última línea, cada línea va seguida de una barra invertida (carácter de continuación de línea).

Aquí quiero enfatizar un punto, no agregue después del logo definido ;

 si:

#define MAX 1000;
if(condition)
 max = MAX;
else
 max = 0;
//在预处理时MAX会被替换
//就会变成下面代码:
if(condition)
 max = 1000;;
else
 max = 0;

Habrá un error de sintaxis aquí. 

Cómo usar #define 

 La primera sintaxis es definir constantes simbólicas:

#define N 100
#define MAX 10000

Cuando usamos N o MAX, todos los nombres de macro se reemplazarán con valores de macro. Si se encuentra #undef N (mencionado más adelante) o #undef MAX más tarde, el valor definido por esta definición se eliminará.

La segunda sintaxis se utiliza para definir funciones simbólicas (definir macros)

El mecanismo #define incluye una disposición que permite que los parámetros se sustituyan en el texto, y esta implementación a menudo se denomina macro o definir macro.

Así es como se declara la macro:

#define nombre (parament-list) cosas

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

Aviso:

El paréntesis de apertura de la lista de parámetros debe estar inmediatamente junto al nombre. Si existe algún espacio en blanco entre los dos, la lista de argumentos se interpreta como parte del material.

#define SQUARE( x ) x * x
//这个宏接收一个参数 x

int main()
{
    SQUARE( 5 );

    return 0;
}
//置于程序中,预处理器就会用下面这个表达式替换上面的表达式:5*5

Esta es la macro más básica, pero a menudo hay errores causados ​​por la precedencia de los símbolos en las macros:

#define SQUARE( x ) x * x
//这个宏接收一个参数 x

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

    return 0;
}

A primera vista, podría pensar que este código imprimirá el valor 36. De hecho, imprimirá 11. ¿Por qué es esto?

Al reemplazar texto, el parámetro x se reemplaza con a + 1, por lo que esta declaración en realidad se convierte en: printf ("%d\n",a + 1 * a + 1); por lo que el resultado es 5+1*5+ 1= 11

Este problema se resuelve fácilmente agregando dos paréntesis a la definición de la macro: #define SQUARE(x) (x) * (x)

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

Hay muchos usos de #define, haré un resumen general aquí:

1. Indica la longitud de la matriz estática

#define N 100 char arr[N];

2. Definir constantes de carácter

#define STOPFLA '#'

3. Definir constantes de cadena

#define PRINT "hola mundo".

4. Definir macros (funciones simples)

#define AGREGAR(X) ((X)+(X))

 #define reglas de sustitución

Hay varios pasos involucrados al expandir #defines para definir símbolos y macros en un programa.

1. Al llamar a una 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, el archivo resultante se escanea nuevamente para ver si contiene algún símbolo definido por #define. En caso afirmativo, repita el proceso anterior.

Aviso:

1. Los símbolos definidos por otros #defines pueden aparecer en parámetros macro y definiciones de #define. Pero con macros, la recursividad no puede ocurrir. 2. Cuando el preprocesador busca símbolos definidos por #define, no se busca el contenido de la constante de cadena.

 El papel de # y ##

Primero veamos este código:

char* p = "hello ""bit\n";
printf("hello"" world\n");
printf("%s", p)

Aquí printf generará esta cadena. Entonces, ¿cómo insertamos parámetros en la cadena? 

Utilice # para convertir un parámetro de macro en una cadena correspondiente.

int i = 10;
#define PRINT(FORMAT, VALUE)\
 printf("the value of " #VALUE "is "FORMAT "\n", VALUE);
...
PRINT("%d", i+3);

 El preprocesador procesará el #VALOR en el código como: "VALOR". El resultado final debe ser: el valor de i+3 es 13.

El rol de ##:

## Puede combinar los símbolos de ambos lados en un solo símbolo. Permite definiciones de macros para crear identificadores a partir de fragmentos de texto separados.

#define ADD_TO_SUM(num, value) \
 sum##num += value;
...
ADD_TO_SUM(5, 10);//作用是:给sum5增加10.

Tal conexión debe dar como resultado un identificador válido. De lo contrario, el resultado es indefinido.

Argumentos macro con efectos secundarios

Cuando un parámetro de macro aparece más de una vez en la definición de la macro, si el parámetro tiene efectos secundarios, entonces puede ser peligroso usar esta macro, lo que puede tener consecuencias impredecibles. Los efectos secundarios son efectos permanentes que ocurren cuando se evalúa una expresión.

Por ejemplo: x+1;//sin efectos secundarios x++;//con efectos secundarios

La macro MAX puede demostrar problemas causados ​​por parámetros con efectos secundarios.

#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
...
x = 5;
y = 8;
z = MAX(x++, y++);
printf("x=%d y=%d z=%d\n", x, y, z);//输出的结果是什么?

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

Entonces el resultado de salida es: x=6 y=10 z=9

¡Porque la sustitución hace que el valor de la variable se manipule varias veces! ! !

Entonces, al usar macros, debemos evitar usar ++, --.

Macros frente a funciones 

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

#define MAX(a, b) ((a)>(b)?(a):(b))

Entonces, ¿por qué no usar una función para esta tarea?

1. El código para llamar y regresar de una función puede tomar más tiempo del que se necesita para realizar este pequeño trabajo computacional. Entonces, las macros son mejores que 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 tipos como enteros, enteros largos y tipos de punto flotante que se pueden usar para comparar? Las macros son independientes del tipo.

Desventajas de las macros : Por supuesto, en comparación con las funciones, las macros también tienen desventajas:

1. Cada vez que se utilice una macro, se insertará una copia del código de definición de la macro en el programa. A menos que la macro sea relativamente corta, puede aumentar considerablemente la duración del programa.

2. Las macros no se pueden depurar. 

3. Dado que las macros son independientes del tipo, no son lo suficientemente rigurosas.

4. Las macros pueden causar problemas con la precedencia de los operadores, lo que hace que la programación sea propensa 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.

Una comparación de macros y funciones:

Atributos #defineDefinir macros 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 la 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 para las llamadas y devoluciones de funciones, por lo que es relativamente lento
precedencia del operador La evaluación de los parámetros de macro está en el contexto de todas las expresiones circundantes. A menos que se agreguen paréntesis, la precedencia de los operadores adyacentes puede producir resultados impredecibles, por lo que se recomienda usar más paréntesis al escribir macros. Los parámetros de función se evalúan solo una vez cuando se llama a la función y su valor resultante se pasa a la función. El resultado de evaluar una expresión es más predecible.
Parámetros con efectos secundarios Los parámetros se pueden sustituir en varios lugares dentro 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, y el resultado es más fácil de controlar.
Tipo de parámetro El parámetro de macro no tiene nada que ver con el tipo, siempre que la operación en el parámetro sea legal, se puede usar para cualquier tipo de parámetro. Los parámetros de una función están relacionados con el tipo, si los tipos de los parámetros son diferentes, se requieren diferentes funciones, incluso si realizan la misma tarea.
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 de uso de las macros de funciones es muy 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 todos los nombres de macros, y no todos los nombres de funciones en mayúsculas.

#undef

Este comando se utiliza para eliminar una definición de macro.

#undef NOMBRE

// Si es necesario redefinir un nombre existente, primero se debe eliminar su nombre anterior.

el archivo contiene 

Ya sabemos que la directiva #include puede hacer que se compile otro archivo. Justo como donde realmente aparece en la directiva #include.

La forma en que funciona esta sustitución es simple: el preprocesador primero elimina la 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.

 La forma en que 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 buscar el archivo de encabezado de la función de biblioteca. Si no se encuentra, se solicitará un error de compilación.

El archivo de la biblioteca contiene

#include<stdio.h>

Para encontrar el archivo de encabezado, vaya directamente a la ruta estándar para encontrarlo.Si no puede encontrarlo, generará un error de compilación.

¿Se puede usar la forma de "" para archivos de biblioteca?

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

El archivo anidado contiene

 Si ocurre tal escenario: comm.h y comm.c son módulos públicos. test1.h y test1.c usan módulos comunes. test2.h y test2.c usan módulos comunes. test.h y test.c usan el módulo test1 y el módulo test2. De esta forma, aparecerán dos copias de comm.h en el programa final. Esto crea la duplicación del contenido del archivo.

¿Cómo resolver este problema?

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

o:

#pragma once

Puede evitar la introducción repetida de archivos de encabezado.


Todavía hay muchas instrucciones de preprocesamiento en el lenguaje C, ¡y todavía tenemos que seguir aprendiendo! !

Supongo que te gusta

Origin blog.csdn.net/m0_74755811/article/details/132009588
Recomendado
Clasificación