GOTO antes de variable local
declaration undefined-behavior (3)
Este código no tiene un comportamiento indefinido. Podemos encontrar un buen ejemplo en Rationale for International Standard — Programming Languages — C en la sección 6.2.4
Duración de almacenamiento de los objetos que dice:
[...] Hay una regla general simple: la variable declarada se crea con un valor no especificado cuando se ingresa al bloque, pero el inicializador se evalúa y el valor se coloca en la variable cuando se alcanza la declaración en el curso normal de ejecución. Por lo tanto, un salto hacia adelante más allá de una declaración lo deja sin inicializar, mientras que un salto hacia atrás hará que se inicialice más de una vez. Si la declaración no inicializa la variable, la establece en un valor no especificado, incluso si esta no es la primera vez que se alcanza la declaración.
El alcance de una variable comienza en su declaración. Por lo tanto, aunque la variable existe tan pronto como se ingresa al bloque, no se puede hacer referencia a él hasta que se llegue a su declaración.
y proporciona el siguiente ejemplo:
int j = 42;
{
int i = 0;
loop:
printf("I = %4d, ", i);
printf("J1 = %4d, ", ++j);
int j = i;
printf("J2 = %4d, ", ++j);
int k;
printf("K1 = %4d, ", k);
k = i * 10;
printf("K2 = %4d, ", k);
if (i % 2 == 0) goto skip;
int m = i * 5;
skip:
printf("M = %4d/n", m);
if (++i < 5) goto loop;
}
y la salida es:
I = 0, J1 = 43, J2 = 1, K1 = ????, K2 = 0, M = ???? I = 1, J1 = 44, J2 = 2, K1 = ????, K2 = 10, M = 5 I = 2, J1 = 45, J2 = 3, K1 = ????, K2 = 20, M = 5 I = 3, J1 = 46, J2 = 4, K1 = ????, K2 = 30, M = 15 I = 4, J1 = 47, J2 = 5, K1 = ????, K2 = 40, M = 15
y dice:
donde "????" indica un valor indeterminado (y cualquier uso de un valor indeterminado es un comportamiento indefinido).
Este ejemplo es coherente con el borrador de la sección 6.29 de la norma C99. Duración del almacenamiento de los objetos, párrafo 5, que dice:
Para un objeto de este tipo que no tiene un tipo de matriz de longitud variable, su tiempo de vida se extiende desde la entrada al bloque con el que está asociado hasta que la ejecución de ese bloque finaliza de cualquier manera. (Entrar en un bloque cerrado o llamar a una función suspende, pero no finaliza, la ejecución del bloque actual). Si el bloque se ingresa recursivamente, se crea una nueva instancia del objeto cada vez. El valor inicial del objeto es indeterminado. Si se especifica una inicialización para el objeto, se realiza cada vez que se alcanza la declaración en la ejecución del bloque; de lo contrario, el valor se vuelve indeterminado cada vez que se alcanza la declaración.
¿El siguiente fragmento de código constituye un comportamiento indefinido, ya que estoy saltando antes de la declaración de la variable y lo uso a través de un puntero? Si es así, ¿hay diferencias entre los estándares?
int main() {
int *p = 0;
label1:
if (p) {
printf("%d/n", *p);
return 0;
}
int i = 999;
p = &i;
goto label1;
return -1;
}
Intentaré responder la pregunta que tal vez intentaste hacer.
El comportamiento de su programa está bien definido. (El return -1;
es problemático; solo 0
, EXIT_SUCCESS
y EXIT_FAILURE
están bien definidos como valores devueltos desde main
. Pero eso no es lo que está preguntando).
Este programa:
#include <stdio.h>
int main(void) {
goto LABEL;
int *p = 0;
LABEL:
if (p) {
printf("%d/n", *p);
}
}
tiene un comportamiento indefinido. El goto
transfiere el control a un punto dentro del alcance de p
, pero pasa por alto su inicialización, por lo que p
tiene un valor indeterminado cuando se ejecuta la prueba if (p)
.
En su programa, el valor de p
está bien definido en todo momento. La declaración, que se alcanza antes del goto
, establece p
en 0
(un puntero nulo). La prueba if (p)
es falsa, por lo que el cuerpo de la sentencia if
no se ejecuta la primera vez. El goto
se ejecuta después de que p
haya recibido un valor no nulo bien definido. Después de goto
, la prueba if (p)
es verdadera y se ejecuta la llamada a printf
.
En su programa, la vida útil de p
e i
comienza cuando se alcanza la apertura {
de main
, y termina cuando se alcanza el cierre }
o cuando se ejecuta una declaración de return
. El alcance de cada uno (es decir, la región del texto del programa en el que su nombre es visible) se extiende desde su declaración hasta el cierre }
. Cuando el goto
transfiere el control hacia atrás, el nombre de la variable i
está fuera del alcance, pero el objeto int
al que se refiere ese nombre todavía existe. El nombre p
está en el ámbito (porque se declaró anteriormente) y el objeto puntero todavía apunta al mismo objeto int
(cuyo nombre sería i
si ese nombre fuera visible).
Recuerde que el alcance se refiere a una región del texto del programa en el que un nombre está visible, y la vida útil se refiere a un período de tiempo durante la ejecución del programa durante el cual se garantiza la existencia de un objeto.
Normalmente, si la declaración de un objeto tiene un inicializador, eso garantiza que tiene un valor válido siempre que su nombre sea visible (a menos que luego se le asigne algún valor no válido). Esto se puede omitir con un goto
o un switch
(pero no si se usan con cuidado).
No hay un comportamiento indefinido en su programa.
goto
sentencia tiene dos restricciones:
(c11, 6.8.6.1p1) "El identificador en una declaración goto nombrará una etiqueta ubicada en algún lugar de la función de encierro. Una instrucción goto no saltará desde fuera del alcance de un identificador que tenga un tipo modificado de forma variable hasta dentro del alcance de ese identificador ".
que no está violando y no hay otros requisitos fuera de restricciones.
Tenga en cuenta que es el mismo (en el sentido de que no hay requisitos adicionales) en c99 y c90. Por supuesto, en c90, el programa no sería válido debido a la combinación de declaraciones y declaraciones.
Con respecto a la vida útil del objeto i
cuando se accede después de la instrucción goto
, C dice (vea mi énfasis, las otras oraciones copiadas en el siguiente párrafo serían interesantes para un programa más complicado):
(c11, 6.2.4p6) " Para un objeto de este tipo que no tiene un tipo de matriz de longitud variable, su vida útil se extiende desde la entrada en el bloque con el que está asociado hasta que la ejecución de ese bloque finaliza de alguna manera. [...] Si el bloque se ingresa recursivamente, se crea una nueva instancia del objeto cada vez. [...] Si se especifica una inicialización para el objeto, se realiza cada vez que se alcanza la declaración o el literal compuesto en la ejecución del bloque de lo contrario, el valor se vuelve indeterminado cada vez que se alcanza la declaración ".
Eso significa que, i
todavía estoy vivo cuando se lee *p
; no se accede a ningún objeto fuera de su duración.