2009-11-04 28 views
71

tengo este pedazo de código en C:¿La pila crece hacia arriba o hacia abajo?

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 

Por lo tanto, veo que a-a[2], direcciones de memoria se incrementa en 4 bytes cada uno. Pero desde q a s, las direcciones de memoria disminuyen en 4 bytes.

Me pregunto 2 cosas:

  1. ¿Tiene pila crece hacia arriba o hacia abajo? (Me parece a mí en este caso)
  2. ¿Qué ocurre entre a[2] y q direcciones de memoria? ¿Por qué hay una gran diferencia de memoria allí? (20 bytes).

Nota: Esta no es la tarea. Tengo curiosidad sobre cómo funciona la pila. Gracias por cualquier ayuda.

+0

El orden es arbitrario. La brecha es probablemente para almacenar un resultado intermedio como & q o & s - mire el desmontaje y compruébelo usted mismo. –

+0

Acepto, lea el código de ensamblaje. Si hace este tipo de preguntas, es hora de aprender a leerlo. –

+0

este código no tiene nada que ver con la pila. – specializt

Respuesta

61

El comportamiento de la pila (creciendo o disminuyendo) depende de la interfaz binaria de la aplicación (ABI) y de cómo está organizada la pila de llamadas (también conocida como registro de activación).

A lo largo de su vida útil 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, consulte el enlace de la wiki this. 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 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 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 apunta en realidad con un solo puntero. La memoria debe ser contigua para una matriz. Entonces, aunque la pila crece hacia abajo, para las matrices la pila crece.

+2

Además, si desea verificar si la pila crece hacia arriba o hacia abajo. Declara una variable local en la función principal. Imprime la dirección de la variable. Llamar a otra función desde main. Declara una variable local en la función. Imprimir su dirección Según las direcciones impresas, podemos decir que la pila crece o disminuye. –

+0

gracias Ganesh, tengo una pequeña pregunta: en la figura que dibujó, en el tercer bloque, ¿se refería a "calleR guardado registro que se utiliza en CALLER" porque cuando f1 llama a f2, tenemos que almacenar la dirección f1 (que es la return addr para f2) y f1 (calleR) registra no registros f2 (callee). ¿Derecha? – CSawy

-1

No creo que sea así de determinista. 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.

+0

en caso afirmativo, por qué q, s, a no tiene memoria congestiva? (Ej: Dirección de q: 2293612 Dirección de s: 2293608 Dirección de un: 2293604) – root

+0

Veo una "brecha" entre s y una – root

+0

Debido s y no se asignaron juntos - los únicos indicadores que tienen que ser contiguos son los que están en la matriz. La otra memoria se puede asignar donde sea. – javanix

2

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í.

+1

De hecho, yo sé que el compilador puede reordenarlos, porque también es libre de no asignarlos en absoluto. Simplemente puede ponerlos en registros y no usar ningún espacio de pila en absoluto. – rmeador

+0

No puede ponerlos en los registros si hace referencia a sus direcciones. – florin

+0

buen punto, no lo había considerado. pero todavía es suficiente como una prueba de que el compilador puede reordenarlos, ya que sabemos que puede hacerlo al menos parte del tiempo :) – rmeador

4

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 elementos de matriz en elementos contiguos de la pila, siempre que tuviera la inteligencia necesaria 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 diversos motivos, incluidos, entre otros:

  • optimización.
  • alineación.
  • los caprichos de la persona la parte de gestión de pila del compilador.

Ver here para mi excelente tratado sobre la dirección pila :-)

En respuesta a sus preguntas específicas:

  1. empila crecer hacia arriba o hacia abajo?
    No importa para nada (en términos del estándar) pero, como usted lo ha pedido, puede crecer o en la memoria, dependiendo de la implementación.
  2. ¿Qué sucede entre las direcciones de memoria [2] yq? ¿Por qué hay una gran diferencia de memoria allí? (20 bytes)?
    No importa en absoluto (en términos de la norma). Ver arriba por posibles razones.
+0

Vi cómo la mayoría de las arquitecturas de CPU adoptan la forma de "crecer", ¿sabes? hay alguna ventaja de hacerlo? –

+0

No tengo idea, realmente. Es posible que alguien piense que el código va hacia arriba desde 0, por lo que el stack debería ir hacia abajo desde highmem, para minimizar la posibilidad de intersección. Pero algunas CPU específicamente comienzan a ejecutar código en ubicaciones distintas de cero, por lo que puede no ser el caso. Al igual que con la mayoría de las cosas, tal vez se hizo de esa manera simplemente porque era la primera forma en que alguien pensó hacerlo :-) – paxdiablo

+0

