El tutorial de lenguaje C más sólido de la historia ---- compilación y preprocesamiento de programas (2)

contenido

3. Preprocesamiento detallado

3.1 Símbolos predefinidos

3.2 #definir

3.2.1 #define definir identificador

3.2.2 #define definir macro

3.2.3 #define la regla de sustitución

3.2.4 # y ##

3.2.5 Parámetros macro con efectos secundarios

3.2.6 Comparación de macros y funciones

3.2.7 Convenciones de nombres

3.3 #undef

3.4 Definición de línea de comando

3.5 Compilación condicional

3.6 Inclusión de archivos

3.6.1 Cómo se incluyen los archivos de encabezado

3.6.2 Inclusión de archivos anidados


3. Preprocesamiento detallado

3.1 Símbolos predefinidos

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

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

Aquí hay un ejemplo:

#include<stdio.h>
int main()
{
	printf("%s\n", __FILE__);
	printf("%d\n", __LINE__);
	printf("%s\n"__DATE__);
	printf("%s\n", __TIME__);
	//printf("%d", __STDC__);在vs中不遵循ANSI C,是未定义行为
	return 0;
}

Ejecute la captura de pantalla:

Aplicación: Registro

#include<stdio.h>
int main()
{
	int i = 0;
	FILE* pf = fopen("log.txt", "w");
	if (pf == NULL)
	{
		return 1;
	}
	for (i = 0; i < 10; i++)
	{
		fprintf(pf,"%s %s %s %d\n", __DATE__, __TIME__, __FILE__, __LINE__);
	}
	fclose(pf);
	pf = NULL;
	return 0;
}

Ejecute la captura de pantalla:

3.2 #definir

3.2.1 #define definir identificador

语法: 
    #define name stuff

por ejemplo:

#define MAX 1000
#define reg register          //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;)     //用更形象的符号来替换一种实现
#define CASE break;case        //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
                          date:%s\ttime:%s\n" ,\
__FILE__,__LINE__ ,       \
__DATE__,__TIME__ ) 

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

por ejemplo:

#define MAX 1000;
#define MAX 1000

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

Por ejemplo el siguiente escenario:

if(condition) 
    max = MAX;
else 
    max = 0;

Hay un error de sintaxis aquí (de lo contrario, no tiene coincidencia si). Aquí debe aclararse la esencia de la definición de macro.La esencia de la definición de macro es el reemplazo.

3.2.2 #define definir macro

El mecanismo #define incluye una disposición que permite que los parámetros se sustituyan en 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.

Nota: El paréntesis izquierdo de la lista de parámetros debe estar inmediatamente junto al nombre. Si hay algún espacio en blanco en el medio, la lista de argumentos se interpreta como parte de cosas.

Tal como:

#define SQUARE( x ) x * x

Esta macro toma un parámetro x .

Si después de la declaración anterior, usted pone

SQUARE( 5 );

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

5 * 5

Advertencia: Hay un problema con esta macro:

Observe el fragmento de código a continuación:

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é?

替换文本时,参数x被替换成a + 1,所以这条语句实际上变成了:
printf ("%d\n",a + 1 * a + 1 );

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

Este problema se resuelve fácilmente agregando dos paréntesis a la definición de la macro:

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

Este preprocesamiento 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 introducir nuevos errores.

int a = 5;
printf("%d\n" ,10 * DOUBLE(a));

¿Qué valor imprimirá esto?

Parece que imprime 100, pero de hecho imprime 55. Encontramos que después de reemplazar:

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

La operación de multiplicación precede a la suma definida por la macro, por lo que hay

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 ) )

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.

3.2.3 #define la regla de sustitución

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. (La definición de macro en la cadena no será reemplazada)

3.2.4 # y ##

¿Cómo insertar parámetros en cadenas?

Primero veamos este código:

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

¿La salida es un poco hola?

La respuesta es definitiva: sí.

Encontramos que las cadenas tienen las características de la concatenación automática.

1. ¿Podemos escribir código como este? :

#define PRINT(FORMAT, VALUE)\
 printf("the value is "FORMAT"\n", VALUE);
...
PRINT("%d", 10);
//上述宏定义替换之后的结果如下所示:
printf("the value is " "%d" "\n",10);

resultado de la operación:

Aquí puede poner una cadena en una cadena solo cuando la cadena se usa como parámetro de macro.

1. Otro truco es usar # para convertir un parámetro macro en una cadena correspondiente.

por ejemplo:

int i = 10;
#define PRINT(VALUE)\
 printf("the value of " #VALUE "is %d \n", VALUE);
...
PRINT( i + 3);//产生了什么效果?
//上面的这段代码会替换为:
printf("the value of " "value" "is %d \n",VALUE);

El #VALOR en el código es procesado por el preprocesador como:

"VALOR"

El resultado final debe ser:

the value of i+3 is 13

El rol de ##

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

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

NOTA: Tales conexiones deben generar un identificador válido. De lo contrario, el resultado es indefinido.

3.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:

x+1;//不带副作用
x++;//带有副作用

La macro MAX puede probar problemas causados ​​por argumentos 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í tenemos que saber cuál es el resultado del procesamiento del preprocesador:

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

Entonces la salida es:

x=6 y=10 z=9

3.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))

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

