En C, ¿las llaves actúan como un marco de stack?

Si creo una variable dentro de un nuevo conjunto de llaves, ¿esa variable sale de la stack en el corchete de cierre o cuelga hasta el final de la función? Por ejemplo:

void foo() { int c[100]; { int d[200]; } //code that takes a while return; } 

¿Va a tomar memoria durante el code that takes a while sección code that takes a while ?

No, las llaves no actúan como un marco de stack. En C, las llaves solo denotan un scope para nombrar, pero nada se destruye ni se saca nada de la stack cuando el control pasa de ella.

Como progtwigdor que escribe código, a menudo puede pensarlo como si fuera un marco de stack. Los identificadores declarados dentro de los corchetes solo son accesibles dentro de los corchetes, por lo que desde el punto de vista de un progtwigdor, es como si fueran insertados en la stack a medida que se declaran y luego aparecen cuando se sale del scope. Sin embargo, los comstackdores no tienen que generar código que empuje / revele cualquier cosa en la entrada / salida (y generalmente, no lo hacen).

También tenga en cuenta que las variables locales pueden no utilizar ningún espacio de stack en absoluto: pueden mantenerse en registros de la CPU o en otra ubicación de almacenamiento auxiliar, o pueden optimizarse por completo.

Entonces, la matriz d , en teoría, podría consumir memoria para toda la función. Sin embargo, el comstackdor puede optimizarlo o compartir su memoria con otras variables locales cuyos tiempos de vida de uso no se superponen.

El tiempo durante el cual la variable está realmente ocupando memoria es obviamente dependiente del comstackdor (y muchos comstackdores no ajustan el puntero de la stack cuando se ingresan y salen bloques internos dentro de las funciones).

Sin embargo, una pregunta estrechamente relacionada pero posiblemente más interesante es si el progtwig tiene permiso para acceder a ese objeto interno fuera del scope interno (pero dentro de la función que lo contiene), es decir:

 void foo() { int c[100]; int *p; { int d[200]; p = d; } /* Can I access p[0] here? */ return; } 

(En otras palabras: ¿se permite al comstackdor desasignar d , incluso si en la práctica la mayoría no lo hace?).

La respuesta es que el comstackdor puede desasignar d y acceder a p[0] donde el comentario indica un comportamiento indefinido (el progtwig no puede acceder al objeto interno fuera del scope interno). La parte relevante del estándar C es 6.2.4p5:

Para dicho objeto [uno que tiene una duración de almacenamiento automática] que no tiene un tipo de matriz de longitud variable, su duración se extiende desde la entrada en el bloque con el que está asociado hasta que la ejecución de ese bloque termina de alguna manera . (Ingresar un bloque cerrado o llamar a una función suspende, pero no termina, 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 statement en la ejecución del bloque; de lo contrario, el valor se vuelve indeterminado cada vez que se alcanza la statement.

Su pregunta no es lo suficientemente clara como para responderla sin ambigüedades.

Por un lado, los comstackdores normalmente no hacen ninguna asignación de memoria local-desasignación para los ámbitos de bloques nesteds. La memoria local normalmente se asigna solo una vez en la entrada de la función y se libera en la salida de la función.

Por otro lado, cuando finaliza la vida útil de un objeto local, la memoria ocupada por ese objeto puede reutilizarse posteriormente para otro objeto local. Por ejemplo, en este código

 void foo() { { int d[100]; } { double e[20]; } } 

Ambas matrices generalmente ocuparán la misma área de memoria, lo que significa que la cantidad total de almacenamiento local que necesita la función foo es la necesaria para la mayor de dos matrices, no para las dos al mismo tiempo.

Si el último califica como d continuar ocupando la memoria hasta el final de la función en el contexto de su pregunta es para que usted decida.

Depende de la implementación. Escribí un progtwig corto para probar lo que hace gcc 4.3.4, y asigna todo el espacio de la stack a la vez al comienzo de la función. Puede examinar el conjunto que gcc produce utilizando la bandera -S.

No, d [] no estará en la stack por el rest de la rutina. Pero alloca () es diferente.

Editar: Kristopher Johnson (y Simon y Daniel) tienen razón , y mi respuesta inicial fue incorrecta . Con gcc 4.3.4.en CYGWIN, el código:

 void foo(int[]); void bar(void); void foobar(int); void foobar(int flag) { if (flag) { int big[100000000]; foo(big); } bar(); } 

da:

 _foobar: pushl %ebp movl %esp, %ebp movl $400000008, %eax call __alloca cmpl $0, 8(%ebp) je L2 leal -400000000(%ebp), %eax movl %eax, (%esp) call _foo L2: call _bar leave ret 

¡Vive y aprende! Y una prueba rápida parece mostrar que AndreyT también tiene razón sobre las asignaciones múltiples.

Agregado mucho más tarde : la prueba anterior muestra que la documentación de gcc no es del todo correcta. Por años ha dicho (énfasis agregado):

“El espacio para una matriz de longitud variable se desasigna en cuanto finaliza el scope del nombre de la matriz”.

Podrían. Puede que no. La respuesta que creo que realmente necesitas es: nunca asums nada. Los comstackdores modernos hacen todo tipo de architecture y magia específica de la implementación. Escribe tu código simple y legiblemente a los humanos y deja que el comstackdor haga las cosas buenas. Si intenta codificar el comstackdor, está buscando problemas, y los problemas que suele tener en estas situaciones suelen ser terriblemente sutiles y difíciles de diagnosticar.

Su variable d lo general no sale de la stack. Las llaves no indican un marco de stack. De lo contrario, no podrías hacer algo como esto:

 char var = getch(); { char next_var = var + 1; use_variable(next_char); } 

Si las llaves forzaran una verdadera stack push / pop (como lo haría una llamada a la función), entonces el código anterior no se comstackría porque el código dentro de las llaves no podría acceder a la var variable que vive fuera de las llaves (como un sub -función no puede acceder directamente a las variables en la función de llamada). Sabemos que este no es el caso.

Las llaves se usan simplemente para determinar el scope. El comstackdor considerará que cualquier acceso a la variable “interna” desde el exterior de las llaves adjuntas es inválido, y puede reutilizar esa memoria para otra cosa (esto depende de la implementación). Sin embargo, es posible que no salte de la stack hasta que regrese la función envolvente.

Actualización: esto es lo que la especificación C tiene para decir. En cuanto a objetos con duración de almacenamiento automático (sección 6.4.2):

Para un objeto que no tiene un tipo de matriz de longitud variable, su duración se extiende desde la entrada en el bloque con el que está asociado hasta que la ejecución de ese bloque termina de todos modos.

La misma sección define el término “vida” como (énfasis mío):

La duración de un objeto es la porción de ejecución del progtwig durante la cual se garantiza que el almacenamiento está reservado para él. Existe un objeto, tiene una dirección constante y conserva su último valor almacenado a lo largo de su vida útil. Si se hace referencia a un objeto fuera de su tiempo de vida, el comportamiento no está definido.

La palabra clave aquí es, por supuesto, ‘garantizada’. Una vez que abandona el scope del conjunto interno de llaves, la vida útil de la matriz ha terminado. El almacenamiento puede o no asignarse (su comstackdor puede volver a usar el espacio para otra cosa), pero cualquier bash de acceder a la matriz invoca un comportamiento indefinido y produce resultados impredecibles.

La especificación C no tiene noción de marcos de stack. Habla solo de cómo se comportará el progtwig resultante y deja los detalles de implementación al comstackdor (después de todo, la implementación sería bastante diferente en una CPU sin stack que en una CPU con una stack de hardware). No hay nada en la especificación C que indique dónde terminará o no terminará un marco de stack. La única manera real de saber es comstackr el código en su comstackdor / plataforma particular y examinar el ensamblaje resultante. El conjunto actual de opciones de optimización de tu comstackdor también jugará un rol en esto.

Si desea asegurarse de que la matriz d ya no consum memoria mientras se ejecuta su código, puede convertir el código entre llaves en una función separada o explícitamente malloc y free la memoria en lugar de utilizar el almacenamiento automático.

Creo que sale fuera del scope, pero no se elimina de la stack hasta que la función vuelva. Por lo tanto, seguirá ocupando la memoria en la stack hasta que se complete la función, pero no será accesible después de la primera llave de cierre.

Ya se ha proporcionado mucha información sobre la norma que indica que, de hecho, es específica de la implementación.

Entonces, un experimento podría ser de interés. Si probamos el siguiente código:

 #include  int main() { int* x; int* y; { int a; x = &a; printf("%p\n", (void*) x); } { int b; y = &b; printf("%p\n", (void*) y); } } 

Usando gcc obtenemos aquí dos veces la misma dirección: Coliro

Pero si probamos el siguiente código:

 #include  int main() { int* x; int* y; { int a; x = &a; } { int b; y = &b; } printf("%p\n", (void*) x); printf("%p\n", (void*) y); } 

Usando gcc obtenemos aquí dos direcciones diferentes: Coliro

Entonces, no puedes estar realmente seguro de lo que está pasando.