@lzprgmr: hay algunas pequeñas ventajas de tener cierto tipo de asignación de montón realizado en orden ascendente, y tiene históricamente ha sido común que la pila y el montón se ubiquen en los extremos opuestos de un espacio de direccionamiento común. Siempre que el uso combinado de static + heap + stack no exceda la memoria disponible, uno no tiene que preocuparse por la cantidad exacta de memoria utilizada por un programa. – supercat

13

La dirección es qué pilas crecen según 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 lo tanto, aunque la pila crezca, las matrices no lo harán (es decir, & matriz [n] siempre será < & matriz [n + 1]);

40

Esto es realmente dos preguntas. Una es acerca de qué manera the stack grows when one function calls another (cuando se asigna un nuevo fotograma), y el otro es sobre cómo se establecen las variables en el marco de una función en particular.

Tampoco se especifica en la norma C, pero las respuestas son un poco diferentes:

  • qué manera hace crecer la pila cuando se asigna un nuevo marco - si la función f() llama a la función g (), ¿el puntero de marco de f será mayor o menor que el puntero de marco de g? Esto puede ir de cualquier manera: depende del compilador y la arquitectura en particular (consulte "convención de llamadas"), pero siempre es coherente dentro de una plataforma dada (con algunas excepciones extrañas, consulte 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 se especifica y es completamente impredecible; el compilador es libre de organizar sus variables locales, sin embargo, le gusta obtener el resultado más eficiente.
+5

"siempre es coherente dentro de una plataforma determinada" - no se garantiza. He visto una plataforma sin memoria virtual, donde la pila se extendió de forma dinámica. Los nuevos bloques de pila estaban en efecto maltratados, lo que significa que uno "bajaba" un bloque de pila durante un tiempo, y de repente "de costado" hacia un bloque diferente. "De lado" podría significar una dirección mayor o menor, totalmente por la suerte del sorteo. –

+2

Para detalles adicionales en el ítem 2: un compilador puede decidir que una variable nunca necesita estar en la memoria (manteniéndola en un registro durante la vida de la variable), y/o si la vida útil de dos o más variables no lo hace Sin embargo, el compilador puede decidir usar la misma memoria para más de una variable. –

+2

Creo que S/390 (IBM zSeries) tiene un ABI donde los marcos de llamadas están vinculados en lugar de crecer en una pila. – ephemient

0

Mi pila parece extenderse hacia direcciones con números inferiores.

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 
0

El compilador es libre de asignar las variables locales (auto) en cualquier lugar en el marco de pila local, no se puede inferir de forma fiable la creciente sentido puramente pila de eso. Se puede inferir la pila crece dirección de la comparación de 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 con respecto a él es destinatario de la 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; 
} 
+4

Estoy bastante seguro de que es un comportamiento indefinido para restar punteros a diferentes objetos de pila; los punteros que no son parte del mismo objeto no son comparables. Obviamente, aunque no chocará con ninguna arquitectura "normal". –

0

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ó una 12 bytes adicionales después de la matriz, por lo que se ve así:

un [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 desea 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.

0

Depende de su sistema operativo y su compilador.

+0

No sé por qué mi respuesta fue rechazada. Realmente depende de tu sistema operativo y compilador. En algunos sistemas, la pila crece hacia abajo, pero en otros crece hacia arriba. Y en * algunos * sistemas, no existe una verdadera pila de marcos desplegables, sino que se simula con un área reservada de memoria o conjunto de registros. –

+3

Probablemente porque las afirmaciones de una sola oración no son buenas respuestas. –

1

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.

Así que el espacio de memoria asignado a la matriz es de 596 a 584. Pero como asignación de matriz es en continuación, el primer elemento de la matriz comienza desde la dirección 584 y no desde 596.

0

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 variables dentro de la pila tienen que seguir la especificación C,

http://c0x.coding-guidelines.com/6.5.8.html

1206 Si los objetos señalaron son miembros de un mismo objeto agregado, los punteros de estructurar los miembros declarados posteriormente comparar mayor que los punteros a los miembros declarados anteriormente en la estructura y los punteros a elementos de matriz con valores de subíndice más grandes se comparan más que punteros a elementos de la misma matriz con valores de subíndice más bajos.

& a [0] < & a [1], siempre debe ser verdad, independientemente de la forma 'a' se asigna

1

crece hacia abajo y esto es debido a la poca estándar byte más significativo cuando se trata de el conjunto de datos en la memoria.

Una forma en que podría 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 que muchos lenguajes de programación y scripts usan una máquina virtual basada en pila en lugar de una basada en registros.

+0

'La razón de que la pila crezca hacia abajo es poder desreferenciar desde la perspectiva de la pila o del puntero base. Muy buen razonamiento – user3405291

Cuestiones relacionadas