El compilador no detecta una variable obviamente no inicializada

9 minutos de lectura

avatar de usuario
Jabberwocky

Todos los compiladores de C que he probado no detectarán variables no inicializadas en el fragmento de código a continuación. Sin embargo, el caso es obvio aquí.

No se preocupe por la funcionalidad de este fragmento. No es un código real y lo eliminé para la investigación de este problema.

BOOL NearEqual (int tauxprecis, int max, int value)
{
  int tauxtrouve;      // Not initialized at this point
  int totaldiff;       // Not initialized at this point

  for (int i = 0; i < max; i++)
  {
    if (2 < totaldiff)  // At this point totaldiff is not initialized
    {
      totaldiff = 2;
      tauxtrouve = value;  // Commenting this line out will produce warning
    }
  }

  return tauxtrouve == tauxprecis ;  // At this point tauxtrouve is potentially
                                     // not initialized.
}

Por otro lado, si hago un comentario tauxtrouve = value ;obtengo el "local variable 'tauxtrouve' used without having been initialized" advertencia.

Probé estos compiladores:

  • CCG 4.9.2 con -Pared -WExtra
  • Microsoft Visual C++ 2013 con todas las advertencias habilitadas

  • No tengo idea, pero tal vez sea la optimización del compilador. Yo también estoy ansioso por saber. Espero que obtengamos la respuesta pronto.

    – Sourav Ghosh

    21 de noviembre de 2014 a las 14:35


  • ¿Qué pasa si agregas el -pedantic bandera a gcc?

    – avgvstvs

    21 de noviembre de 2014 a las 14:35


  • No estoy familiarizado con cómo se prueba la inicialización, pero echa un vistazo gcc.gnu.org/wiki/Better_Uninitialized_Warnings que encontré googleando. Especialmente CCP assumes a value for uninitialized variables. Si estuviera seguro de mí mismo, habría hecho este comentario y respuesta.

    – Pedro M.

    21 de noviembre de 2014 a las 14:42


  • Visual Studio 2013 me dice “error C4700: variable local no inicializada ‘totaldiff’ utilizada”.

    – barak manos

    21 de noviembre de 2014 a las 15:07

  • stackoverflow.com/q/25151508/541686

    – usuario541686

    21 de noviembre de 2014 a las 19:08

avatar de usuario
brian caín

Se exagera la obviedad con la que esta variable no se inicializa. El análisis de ruta cuesta tiempo y los proveedores de compiladores no querían implementar la función o pensaron que le costaría demasiado tiempo, o simplemente no se inscribió explícitamente.

Por ejemplo, con clang:

$ clang -Wall -Wextra -c obvious.c 
$ clang -Wall -Wextra --analyze -c obvious.c 
obvious.c:9:11: warning: The right operand of '<' is a garbage value
    if (2 < totaldiff)  // at this point totaldiff is not initialized
          ^ ~~~~~~~~~
obvious.c:16:21: warning: The left operand of '==' is a garbage value
  return tauxtrouve == tauxprecis ;  // at this point tauxtrouve is potentially
         ~~~~~~~~~~ ^
2 warnings generated.

La diferencia en el tiempo de ejecución de estos ejemplos ingenuos es insignificante. Pero imagine una unidad de traducción con miles de líneas, decenas de funciones, cada una con bucles y un gran anidamiento. El número de caminos se agrava rápidamente y se convierte en una gran carga para analizar si la primera iteración a través del bucle se producirá o no antes de esa comparación.


EDITAR: @Matthieu señala que con LLVM/clang, el análisis de ruta requerido para encontrar el uso de un valor no inicializado no se agrava a medida que aumenta el anidamiento debido a la notación SSA utilizada por el IR.

No fue tan simple como “-S -emit-llvm” como esperaba, pero encontré la salida de notación SSA que describió. Seré honesto, no estoy lo suficientemente familiarizado con LLVM IR para estar seguro, pero confío en la palabra de Matthieu.

En pocas palabras: uso clang con --analyzeo convencer a alguien para arreglar el gcc error.

; Function Attrs: nounwind uwtable
define i32 @NearEqual(i32 %tauxprecis, i32 %max, i32 %value) #0 {
  br label %1

; <label>:1                                       ; preds = %7, %0
  %tauxtrouve.0 = phi i32 [ undef, %0 ], [ %tauxtrouve.1, %7 ]
  %i.0 = phi i32 [ 0, %0 ], [ %8, %7 ]
  %2 = icmp slt i32 %i.0, %max
  br i1 %2, label %3, label %9

