Evite usar múltiples bloques pequeños #ifdef

Evite usar múltiples bloques pequeños #ifdef

El fragmento está tomado de CoreCLR proyecto. El error se detecta mediante el siguiente diagnóstico:V522 Es posible que se desreferencia el puntero nulo 'hp'.

heap_segment* gc_heap::get_segment_for_loh (size_t size
#ifdef MULTIPLE_HEAPS
                                           , gc_heap* hp
#endif //MULTIPLE_HEAPS
                                           )
{
#ifndef MULTIPLE_HEAPS
    gc_heap* hp = 0;
#endif //MULTIPLE_HEAPS
    heap_segment* res = hp->get_segment (size, TRUE);
    if (res != 0)
    {
#ifdef MULTIPLE_HEAPS
        heap_segment_heap (res) = hp;
#endif //MULTIPLE_HEAPS
  ....
}

Explicación

Creemos que #ifdef/#endif las construcciones son malas, un mal inevitable, por desgracia. Son necesarios y tenemos que usarlos. Así que no le pediremos que deje de usar #ifdef, no tiene sentido eso. Pero queremos pedirle que tenga cuidado de no "usarlo en exceso".

Tal vez, muchos de ustedes hayan visto código literalmente repleto de #ifdefs . Es especialmente doloroso lidiar con código donde #ifdef se repite cada diez líneas, o incluso más a menudo. Dicho código suele depender del sistema y no puede prescindir del uso de #ifdef i n eso Sin embargo, eso no te hace más feliz.

¡Vea lo difícil que es leer el ejemplo de código anterior! Y es la lectura de código lo que los programadores tienen que hacer como actividad básica. Sí, lo decimos en serio. Pasamos mucho más tiempo revisando y estudiando el código existente que escribiendo uno nuevo. Es por eso que el código que es difícil de leer reduce tanto nuestra eficiencia y deja más posibilidades de que se cuelen nuevos errores.

Volviendo a nuestro fragmento de código, el error se encuentra en la operación de desreferenciación del puntero nulo y ocurre cuando no se declara la macro MULTIPLE_HEAPS. Para ponértelo más fácil, ampliemos las macros:

heap_segment* gc_heap::get_segment_for_loh (size_t size)
{
  gc_heap* hp = 0;
  heap_segment* res = hp->get_segment (size, TRUE);
  ....

El programador declaró el hp variable, la inicializó a NULL , y lo eliminé de inmediato. Si no se ha definido MULTIPLE_HEAPS, nos meteremos en problemas.

Código correcto

Este error aún vive en CoreCLR (12.04.2016) a pesar de que un colega mío lo informó en el artículo "25 Fragmentos de código sospechosos en CoreCLR", por lo que no estamos seguros de cuál es la mejor manera de corregir este error.

Dado que (hp ==nullptr), la variable 'res' también debe inicializarse con algún otro valor, pero no sabemos qué valor exactamente. Así que tendremos que prescindir de la solución esta vez.

Recomendaciones

Eliminar pequeños #ifdef/#endif Bloques de su código:¡hacen que sea realmente difícil de leer y comprender! Código con "woods" de #ifdefs es más difícil de mantener y más propenso a errores.

No existe una recomendación que se adapte a todos los casos posibles; todo depende de la situación particular. De todos modos, recuerda que #ifdef es una fuente de problemas, por lo que siempre debe esforzarse por mantener su código lo más claro posible.

Consejo N1. Intenta rechazar #ifdef .

#ifdef a veces se puede reemplazar con constantes y el habitual if operador. Compare los siguientes 2 fragmentos de código:Una variante con macros:

#define DO 1

#ifdef DO
static void foo1()
{
  zzz();
}
#endif //DO

void F()
{
#ifdef DO
  foo1();
#endif // DO
  foo2();
}

Este código es difícil de leer; ni siquiera tienes ganas de hacerlo. Apuesto a que te lo has saltado, ¿no? Ahora compárelo con lo siguiente:

const bool DO = true;

static void foo1()
{
  if (!DO)
    return;
  zzz();
}

void F()
{
  foo1();
  foo2();
}

Ahora es mucho más fácil de leer. Algunos pueden argumentar que el código se ha vuelto menos eficiente ya que ahora hay una llamada de función y una verificación en él. Pero no estamos de acuerdo con eso. En primer lugar, los compiladores modernos son bastante inteligentes y es muy probable que obtenga el mismo código sin comprobaciones adicionales ni llamadas a funciones en la versión de lanzamiento. En segundo lugar, las posibles pérdidas de rendimiento son demasiado pequeñas para preocuparse. El código limpio y claro es más importante.

Consejo N2. Haga su #ifdef bloques más grandes.

Si tuviéramos que escribir get_segment_for_loh() función, no usaríamos un número de #ifdefs allá; en su lugar, haríamos dos versiones de la función. Cierto, entonces habría un poco más de texto, pero las funciones serían más fáciles de leer y editar también.

Nuevamente, algunos pueden argumentar que es un código duplicado, y dado que tienen muchas funciones largas con #ifdef en cada uno, tener dos versiones de cada función puede hacer que se olviden de una de las versiones al arreglar algo en la otra.

¡Hey, espera! ¿Y por qué sus funciones son largas? Separa la lógica general en funciones auxiliares separadas; luego, las versiones de ambas funciones se acortarán, lo que garantiza que detectarás fácilmente cualquier diferencia entre ellas.

Sabemos que este consejo no es una panacea. Pero piénsalo.

Consejo N3. Considere usar plantillas; podrían ayudar.

Consejo N4. Tómese su tiempo y piénselo bien antes de usar #ifdef . ¿Quizás puedas prescindir de él? O quizás puedas hacerlo con menos #ifdefs , y mantener este "mal" en un solo lugar?

Escrito por Andrey Karpov.

Este error se encontró con PVS-Studio herramienta de análisis estático.