Hay dos razones:

  1. El código utilizado para llamar a la función y regresar de la función puede llevar más tiempo que realizar realmente este pequeño trabajo computacional (las funciones incluyen llamadas a funciones, operaciones lógicas y la función devuelve tres partes que llevan tiempo. Las macros solo necesitan operaciones lógicas). 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. (La macro se reemplazó en la etapa de precompilación y el código posterior a la macro se reemplazó durante la depuración, es decir, el código depurado y el código depurado real no son el mismo código)
  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

macro #defindedefine

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.

3.2.7 Convenciones de nombres

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:

poner en mayúscula los nombres de las macros

No use todos los nombres de funciones en mayúsculas

3.3 #undef

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

#undef NAME
//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。

3.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).

#include <stdio.h>
int main()
{
    int array[ARRAY_SIZE];
    int i = 0;
    for (i = 0; i < ARRAY_SIZE; i++)
    {
        array[i] = i;
    }
    for (i = 0; i < ARRAY_SIZE; i++)
    {
        printf("%d ", array[i]);
    }
    printf("\n");
    return 0;
}

Instrucciones de compilación:

gcc -D ARRAY_SIZE=10 test.c(假设文件名为test.c)

3.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, es una lástima eliminarlo y es una lástima mantenerlo en el camino, para que podamos compilarlo selectivamente.

#include<stdio.h>
int main()
{
#if 0
	for (int i = 0; i < 10; i++)
	{
		printf("hello world\n");
	}
#endif
	return 0;
}

Ejecute la captura de pantalla:

Supongamos que el código se modifica de la siguiente manera:

#include<stdio.h>
int main()
{
#if 1
	for (int i = 0; i < 10; i++)
	{
		printf("hello world\n");
	}
#endif
	return 0;
}

Ejecute la captura de pantalla:

Nota: si el #if va seguido de una variable, el resultado es similar al #if seguido de 0. ¿Por qué sucede esto? Porque las variables se generan después de la ejecución, es decir, las variables tienen significados correspondientes después de la ejecución. Después de ejecutarlo, las instrucciones de preprocesamiento han desaparecido.

Directivas comunes de compilación condicional:

1.
#if 常量表达式
//...
#endif
//常量表达式由预处理器求值。常量表达式也可以是逻辑表达式,比如0>2为假,返回值为0,就相当于0
如:
#define __DEBUG__ 1
#if __DEBUG__//此时的效果与1完全相同
//..
#endif
2.多个分支的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
3.判断是否被定义
#if defined(symbol)
#ifdef symbol
//这两种是来判断是否被定义 
#if !defined(symbol)
#ifndef symbol
//这两种是来判断是否没有被定义
4.嵌套指令
#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

Nota: En la compilación condicional, si el carácter después de nuestro if no está predefinido, o está predefinido después de la compilación condicional, entonces este carácter es equivalente a 0. Pero dado que generalmente lo usamos en instrucciones de compilación condicional, generalmente debe aparecer el frente. Aquí también debemos prestar atención a un punto, aunque los caracteres que no están predefinidos se establecerán por defecto en 0 cuando se realiza el juicio condicional de compilación condicional, pero cuando se usa la función printf para generar, si no hay predefinidos antes, no puede ser realizado.salida, ya que no hay una directiva de preprocesamiento correspondiente para hacer que se reemplace durante el preprocesamiento.

3.6 Inclusión de archivos

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.

3.6.1 Cómo se incluyen los archivos de encabezado

  • el archivo local contiene
#include "filename"

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 el archivo de encabezado de la función de biblioteca.

Si no se encuentra, generará un error de compilación.

Ruta a los archivos de encabezado estándar para el entorno Linux:

/usr/include

Ruta a los archivos de encabezado estándar para el entorno VS: (VS2013)

C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include

Preste atención para encontrarlo de acuerdo con su propia ruta de instalación.

  • el archivo de la biblioteca contiene
#include <filename.h>

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

3.6.2 Inclusión de archivos anidados

Si tal escenario ocurre:

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, 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__

Motivo: Porque cuando se incluye el archivo por segunda vez, antes se ha realizado una compilación condicional, es decir, se ha definido __TEST_H__ una vez, por lo que esta vez no se incluirá dos veces el contenido del archivo en test.h, eso es Ignorar el contenido entre la compilación condicional en test.h.

Nota: No es necesario usar __TEST_H__, pero generalmente se usa el nombre del archivo incluido, principalmente para una mejor identificación y distinción.

o:

#pragma once

Esto evita la duplicación de archivos de encabezado. Esta forma de escribir generalmente es compatible con los nuevos compiladores.

Supongo que te gusta

Origin blog.csdn.net/m0_57304511/article/details/123211919
Recomendado
Clasificación