; <label>:3                                       ; preds = %1
  %4 = icmp slt i32 2, 2
  br i1 %4, label %5, label %6

; <label>:5                                       ; preds = %3
  br label %6

; <label>:6                                       ; preds = %5, %3
  %tauxtrouve.1 = phi i32 [ %value, %5 ], [ %tauxtrouve.0, %3 ]
  br label %7

; <label>:7                                       ; preds = %6
  %8 = add nsw i32 %i.0, 1
  br label %1

; <label>:9                                       ; preds = %1
  %10 = icmp eq i32 %tauxtrouve.0, %tauxprecis
  %11 = zext i1 %10 to i32
  ret i32 %11
}

  • ¿Tiene cifras sobre la degradación? Para su información, LLVM IR se basa en la notación SSA que, como su nombre lo indica, solo permite una sola asignación a cualquier variable (a menudo, el nombre de la variable en el código fuente se reutiliza con el sufijo de un índice). Como resultado, los caminos son necesariamente materializado, y verificar si una variable se usa potencialmente sin inicializar es, por lo tanto, trivial: para cada variable, al calcular su asignación, verifique si está definitivamente inicializada, potencialmente sin inicializar o definitivamente sin inicializar. Al usarlo, advertir adecuadamente.

    – Matthieu M.

    21 de noviembre de 2014 a las 17:42

  • @Matthieu Oh, en realidad, no es tan trivial como uno podría pensar. [See here](gist.github.com/voo42/293d3cafa820f8c86e54 para un ejemplo simple donde su propuesta daría advertencias falsas. Ciertamente factible, pero solo usar el formulario SSA no es suficiente. De hecho, no estoy seguro de si existe una solución general que no implique verificar todas las rutas posibles a través de una función.

    – Vu

    22 de noviembre de 2014 a las 22:54

  • @Voo no hay uno que no implique encontrarse con HP. Considerar if (foo(0)) { i = 0 } if (bar(0)) { j = i }. Pero esto requiere que bar(0) => foo(0) entonces necesitamos saber los valores bar(0) y foo(0) se evalúa como – lo cual no es posible sin ejecutar un programa (y esperando que finalice) debido a HP.

    – Maciej Piechotka

    23 de noviembre de 2014 a las 0:00


  • @Voo: Oh, no estoy, EN ABSOLUTO, implicando que es posible una predicción 100% precisa. Por eso tengo 3 estados: el “potencialmente no inicializado” es exactamente para la condición que describe. Y es por eso que Clang tiene 2 advertencias: -Wuninitialized (activado por defecto) solo da advertencias para variables que definitivamente no están inicializadas mientras -Wmaybe-uninitialized (no activado por defecto) solo da advertencias para variables que potencialmente no están inicializadas. Entonces, sí, este último podría dar advertencias falsas por código intrincado; Yo diría que es mejor que arregles el código, pero puedes ignorarlo.

    – Matthieu M.

    23 de noviembre de 2014 a las 11:34

  • El IR es realmente simple, si ignoras el ruido 🙂 Por ejemplo, aquí hay 2 apariciones de tauxtrouve: tauxtrouve.0 y tauxtrouve.1. los phi magic selecciona el valor dependiendo del predecesor: phi i32 [ %value, %5 ], [ %tauxtrouve.0, %3 ] da un valor de tipo i32si el predecesor era la etiqueta denominada 5 entonces cede %value y si fuera el que se nombra 3 cede tauxtrouve.0; undef es un valor especial para representar la falta de inicialización, y br es un condicional goto

    – Matthieu M.

    23 de noviembre de 2014 a las 11:41

avatar de usuario
trucos

Sí, debería generar una advertencia sobre esa variable no inicializada, pero es un error de CCG. El ejemplo que se da allí es:

unsigned bmp_iter_set ();
int something (void);

void bitmap_print_value_set (void)
{
    unsigned first;

    for (; bmp_iter_set (); )
    {
        if (!first)
            something ();
        first = 0;
    }
}

Y diagnosticado con -O2 -W -Wall.

Desafortunadamente, ¡este año es el décimo aniversario de este error!

  • Vaya… un error de diez años, con una lista de comentarios que se lee como la encarnación de los peores temores de snark-iness en un proyecto OSS

    – Pedro M.

    21 de noviembre de 2014 a las 14:49

  • @PeterM; Desafortunadamente, este error de 10 años aún no se ha solucionado.

    – trucos

    21 de noviembre de 2014 a las 14:51


  • #WontFix #NotLouficientementesexyparapreocuparse

    – Pedro M.

    21 de noviembre de 2014 a las 14:53

  • @Lundin escribe en su respuesta que “no puede esperar” que el compilador detecte errores y que “el compilador solo tiene dos tareas”. De hecho, eso era correcto hace 20 años, no tan correcto hoy en día. Los compiladores han evolucionado enormemente, y en estos días la gente confía en los compiladores para detectar errores obvios. A pesar de tener un comportamiento indefinido, sería bueno (y esperado) que el compilador genere una advertencia. No para definir UB, sino para que el programador sepa que lo está haciendo mal. Esto puede no ser un técnico error, pero definitivamente es un error de experiencia del usuario.

    – El Croissant Paramagnético

    21 de noviembre de 2014 a las 15:12


  • Me veo leyendo esto en el futuro: “…este error no se resolvió debido a razones históricas.

    – John Tortugo

    26 de noviembre de 2014 a las 13:17

avatar de usuario
Nate Eldredge

Esta respuesta solo aborda GCC.

Después de una mayor investigación y comentarios, hay más cosas que en mi respuesta anterior. Este fragmento de código tiene dos variables no inicializadas y cada una de ellas no se detecta por un motivo diferente.

En primer lugar, el Documentación CCG Para el -Wuninitialized opción dice:

Debido a que estas advertencias dependen de la optimización, las variables o elementos exactos para los que hay advertencias dependen de las opciones de optimización precisas y la versión de GCC utilizada.

Las versiones anteriores del manual de GCC redactaron esto de manera más explícita. He aquí un extracto de la manual para gcc 3.3.6:

Estas advertencias solo son posibles al optimizar la compilación, porque requieren información de flujo de datos que se calcula solo al optimizar. Si no especifica -O, simplemente no recibirá estas advertencias.

Parece que la versión actual puede dar algunas advertencias sin variables no inicializadas sin -Opero todavía obtienes resultados mucho mejores con él.

Si compilo tu ejemplo usando gcc -std=c99 -Wall -OYo obtengo:

foo.c: In function ‘NearEqual’:
foo.c:15:21: warning: ‘tauxtrouve’ is used uninitialized in this function [-Wuninitialized]
   return tauxtrouve == tauxprecis ;  // at this point tauxtrouve is potentially
                     ^

(Tenga en cuenta que esto es con GCC 4.8.2 ya que no tengo 4.9.x instalado, pero el principio debería ser el mismo).

Eso detecta el hecho de que tauxtrouve no está inicializado.

Sin embargo, si reparamos parcialmente el código agregando un inicializador para tauxtrouve (pero no para totaldiff), luego gcc -std=c99 -Wall -O lo acepta sin ninguna advertencia. Esto parecería ser una instancia del “error” citado en la respuesta de hacks.

Hay algunas dudas sobre si esto realmente debería considerarse un error: GCC no promete capturar todas las instancias posibles de una variable no inicializada. De hecho, no puede hacerlo con perfecta precisión, porque ese es el problema de detención. Por lo tanto, advertencias como esta pueden ser útiles cuando funcionan, ¡pero la ausencia de advertencias no prueba que su código esté libre de variables no inicializadas! Realmente no son un sustituto para verificar cuidadosamente su propio código.

En el informe de error vinculado por hackshay mucha discusión sobre si el error es reparable, o si tratar de detectar esta construcción en particular daría como resultado una tasa de falsos positivos inaceptable para otro código correcto.

  • Creo que el OP está más preocupado por detectar totaldiff no siendo inicializado.

    – Pedro M.

    21 de noviembre de 2014 a las 19:12

  • @PeterM: Buen punto. En realidad, hay dos problemas separados aquí y he reescrito mi respuesta para abordar ambos.

    – Nate Eldredge

    22 de noviembre de 2014 a las 18:42

Michael, no sé en qué versión de Visual Studio 2013 probaste esto, pero ciertamente está desactualizado. Actualización 4 de Visual Studio 2013 produce correctamente el siguiente mensaje de error en el primer uso de totaldiff:

error C4700: uninitialized local variable 'totaldiff' used

Debería considerar actualizar su entorno de trabajo.

Por cierto, esto es lo que veo directamente en el editor:

Visual Studio 2013 detectó el error

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad