suelo - ¿La pila crece hacia arriba o hacia abajo?
porque las raices crecen hacia abajo (13)
Depende de tu sistema operativo y tu compilador.
Tengo este fragmento de código en c:
int q = 10;
int s = 5;
int a[3];
printf("Address of a: %d/n", (int)a);
printf("Address of a[1]: %d/n", (int)&a[1]);
printf("Address of a[2]: %d/n", (int)&a[2]);
printf("Address of q: %d/n", (int)&q);
printf("Address of s: %d/n", (int)&s);
El resultado es:
Address of a: 2293584
Address of a[1]: 2293588
Address of a[2]: 2293592
Address of q: 2293612
Address of s: 2293608
Entonces, veo que de a
a a[2]
, las direcciones de memoria aumentan en 4 bytes cada una. Pero de q
a s
, las direcciones de memoria disminuyen en 4 bytes.
Me pregunto 2 cosas:
- ¿La pila crece o baja? (Me parece a mí en este caso)
- ¿Qué ocurre entre las direcciones de memoria
a[2]
q
? ¿Por qué hay una gran diferencia de memoria allí? (20 bytes).
Nota: esta no es una pregunta para la tarea. Tengo curiosidad sobre cómo funciona la pila. Gracias por cualquier ayuda.
El compilador puede asignar variables locales (automáticas) en cualquier lugar del marco de pila local, no se puede deducir de manera confiable la dirección de crecimiento de la pila a partir de eso. Puede inferir la dirección de crecimiento de la pila comparando las direcciones de los marcos de pila anidados, es decir, comparando la dirección de una variable local dentro del marco de la pila de una función en comparación con su llamada:
int f(int *x)
{
int a;
return x == NULL ? f(&a) : &a - x;
}
int main(void)
{
printf("stack grows %s!/n", f(NULL) < 0 ? "down" : "up");
return 0;
}
El comportamiento de la pila (creciendo o disminuyendo) depende de la interfaz binaria de la aplicación (ABI) y de cómo se organiza la pila de llamadas (también conocida como registro de activación).
A lo largo de su vida, un programa está obligado a comunicarse con otros programas como el sistema operativo. ABI determina cómo un programa puede comunicarse con otro programa.
La pila para diferentes arquitecturas puede crecer de cualquier forma, pero para una arquitectura será consistente. Por favor revisa this enlace wiki. Pero, el crecimiento de la pila es decidido por el ABI de esa arquitectura.
Por ejemplo, si toma MIPS ABI, la pila de llamadas se define a continuación.
Consideremos que la función ''fn1'' llama ''fn2''. Ahora el marco de pila visto por ''fn2'' es el siguiente:
direction of | |
growth of +---------------------------------+
stack | Parameters passed by fn1(caller)|
from higher addr.| |
to lower addr. | Direction of growth is opposite |
| | to direction of stack growth |
| +---------------------------------+ <-- SP on entry to fn2
| | Return address from fn2(callee) |
V +---------------------------------+
| Callee saved registers being |
| used in the callee function |
+---------------------------------+
| Local variables of fn2 |
|(Direction of growth of frame is |
| same as direction of growth of |
| stack) |
+---------------------------------+
| Arguments to functions called |
| by fn2 |
+---------------------------------+ <- Current SP after stack
frame is allocated
Ahora puedes ver que la pila crece hacia abajo. Entonces, si las variables se asignan al marco local de la función, las direcciones de la variable realmente crecen hacia abajo. El compilador puede decidir sobre el orden de las variables para la asignación de memoria. (En su caso, puede ser ''q'' o ''s'' que primero se asigna la memoria de pila. Pero, en general, el compilador acumula la asignación de memoria según el orden de la declaración de las variables).
Pero en el caso de las matrices, la asignación solo tiene un puntero y la memoria necesita asignarse en realidad apuntada por un solo puntero. La memoria debe ser contigua para una matriz. Entonces, aunque la pila crece hacia abajo, para las matrices la pila crece.
En primer lugar, sus 8 bytes de espacio no utilizado en la memoria (no es 12, recuerda que la pila crece hacia abajo, por lo que el espacio que no está asignado es de 604 a 597). ¿y por qué?. Porque cada tipo de datos ocupa espacio en la memoria a partir de la dirección divisible por su tamaño. En nuestro caso, una matriz de 3 enteros toma 12 bytes de espacio de memoria y 604 no es divisible por 12. Entonces deja espacios vacíos hasta que encuentra una dirección de memoria que es divisible por 12, es 596.
Por lo tanto, el espacio de memoria asignado a la matriz es de 596 a 584. Pero como la asignación de la matriz es en continuación, el primer elemento de la matriz comienza desde la dirección 584 y no desde la 596.
En un x86, la "asignación" de memoria de un marco de pila consiste simplemente en restar el número necesario de bytes del puntero de pila (creo que otras arquitecturas son similares). En este sentido, creo que la pila crece "hacia abajo", ya que las direcciones se vuelven progresivamente más pequeñas a medida que llamas más profundamente a la pila (pero siempre visualizo la memoria comenzando con 0 en la parte superior izquierda y obteniendo direcciones más grandes a medida que avanzas a la derecha y envolver, así que en mi imagen mental la pila crece ...). El orden de las variables que se declaran puede no tener ninguna incidencia en sus direcciones: creo que el estándar permite al compilador reordenarlas, siempre que no cause efectos secundarios (por favor, corrígeme si me equivoco) . Simplemente están atrapados en algún lugar de esa brecha en las direcciones utilizadas creadas cuando resta el número de bytes del puntero de la pila.
La brecha alrededor de la matriz puede ser algún tipo de relleno, pero es misteriosa para mí.
Esto es realmente dos preguntas. Una es sobre la forma en que crece la pila cuando una función llama a otra (cuando se asigna un nuevo marco) y la otra sobre cómo se presentan las variables en el marco de una función en particular.
Ninguno de los dos está especificado por el estándar C, pero las respuestas son un poco diferentes:
- ¿De qué manera crece la pila cuando se asigna un nuevo cuadro? Si la función f () llama a la función g (), ¿será el puntero de marco de
f
mayor o menor que el puntero de marco deg
? Esto puede ir de cualquier modo: depende del compilador y la arquitectura en particular (buscar "convención de llamadas"), pero siempre es coherente dentro de una plataforma determinada (con algunas excepciones extrañas, véanse los comentarios). Abajo es más común; es el caso en x86, PowerPC, MIPS, SPARC, EE y las SPU de celda. - ¿Cómo se presentan las variables locales de una función dentro de su marco de pila? Esto no está especificado y es completamente impredecible; el compilador es libre de organizar sus variables locales, sin embargo, le gusta obtener el resultado más eficiente.
La dirección en que crecen las pilas es específica de la arquitectura. Dicho esto, tengo entendido que solo unas pocas arquitecturas de hardware tienen pilas que crecen.
La dirección en que crece una pila es independiente del diseño de un objeto individual. Por lo tanto, aunque la pila crezca, las matrices no lo harán (es decir, & array [n] siempre será <& array [n + 1]);
La pila crece hacia abajo (en x86). Sin embargo, la pila se asigna en un bloque cuando se carga la función, y no tienes garantía de qué orden serán los elementos en la pila.
En este caso, asignó espacio para dos entradas y una matriz de tres int en la pila. También asignó 12 bytes adicionales después de la matriz, por lo que se ve así:
a [12 bytes]
relleno (?) [12 bytes]
s [4 bytes]
q [4 bytes]
Por alguna razón, su compilador decidió que necesitaba asignar 32 bytes para esta función, y posiblemente más. Eso es opaco para usted como programador de C, no puede saber por qué.
Si quiere saber por qué, compile el código en lenguaje ensamblador, creo que es -S en gcc y / S en el compilador C de MS. Si observa las instrucciones de apertura de esa función, verá que se guarda el antiguo puntero de pila y luego se restan 32 (¡o algo más!). Desde allí, puede ver cómo el código accede a ese bloque de 32 bytes de memoria y descubrir qué está haciendo su compilador. Al final de la función, puede ver que se restaura el puntero de la pila.
La pila crece. Entonces f (g (h ())), la pila asignada para h comenzará en una dirección más baja, entonces g yg serán menores que f. Pero las variables dentro de la pila deben seguir la especificación C,
http://c0x.coding-guidelines.com/6.5.8.html
1206 Si los objetos apuntados son miembros del mismo objeto agregado, los punteros a los miembros de estructura declarados posteriormente comparan mayor que los punteros con los miembros declarados anteriormente en la estructura, y los punteros a los elementos de matriz con valores de subíndice más grandes comparan mayor que los punteros a elementos del mismo matriz con valores de subíndices más bajos.
& a [0] <& a [1], siempre debe ser verdadero, independientemente de cómo se asigne ''a''
Mi pila parece extenderse hacia direcciones con números más bajos.
Puede ser diferente en otra computadora, o incluso en mi propia computadora si utilizo una invocación de compilador diferente. ... o el muigt del compilador eligió no usar ninguna pila en absoluto (todo en línea (funciones y variables si no tomé la dirección de ellas)).
$ cat stack.c
#include <stdio.h>
int stack(int x) {
printf("level %d: x is at %p/n", x, (void*)&x);
if (x == 0) return 0;
return stack(x - 1);
}
int main(void) {
stack(4);
return 0;
}
$ /usr/bin/gcc -Wall -Wextra -std=c89 -pedantic stack.c
$ ./a.out level 4: x is at 0x7fff7781190c level 3: x is at 0x7fff778118ec level 2: x is at 0x7fff778118cc level 1: x is at 0x7fff778118ac level 0: x is at 0x7fff7781188c
No creo que sea determinista así. La matriz parece "crecer" porque esa memoria debe asignarse contiguamente. Sin embargo, como q y s no están relacionados entre sí, el compilador simplemente los pega en una ubicación de memoria libre arbitraria dentro de la pila, probablemente los que encajan mejor en un tamaño entero.
Lo que sucedió entre a [2] yq es que el espacio alrededor de la ubicación de q no era lo suficientemente grande (es decir, no era más grande que 12 bytes) para asignar una matriz de 3 enteros.
No hay nada en el estándar que indique cómo se organizan las cosas en la pila. De hecho, podría compilar un compilador conforme que no almacenara los elementos de la matriz en elementos contiguos de la pila, siempre que tuviera la inteligencia para hacer aritmética de elementos de matriz correctamente (para que supiera, por ejemplo, que un 1 era 1 K de distancia de un [0] y podría ajustarse para eso).
La razón por la que puede obtener resultados diferentes es porque, aunque la pila puede crecer para agregarle "objetos", la matriz es un único "objeto" y puede tener elementos de matriz ascendente en el orden opuesto. Pero no es seguro confiar en ese comportamiento, ya que la dirección puede cambiar y las variables pueden intercambiarse por una variedad de razones que incluyen, entre otras:
- mejoramiento.
- alineación.
- los caprichos de la persona la parte de gestión de la pila del compilador.
Mira 1 mi excelente tratado sobre la dirección de la pila :-)
En respuesta a sus preguntas específicas:
- ¿La pila crece o baja?
No importa para nada (en términos del estándar) pero, como usted lo pidió, puede crecer o disminuir en la memoria, dependiendo de la implementación. - ¿Qué ocurre entre las direcciones de memoria [2] yq? ¿Por qué hay una gran diferencia de memoria allí? (20 bytes)?
No importa para nada (en términos del estándar). Ver arriba por posibles razones.
crece hacia abajo y esto se debe al pequeño estándar de orden de bytes endian cuando se trata del conjunto de datos en la memoria.
Una forma de verlo es que la pila crece SI mira la memoria desde 0 desde la parte superior y desde la parte inferior.
La razón de que la pila crezca hacia abajo es poder desreferencia desde la perspectiva de la pila o el puntero base.
Recuerde que la desreferenciación de cualquier tipo aumenta de la dirección más baja a la más alta. Dado que Stack crece hacia abajo (dirección más alta a más baja), esto le permite tratar la pila como memoria dinámica.
Esta es una razón por la cual muchos lenguajes de programación y scripts usan una máquina virtual basada en pila en lugar de una basada en registro.