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);
La salida 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 hacia arriba o hacia abajo? (Me parece que ambos en este caso)
- ¿Qué sucede entre las direcciones de memoria
a[2]
yq
? ¿Por qué hay una gran diferencia de memoria? (20 bytes).
Nota: Esta no es una pregunta de tarea. Tengo curiosidad por saber cómo funciona la pila. Gracias por cualquier ayuda.
Respuestas:
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 conocido como registro de activación).
A lo largo de su vida, un programa está obligado a comunicarse con otros programas como OS. ABI determina cómo un programa puede comunicarse con otro programa.
La pila para diferentes arquitecturas puede crecer de cualquier manera, pero para una arquitectura será consistente. Consulte este enlace wiki. Pero, el crecimiento de la pila lo decide la 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 a '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 puede 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 en realidad crecen hacia abajo. El compilador puede decidir el orden de las variables para la asignación de memoria. (En su caso, puede ser 'q' o 's' el primero en asignar memoria de pila. Pero, generalmente, el compilador hace la asignación de memoria de pila según el orden de la declaración de las variables).
Pero en el caso de las matrices, la asignación tiene un solo puntero y la memoria que se debe asignar será 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.
fuente
En realidad, se trata de dos preguntas. Uno trata sobre la forma en que crece la pila cuando una función llama a otra (cuando se asigna un nuevo marco), y el otro trata sobre cómo se distribuyen 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:
f
el puntero del marco será mayor o menor queg
el puntero del marco? Esto puede ser de cualquier manera: depende del compilador y la arquitectura en particular (busque "convención de llamada"), pero siempre es consistente dentro de una plataforma dada (con algunas excepciones extrañas, vea los comentarios). Hacia abajo es más común; es el caso de x86, PowerPC, MIPS, SPARC, EE y las SPU de celda.fuente
La dirección en la que crecen las pilas depende de la arquitectura. Dicho esto, tengo entendido que solo unas pocas arquitecturas de hardware tienen pilas que crecen.
La dirección en la que crece una pila es independiente del diseño de un objeto individual. Por tanto, aunque la pila puede reducirse, las matrices no lo harán (es decir, & array [n] siempre será <& array [n + 1]);
fuente
No hay nada en el estándar que exija cómo se organizan las cosas en la pila. De hecho, se podría construir un compilador que no se ajusten almacenar elementos de la matriz de elementos contiguos en la pila en absoluto, siempre y cuando tuvo la inteligencia para hacer aún gama de elementos aritméticos correctamente (por lo que sabía, por ejemplo, que un 1 fue 1K de distancia de un [0] y podría ajustarlo).
La razón por la que puede obtener resultados diferentes es porque, si bien la pila puede crecer hacia abajo para agregarle "objetos", la matriz es un solo "objeto" y puede tener elementos de matriz ascendentes 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:
Vea aquí mi excelente tratado sobre la dirección de la pila :-)
En respuesta a sus preguntas específicas:
No importa en absoluto (en términos del estándar) pero, como lo preguntaste, puede crecer hacia arriba o hacia abajo en la memoria, dependiendo de la implementación.
No importa en absoluto (en términos del estándar). Consulte más arriba para conocer las posibles razones.
fuente
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, supongo que la pila crece "hacia abajo", en el sentido de que las direcciones se vuelven progresivamente más pequeñas a medida que llama más profundamente en la pila (pero siempre imagino que la memoria comienza con 0 en la parte superior izquierda y obtiene direcciones más grandes a medida que avanza a la derecha y envolver hacia abajo, por lo que en mi imagen mental la pila crece ...). El orden de las variables que se declaran puede no tener ninguna relación con sus direcciones; creo que el estándar permite que el compilador las reordene, siempre que no cause efectos secundarios (alguien, por favor, corríjame si me equivoco) . Ellos'
El espacio alrededor de la matriz puede ser una especie de relleno, pero me resulta misterioso.
fuente
En primer lugar, sus 8 bytes de espacio no utilizado en la memoria (no son 12, recuerde que la pila crece hacia abajo, por lo que el espacio que no se asigna 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, la matriz de 3 enteros ocupa 12 bytes de espacio de memoria y 604 no es divisible por 12. De modo que deja espacios vacíos hasta que encuentra una dirección de memoria que es divisible por 12, es 596.
Entonces, el espacio de memoria asignado a la matriz es de 596 a 584. Pero como la asignación de matriz está en continuación, el primer elemento de la matriz comienza desde la dirección 584 y no desde la 596.
fuente
El compilador es libre de asignar variables locales (automáticas) en cualquier lugar del marco de pila local, no puede inferir de manera confiable la dirección de crecimiento de la pila simplemente 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 pila de una función en comparación con su destinatario:
#include <stdio.h> 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; }
fuente
No creo que sea tan determinista. La matriz a parece "crecer" porque esa memoria debe asignarse de forma contigua. Sin embargo, dado que qys no están relacionados entre sí en absoluto, el compilador simplemente coloca cada uno de ellos en una ubicación de memoria libre arbitraria dentro de la pila, probablemente las que se ajustan mejor a 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 mayor de 12 bytes) para asignar una matriz de 3 enteros.
fuente
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 uso una invocación de compilador diferente. ... o el compilador muigt elige no usar una 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; }
fuente
La pila crece hacia abajo (en x86). Sin embargo, la pila se asigna en un bloque cuando se carga la función, y no tiene garantía de qué orden estarán los elementos en la pila.
En este caso, asignó espacio para dos entradas y una matriz de tres entradas 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 llega a 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 puntero de pila anterior y luego se restan 32 (¡o algo más!). Desde allí, puede ver cómo el código accede a ese bloque de memoria de 32 bytes y averiguar qué está haciendo su compilador. Al final de la función, puede ver cómo se restaura el puntero de la pila.
fuente
Depende de su sistema operativo y su compilador.
fuente
La pila crece hacia abajo. Entonces, f (g (h ())), la pila asignada para h comenzará en la dirección más baja, luego g y g serán más bajas 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 apuntadores a miembros de estructura declarados más tarde comparan mayores que apuntadores a miembros declarados anteriormente en la estructura, y los apuntadores a elementos de matriz con valores de subíndice más grandes comparan mayores que apuntadores a elementos de la misma matriz con valores de subíndice más bajos.
& a [0] <& a [1], siempre debe ser verdadero, independientemente de cómo se asigne 'a'
fuente
crece hacia abajo y esto se debe al estándar de orden de bytes little endian cuando se trata del conjunto de datos en la memoria.
Una forma de verlo es que la pila SÍ crece hacia arriba si mira la memoria desde 0 desde arriba y max desde abajo.
La razón por la que la pila crece hacia abajo es poder desreferenciar desde la perspectiva de la pila o del 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 la pila crece hacia abajo (dirección de mayor a menor), esto le permite tratar la pila como una memoria dinámica.
Esta es una de las razones por las que tantos lenguajes de programación y scripting utilizan una máquina virtual basada en pilas en lugar de una basada en registros.
fuente
The reason for the stack growing downward is to be able to dereference from the perspective of the stack or base pointer.
Muy buen razonamientoDepende de la arquitectura. Para verificar su propio sistema, use este código de GeeksForGeeks :
// C program to check whether stack grows // downward or upward. #include<stdio.h> void fun(int *main_local_addr) { int fun_local; if (main_local_addr < &fun_local) printf("Stack grows upward\n"); else printf("Stack grows downward\n"); } int main() { // fun's local variable int main_local; fun(&main_local); return 0; }
fuente