<Lenguaje C> Preprocesamiento y macros

1. Símbolos predefinidos

__FILE__    //进行编译的源文件
__LINE__    //文件当前的行号
__DATE__    //文件被编译的日期
__TIME__    //文件被编译的时间
__STDC__    //如果编译器遵循ANSI C,其值为1,否则未定义

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

Por ejemplo:

#include <stdio.h>

int main() {
    
    
    printf("%s\n", __FILE__);//如:1.c
    printf("%d\n", __LINE__);// 5
    printf("%s\n", __DATE__);// Jul 30 2023
    printf("%s\n", __TIME__);// 10:13:20  记录的时间是编译的时间
    printf("%d\n", __STDC__);   //1   也可能是未定义  不遵循ANSI C
    return 0;
}

2 #definir

2.1 #define definición identificador

#defineDefina un identificador de la forma:

#define 标识符 值

Entre ellos, 标识符se encuentra el nombre que desea definir, y puede ser un valor, una cadena o una expresión.

ejemplo:

#include <stdio.h>
#define MAX 100
#define STR "Hello Wrold"
#define do_forever for (;;)
int main() {
    
    
    printf("%d\n", MAX);//100
    printf(STR);        //Hello World
    do_forever;       //死循环

    return 0;
}

#defineSimplemente haga un reemplazo de texto simple, sin verificación de tipos ni verificación de errores.

Se recomienda #defineno agregar un punto y coma después

#include <stdio.h>
#define MAX 1000;
int main() {
    
    
    int max = 0;
    if (3 > 5) {
    
    
        //max = MAX;   //报错  因为MAX ==1000; 出现了两个分号
        max = MAX//正确
    } else {
    
    
        max = 0;
    }

    return 0;
}

2.2 #define macro de definición

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

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.

ejemplo:

#include <stdio.h>

//函数解决
int Max_hanshu(int x, int y) {
    
    
    return x > y ? x : y;
}
//宏解决
#define MAX(x, y) (x > y ? x : y)
int main() {
    
    
    int a = 10;
    int b = 20;
    int max = Max_hanshu(a, b);
    int m = MAX(a, b);
    printf("%d\n", max);  //20
    printf("%d\n", m);    //20
    return 0;
}

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.

Por ejemplo:

#define SQUARE( x ) x * x

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

SQUARE( 5 );

Colocado en un programa, el preprocesador reemplazará la expresión anterior con la siguiente 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 con un + 1, por lo que esta declaración en realidad se convierte en:

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

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

Agregar dos paréntesis a la definición de macro resuelve este problema fácilmente:

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

Después de preprocesar de esta manera, se 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 tener 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.
Encontramos que después del reemplazo:

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

La operación de multiplicación precede a la suma definida por la macro, por lo que aparece 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 ) )
#include <stdio.h>

#define SQUARE(X) X *X
#define SQUARE1(X) (X) * (X)
#define DOUBLE(X) (X) + (X)
#define DOUBLE1(X) ((X) + (X))
int main() {
    
    
    printf("%d\n", SQUARE(5));     // 25
    printf("%d\n", SQUARE(5 + 1)); // 5+1*5+1 == 11
    printf("%d\n", SQUARE1(5 + 1));// 36

    printf("%d\n", DOUBLE(6));      // 12
    printf("%d\n", DOUBLE(6 + 1));  // 14
    printf("%d\n", 10 * DOUBLE(6)); // 66  10*(6)+(6) ==66
    printf("%d\n", 10 * DOUBLE1(6));//120
    return 0;
}

Resumir:

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.

2.3 Reglas de reemplazo para #define

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

  • Al llamar a una macro, primero se comprueban los argumentos para ver si contienen algún símbolo definido por #define. Si es así, se reemplazan primero.
  • 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.
  • 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:

  • Otros símbolos definidos por #define pueden aparecer en parámetros de macro y definiciones de #define. Pero con macros, la recursividad no puede ocurrir.
  • Cuando el preprocesador busca símbolos definidos por #define, no se busca el contenido de las constantes de cadena.

Ejemplo 1 - Definición de macro legal:

#define PI 3.14159
#define CIRCLE_AREA(radius) (PI * (radius) * (radius))

double area = CIRCLE_AREA(2.5); // 宏 CIRCLE_AREA 使用了已定义的宏 PI

Ejemplo 2 - Definición de macro ilegal (recursiva):

// 这是一个非法的宏定义,宏 AREA 使用了它自身
#define AREA(x) (x > 0 ? x * x : AREA(x))

int result = AREA(5); // 这将导致宏展开的无限循环,造成编译错误

2.4 # y ##

#El operador puede convertir argumentos macro en constantes de cadena. Le permite convertir argumentos en cadenas literales en definiciones de macros .

Ejemplo:

#define STRINGIFY(x) #x

int main() {
    
    
    int num = 42;
    const char* str = STRINGIFY(num);
    // 在宏展开时,num 被转换为字符串 "42"
    printf("num as a string: %s\n", str); // Output: "num as a string: 42"
    return 0;
}

##El operador se utiliza para pegar dos tokens juntos en una definición de macro. Le permite combinar múltiples identificadores en un nuevo identificador .

Ejemplo:

#define CONCAT(x, y) x ## y

int main() {
    
    
    int num1 = 10;
    int num2 = 20;
    int result = CONCAT(num, 1) + CONCAT(num, 2);
    // 在宏展开时,CONCAT(num, 1) 变成 num1,CONCAT(num, 2) 变成 num2
    // 所以,result 的值就是 num1 + num2,即 10 + 20
    printf("result: %d\n", result); // Output: "result: 30"
    return 0;
}

2.5 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.

Código con efectos secundarios :

int main() {
    
    
    int a = 1;
    int b = a + 1;// b=2,a=1
    a = 1;
    b = ++a;// b=2,a=2  带有副作用的代码,a的值发生了改变
    int ch = getchar();//读一个字符,缓冲区少一个字符

    return 0;
}
x+1;//不带副作用
x++;//带有副作用

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);//输出的结果是什么?

Aquí necesitamos saber cuál es el resultado del preprocesador:

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

Entonces la salida es:

x=6 y=10 z=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.

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

Hay dos razones:

1. El código utilizado para llamar a las funciones y regresar de las funciones 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.

Ejemplo:

#include <stdio.h>

int Max(int x, int y) {
    
    
    return x > y ? x : y;
}

#define MAX(x, y) ((x) > (y) ? (x) : (y))
int main() {
    
    
    int a = 10;
    int b = 20;
    //函数的方式
    int m1 = Max(a, b);
    printf("%d\n", m1);

    //宏的方式
    int m2 = MAX(a, b);
    printf("%d\n", m2);
    return 0;
}

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

  1. Cada vez que se utiliza una macro, se inserta en el programa una copia del código definido por la macro. A menos que la macro sea relativamente corta, puede aumentar considerablemente 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 introducir problemas de precedencia de operadores, lo que hace que los programas 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))
int main(){
    
    
    int *p = malloc(10, sizeof(int));
    MALLOC(10, int);  //类型作为参数
    return 0;
} 

Una comparación de macros y funciones.

Atributos #definemacros 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.

2.7 Convenciones de nombres

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 los nombres de las macros

No use todos los nombres de funciones en mayúsculas

3.#undef

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

#include <stdio.h>

#define M 100
int main() {
    
    
    printf("%d\n");
#undef M
    printf("%d\n", M);//报错,M的宏定义已经被移除
    return 0;
}

4. Definición de la línea de comandos

El lenguaje C es un lenguaje de programación de propósito general que permite a los desarrolladores interactuar con las computadoras escribiendo programas de línea de comandos. Los programas de línea de comandos son programas que se ejecutan en una interfaz de línea de comandos (también conocida como terminal o símbolo del sistema). Los usuarios pueden invocar estos programas ingresando comandos y parámetros y obtener resultados de la salida del programa.

