que - c99 pasó a la inicialización
goto c++ para que sirve (7)
Como dice AndreyT , C99 permite saltar sobre la inicialización. Puede corregir su lógica utilizando etiquetas separadas para los dos fracasos:
int func()
{
char *p1 = malloc(...);
if (p1 == NULL)
goto err_exit_p1;
char *p2 = malloc(...);
if (p2 == NULL)
goto err_exit;
...
err_exit:
free(p2);
err_exit_p1:
free(p1);
return -1;
}
Este es un patrón estándar: "errores tempranos" provocan un salto a una parte posterior del código de salida de error.
Al depurar un bloqueo, encontré este problema en algún código:
int func()
{
char *p1 = malloc(...);
if (p1 == NULL)
goto err_exit;
char *p2 = malloc(...);
if (p2 == NULL)
goto err_exit;
...
err_exit:
free(p2);
free(p1);
return -1;
}
El problema ocurre cuando falla el primer malloc. Debido a que saltamos la inicialización de p2
, contiene datos aleatorios y la llamada a free(p2)
puede fallar.
Esperaría / espero que esto se trate de la misma manera que en C ++, donde el compilador no permite que un goto salte a través de una inicialización.
Mi pregunta: ¿está saltando una inicialización permitida por el estándar o es esto un error en la implementación de c99 de gcc?
Esto no es un error en gcc. Un salto es solo un salto en C. No se aplica una lógica especial. El problema es que no estás inicializando tus punteros a NULL
primero. Si hicieras eso, entonces la llamada gratuita sería free(NULL)
y no se bloquearía. Comience la función con char *p1 = NULL, *p2 = NULL;
y todo estará bien.
Hmm, no es porque el nuevo estándar permita declaraciones variables en ningún lado, siempre es una buena idea usarlo. En tu caso, me gustaría que lo hiciéramos en el clásico C.
int func()
{
char *p1 = NULL; /* So we have a definite value */
char *p2 = NULL;
p1 = malloc(...);
if(!p1)
goto err_exit;
p2 = malloc(...);
if(!p2)
goto err_exit;
...
err_exit:
free(p2);
free(p1);
return -1;
}
Puedes pedirle a gcc que te avise cuando -Wjump-misses-init
por encima de una definición variable usando -Wjump-misses-init
y luego puedes usar -Werror
(o, más precisamente, -Werror=jump-misses-init
) para forzar a los usuarios a tratar con eso. Esta advertencia se incluye en -Wc++-compat
por lo que los desarrolladores de gcc son conscientes de que el código se comporta de manera diferente en C frente a C ++.
También puedes cambiar el código ligeramente:
int func()
{
char *p1 = malloc(...);
if (p1 == NULL)
goto err_exit_1;
char *p2 = malloc(...);
if (p2 == NULL)
goto err_exit_2;
...
err_exit_2:
free(p2);
err_exit_1:
free(p1);
return -1;
}
... y solo sigue emparejando etiquetas con variables inicializadas. Tendrá el mismo problema al llamar a muchas otras funciones con variables unitarias, y resulta que es mucho más obvio.
Un salto como ese está permitido por el estándar, por lo que no es un error en GCC. La norma enumera esta situación como una advertencia sugerida en el Anexo I.
La única restricción impuesta a los saltos en C99 con respecto al alcance es que es ilegal saltar al ámbito de una variable de tipo modificado variablemente, como un VLA
int main() {
int n = 5;
goto label; // <- ERROR: illegal jump
int a[n];
label:;
}
En otras palabras, no es correcto decir que "un salto es solo un salto en C". Los saltos son algo restringidos cuando se trata de ingresar el alcance variable, aunque no tan estrictamente como en C ++. La situación que describes no es una de las restringidas.
Usar gotos no es una buena idea, y acabas de encontrar una razón por la cual. Debería llamar a una función de manejo de errores para cada error individual.
si compilo este código con la bandera -O2
gcc -Wall -std=c99 -O2 jump.c
Tengo una advertencia:
jump.c: In function ‘func’:
jump.c:10: warning: ‘p2’ may be used uninitialised in this function
y sin advertencia sin optimización