Tabla de contenido
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 #including
el 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:
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:
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:
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:
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:
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+1
es 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:
¡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
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 max
se 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 a
veces 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:
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 #undef
eliminar 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 printf
dichas 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.c
archivo 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.h
el archivo y declaralo:
#pragma once
void fun_print(char *x);
Finalmente, main.c
haz 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
#error
Es 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:
por supuesto, diferentes IDE pueden informar errores en diferentes formas.
#progma
Las 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 #pragma
instrucció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.