Saltar a contenido

Última actualización:2026-10-12

Macros y compilación condicional en C++

Sobre el autor:

Cuenta pública personal:

wechat

1. Símbolos especiales en macros

Se dividen en varias categorías: #, ##, \

1.1 Operador de conversión a cadena (#)

Cuando se usa un # delante de un parámetro dentro de una macro, el preprocesador convierte ese parámetro en un arreglo de caracteres, dicho de otro modo: # significa "conversiones a cadena"; el # que aparece en la definición de una macro convierte en una cadena el parámetro que lo sigue.

Nota: solo puede usarse en definiciones de macros con parámetros de entrada, y debe colocarse delante del nombre del parámetro dentro del cuerpo de la definición de la macro.

Por ejemplo:

#define exp(s) printf("test s is:%s\n",s)
#define exp1(s) printf("test s is:%s\n",#s)
#define exp2(s) #s 
int main() {
    exp("hello");
    exp1(hello);

    string str = exp2(   bac );
    cout<<str<<" "<<str.size()<<endl;
    /**
     * 忽略传入参数名前面和后面的空格。
     */
    string str1 = exp2( asda  bac );
    /**
     * 当传入参数名间存在空格时,编译器将会自动连接各个子字符串,
     * 用每个子字符串之间以一个空格连接,忽略剩余空格。
     */
    cout<<str1<<" "<<str1.size()<<endl;
    return 0;
}

El código anterior muestra el uso básico y las reglas de tratamiento de los espacios, que son las siguientes:

  • Se ignoran los espacios delante y detrás del nombre del parámetro pasado.
string str = exp2(   bac );
cout<<str<<" "<<str.size()<<endl;

Salida:

bac 3
  • Cuando hay espacios entre los nombres de los parámetros pasados, el compilador concatena automáticamente las subcadenas, uniendo cada subcadena con un solo espacio e ignorando los espacios restantes.
string str1 = exp2( asda  bac );
cout<<str1<<" "<<str1.size()<<endl;

Salida:

asda bac 8

1.2 Operador de concatenación de símbolos (##)

"##" es una forma de conexión mediante separación: primero separa y luego realiza una concatenación forzada. Convierte los múltiples parámetros formales de la definición de la macro en un único nombre de parámetro real.

Puntos a tener en cuenta:

(1) Al conectar parámetros formales con ##, los espacios antes y después de ## son opcionales.

(2) El nombre de parámetro real resultante de la conexión debe ser un nombre de parámetro realmente existente o una definición de macro conocida por el compilador.

(3) Si el parámetro posterior a ## es a su vez una macro, ## impide la expansión de esa macro.

Ejemplo:

#define expA(s) printf("前缀加上后的字符串为:%s\n",gc_##s)  //gc_s必须存在
// 注意事项2
#define expB(s) printf("前缀加上后的字符串为:%s\n",gc_  ##  s)  //gc_s必须存在
// 注意事项1
#define gc_hello1 "I am gc_hello1"
int main() {
    // 注意事项1
    const char * gc_hello = "I am gc_hello";
    expA(hello);
    expB(hello1);
}

1.3 Operador de continuación de línea ()

Cuando una macro definida no puede expresarse por completo en una sola línea, se puede usar "\" para indicar que la definición de la macro continúa en la línea siguiente.

Nota: deje un espacio antes de .

#define MAX(a,b) ((a)>(b) ? (a) \
   :(b))  
int main() {
    int max_val = MAX(3,6);
    cout<<max_val<<endl;
}

El código anterior puede verse en: sig_examp.cpp

2. Uso de do{...}while(0)

2.1 Evitar interpretaciones semánticas erróneas

Por ejemplo:

#define fun() f1();f2();
if(a>0)
    fun()

Esta macro, una vez expandida, queda así:

if(a>0)
    f1();
    f2();

La intención era que con a>0 se ejecutaran f1 y f2, pero en realidad f2 se ejecuta cada vez, de modo que el resultado es erróneo.

Para resolver este tipo de problemas, al escribir código normalmente se puede emplear un bloque {}.

Por ejemplo:

#define fun() {f1();f2();}
if(a>0)
    fun();
// 宏展开
if(a>0)
{
    f1();
    f2();
};

Sin embargo, se observa que tras la expansión de la macro anterior sobra un punto y coma, y la sintaxis real no es del todo correcta. (Aunque compila y se ejecuta sin problema; lo normal es que no haya punto y coma).

2.2 Evitar el uso de goto para el control de flujo

En algunas funciones puede que necesitemos hacer algún trabajo de limpieza antes de la sentencia return, por ejemplo liberar el espacio de memoria solicitado con malloc al comienzo de la función; usar goto siempre ha sido un método sencillo:

int f() {
    int *p = (int *)malloc(sizeof(int));
    *p = 10; 
    cout<<*p<<endl;
#ifndef DEBUG
    int error=1;
#endif
    if(error)
        goto END;
    // dosomething
END:
    cout<<"free"<<endl;
    free(p);
    return 0;
}

Pero dado que goto no se ajusta a la estructuración de la ingeniería del software y puede hacer que el código resulte difícil de entender, mucha gente desaconseja su uso; en ese caso podemos usar do{...}while(0) para hacer lo mismo:

int ff() {
    int *p = (int *)malloc(sizeof(int));
    *p = 10; 
    cout<<*p<<endl;
    do{ 
#ifndef DEBUG
        int error=1;
#endif
        if(error)
            break;
        //dosomething
    }while(0);
    cout<<"free"<<endl;
    free(p);
    return 0;
}

Aquí el cuerpo principal de la función se envuelve con do{...}while(0), se usa break en lugar de goto y el trabajo de limpieza posterior se coloca después del while; de este modo se consigue el mismo efecto y, además, la legibilidad y la mantenibilidad del código son mucho mejores que las del código con goto anterior.

2.3 Evitar las advertencias causadas por macros

En el kernel, debido a las limitaciones de las distintas arquitecturas, muchas veces se utilizan macros vacías. Al compilar, estas macros vacías generan warnings; para evitar tales warnings, podemos usar do{...}while(0) para definir macros vacías:

#define EMPTYMICRO do{}while(0)

2.4 Definir un único bloque de función para completar operaciones complejas

Si tienes una función compleja, con muchas variables, y no quieres añadir una función nueva, puedes usar do{...}while(0) y escribir tu código dentro; dentro puedes definir variables sin preocuparte de que sus nombres colisionen con los de antes o después de la función. Esta situación se refiere a cuando una variable se usa en varios lugares (pero con un significado distinto en cada uno); podemos reducir el ámbito dentro de cada do-while, por ejemplo:

int fc()
{
    int k1 = 10;
    cout<<k1<<endl;
    do{
        int k1 = 100;
        cout<<k1<<endl;
    }while(0);
    cout<<k1<<endl;
}

El código anterior puede verse en: do_while.cpp

Artículo de estudio: https://www.cnblogs.com/lizhenghn/p/3674430.html

Temas relacionados

Comentarios