En lenguaje C, los parámetros de la línea de comandos se pasan al programa a través de los parámetros de la función principal. La función principal es el punto de entrada del programa C, tiene dos parámetros: argcy argv.

  1. argc: Indica el número de argumentos de la línea de comandos, incluido el propio programa. Es una variable de tipo entero.
  2. argv: es un puntero a una matriz de punteros de caracteres, que se utiliza para almacenar cadenas de argumentos de línea de comandos. Cada cadena representa un argumento de línea de comandos. Entre ellos, argv[0] almacena el nombre del programa (el nombre del archivo ejecutable), argv[1] almacena el primer parámetro de la línea de comandos, etc.
int main(int argc, char *argv[]) {
    
    
    // Your code here
    return 0;
}

Ejemplo de explicación:

Suponiendo que tenemos un programa llamado "my_program", el archivo ejecutable compilado "my_program.exe" (en Windows), y luego ejecutamos el programa en la línea de comando, la entrada es la siguiente:

my_program hello world

En este ejemplo, el valor de argc será 4, ya que hay cuatro argumentos: el nombre del programa "mi_programa", "hola", "mundo" y un puntero nulo implícito que indica el final de la cadena.

La matriz argv contendrá lo siguiente:

argv[0] -> "my_program"
argv[1] -> "hello"
argv[2] -> "world"
argv[3] -> NULL
#include <stdio.h>

int main(int argc, char *argv[]) {
    
    
    for (int i = 0; i < argc; i++) {
    
    
        printf("Argument %d: %s\n", i, argv[i]);
    }
    return 0;
}

Resultado de salida:

Argument 0: my_program
Argument 1: hello
Argument 2: world

5. Compilación condicional

La compilación condicional es una directiva de preprocesamiento que permite incluir o excluir selectivamente fragmentos de código en función de diferentes condiciones durante la fase de compilación. La compilación condicional se puede usar para controlar el comportamiento de un programa en función de diferentes condiciones de compilación, como usar un código diferente en diferentes plataformas o habilitar/deshabilitar funciones específicas.

La compilación condicional se implementa mediante las directivas de preprocesamiento , #ifdef, #ifndef, #else, #endif, #if, #elifetc. #defineTodas estas directivas comienzan con un signo de almohadilla (#) y son procesadas por el preprocesador antes de la compilación.

Las siguientes son las instrucciones básicas para la compilación condicional en lenguaje C:

1. #ifdefy #ifndef:

#ifdef 宏名
    // 如果宏已定义,则编译这里的代码
#else
    // 如果宏未定义,则编译这里的代码
#endif

#ifdefSe utiliza para verificar si se ha definido una macro, si está definida, luego compilar el código #ifdefentre #endify , de lo contrario, omita esta parte del código.

#ifndefLuego, en lugar #ifdefde , verifica si una macro no está definida y, de ser así, compila el código entre #ifndefy #endif.

2. #else

#ifdef 宏名
    // 如果宏已定义,则编译这里的代码
#else
    // 如果宏未定义,则编译这里的代码
#endif

#elseSe utiliza para compilar el código entre y cuando no se cumplen las condiciones #ifdefo .#ifndef#else#endif

3. #if, #elify #endif:

#if 表达式
    // 如果表达式为真,则编译这里的代码
#elif 其他表达式
    // 如果其他表达式为真,则编译这里的代码
#else
    // 如果前面的条件都不满足,则编译这里的代码
#endif

#ifLe permite decidir si compilar el siguiente código en función del resultado de una expresión. #elifSe utiliza para comprobar otras condiciones cuando no se cumplen las condiciones anteriores. #elseSe utiliza para hacer frente a la situación de que no se cumple ninguna de las condiciones anteriores.

compilación condicional anidada

#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 archivo contiene

6.1 Cómo se incluyen los archivos de encabezado

1. El archivo local contiene " "

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.

#include"add.h"
int main(){
    
    
    printf("hehe\n");
    return 0;
}

2. El archivo de biblioteca contiene < >

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.

De esta manera, se puede decir que los archivos de biblioteca también se pueden incluir en forma de " "?
La respuesta es sí, puedes.

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.

6.2 El archivo anidado incluye

inserte la descripción de la imagen aquí

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 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?
Respuesta: compilación condicional.

Al comienzo de cada archivo de encabezado, escriba:

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

o:

#pragma once

Supongo que te gusta

Origin blog.csdn.net/ikun66666/article/details/132005546
Recomendado
Clasificación