TIP/BUG: Ese maldito preprocesador

Os voy a contar una historia, que comienza con un díalogo entre programador y cliente:


CLIENTE: Quiero que las opciones A y B no soporten esto.
PROGRAMADOR: ¿Una opción en la configuración?
C: No, que no se pueda cambiar.
P: Okis [Por dentro: Grrrrrr, una espuerta de ifs por todo el código, pero bueno].


El proyecto se termina, se testea, se ponen en la calle unas cuantas máquinas de muestra antes de entrar en producción a nivel nacional y…


C: Oye, que cuando dije Diego digo Ciego… Que quiero que todas las opciones soporten esto, incluidas la A y la B.
P: Grrrrrrrrrr. Si me hubieras hecho caso en un principio… te hubiera puesto la opción en la sección de configuración encriptada… Ahora hay que tocar el código y volver a verificar todo…
C: Ya, tu hazlo.


Okis. A quitar una espuerta de ifs… Pero mejor, que ya me conozco el percal, y lo mismo le da por querer que lo vuelva a poner. Compilación condicional. Si vuelve a cambiar de opinión, sólo tengo que borrar un define en las opciones del proyecto.


Se hacen los cambios pertinentes, lo pruebo en mi entorno virtual y todo OK. Se lo paso al cliente, lo prueba y el programa peta miserablemente y de la forma más extraña que pueda ocurrir… A mi no me ocurre. Me traen una máquina final, hago los cambios y no me ocurre. Al cliente sí y a mi no. Se detiene la producción en serie.


Vienen a mi casa a verlo: efectivamente, no falla. Pero en la calle sí. Traen otra máquina, la adapto y no falla. Se la llevan, la ponen en la calle y falla. La traen: no falla. Y mientras la producción detenida, unos diez tíos tocándose las bolas durante una semana…


La cosa sube a mayores. Me voy con ellos al local donde está la máquina de pruebas. La dejamos sola y nos sentamos en un rincón. La gente comienza a usarla: no falla. De repente, ¡pop, fallo! Salimos corriendo como lobos hacia el tipo que la estaba usando, le preguntamos qué ha hecho, nos dice que nada, que usar la máquina…


La abrimos. Miramos el cajón del dinero: billete de 5 euros encima de todo… Aísssss. Le damos su billete al cliente, que se va. Sacamos un billete de 5, probamos, y ¡peta!


Vale. Ahora ya está claro: desde el cambio nunca la he probado con billetes de 5, el valor más bajo ha sido de 10. Esas coincidencias de la vida en que cuando yo hice las pruebas tras el último cambio no tenía billetes de 5, y cuando he estado yo presente nunca se ha probado con un billete de 5… Pero la calle es la calle.


Ya termino. Anoche, revisión de código hasta las tantas. Ya la puedo hacer fallar en un entorno controlado… Vaya, un gráfico que no se carga. Pues sí que… Vaya, pues sí que se carga, en esta línea se carga… espera, no se carga. Cagontó. ¡Solucionado!


¿Qué estaba pasando?
Echemos un vistazo a este código (que es una especie de pseudocódigo que simula lo que me estaba pasando):



if(param!=Enum::Opcion1)
  
CargaGráfico(1);
CargaGráfico(2);
CargaGráfico(3);
CargaGráfico(4);


Simple, ¿no? ¿Y ahora?



#ifdef LIMITAR_AB 
if(param!=Enum::Opcion1)
#endif  
CargaGráfico(1);
CargaGráfico(2);
CargaGráfico(3);
CargaGráfico(4);


¿Lo ven? Pues yo cuando hice el código, no me di cuenta de ello. Si no está definido LIMITAR_AB, la sentencia con el if no se compila… Pero tampoco CargaGráfico(1);, esté o no definido LIMITAR_AB .


¿Por qué? Pues porque el preprocesador exige líneas completas, con lo cual ignora lo que venga después. Por lo menos en C++/CLI. Lo que no sé si se trata de un bug del compilador, de algo perfectamente definido en el estándar, o una de esas zonas grises que toda definición de un lenguaje tiene. Y también ignoro si ocurre en C#. Y tampoco tengo ganas de comprobarlo.


El código correcto sería:



#ifdef LIMITAR_AB 
if(param!=Enum::Opcion1)
#endif  
   
CargaGráfico(1);
CargaGráfico(2);
CargaGráfico(3);
CargaGráfico(4);


que ejecuta CargaGráfico(1); si no está definido LIMITAR_AB o, si estándolo, param no es la Opcion1.


Ahora estoy a la espera de llamadas con problemas que estoy casi seguro que no se van a producir… Aunque más de uno esté acordándose de mi por tener que trabajar, como poco, hasta mañana por la mañana. 🙁

4 comentarios sobre “TIP/BUG: Ese maldito preprocesador”

  1. Y si ese es el código, sigue habiendo una puerta a fallos.

    lo mejor es
    #ifdef xxx
    if (opcion = xxx)
    #endif
    {cargag(x);}
    cargag(y);

    Es buena costumbre poner llaves en los ifs aunque solo sea una instrucción.

    Un saludo.

  2. Está definido en el estandar (por lo menos en C++).
    Las directivas de preprocesamiento empiezan a trabajar cuando en una linea el primer carácter no blanco es # y acaba con el final de linea. En tu caso, toda lo que hay detrás del #endif se considera comentario, no es necesario poner //

    Por curiosidad prueba lo siguiente:

    #include Comentario sin //

    Tiene que compilar correctamente, aunque haga daño a la vista 🙂

    Yo, personalmente, siempre pongo las // aunque no sean necesarias. Hace años me pasó algo parecido a lo que cuentas.

  3. Bartomeu, es muy curioso. Haciendo lo que comentas, compila pero da un warnig:

    «warning C4067: unexpected tokens following preprocessor directive – expected a newline»

    Sin embargo, cuando va detrás de un #endif no dice nada…

    Bleach, tienes razón, pero yo veo más claro el código sin las llaves, uno espera una sola sentencia. El error se puede dar cuando haces algo como

    while() hazAlgo();

    que sí puede dar lugar a error con la línea siguiente, pero en mi caso siempre lo pongo así:

    while()

    hazAlgo();

    y hasta la fecha no me he equivocado nunca.

    Por otro lado hay que tener en cuenta que no todos los compiladores trabajan igual, y existen algunos para arquitecturas de micros «tiny» (sobre todo viejos), generan diferente código si hay llaves o si no las hay.

  4. Pues eso si que es un bug 🙂

    O no tendría que dar warning núnca. O tendría que dar warning siempre.

    Yo preferiría que lo diera siempre.

    Si quieres ser exhaustivo, comprueba otras ordenes: #elif, #else, #undef

Responder a anonymous Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *