Eu tenho este trecho de código em 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);
O resultado é:
Address of a: 2293584
Address of a[1]: 2293588
Address of a[2]: 2293592
Address of q: 2293612
Address of s: 2293608
Portanto, vejo que de a
para a[2]
, os endereços de memória aumentam em 4 bytes cada. Mas de q
para s
, os endereços de memória diminuem em 4 bytes.
Eu me pergunto 2 coisas:
- A pilha aumenta ou diminui? (Parece que ambos neste caso)
- O que acontece entre os endereços de memória
a[2]
eq
? Por que há uma grande diferença de memória aí? (20 bytes).
Observação: esta não é uma questão de dever de casa. Estou curioso para saber como funciona a pilha. Obrigado por qualquer ajuda.
Respostas:
O comportamento da pilha (crescendo ou diminuindo) depende da interface binária do aplicativo (ABI) e de como a pilha de chamadas (também conhecida como registro de ativação) está organizada.
Ao longo de sua vida, um programa é obrigado a se comunicar com outros programas, como o sistema operacional. ABI determina como um programa pode se comunicar com outro programa.
A pilha para diferentes arquiteturas pode crescer de qualquer maneira, mas para uma arquitetura será consistente. Por favor, verifique este link do wiki. Porém, o crescimento da pilha é decidido pela ABI dessa arquitetura.
Por exemplo, se você pegar o MIPS ABI, a pilha de chamadas é definida conforme abaixo.
Vamos considerar que a função 'fn1' chama 'fn2'. Agora, o frame da pilha visto por 'fn2' é o seguinte:
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
Agora você pode ver que a pilha cresce para baixo. Portanto, se as variáveis são alocadas ao quadro local da função, os endereços das variáveis na verdade crescem para baixo. O compilador pode decidir sobre a ordem das variáveis para alocação de memória. (No seu caso, pode ser 'q' ou 's' que é a primeira alocação de memória de pilha. Mas, geralmente o compilador faz a alocação de memória de pilha de acordo com a ordem de declaração das variáveis).
Mas no caso dos arrays, a alocação possui apenas um único ponteiro e a memória que precisa ser alocada será na verdade apontada por um único ponteiro. A memória precisa ser contígua para uma matriz. Portanto, embora a pilha cresça para baixo, para os arrays, a pilha aumenta.
fonte
Na verdade, são duas perguntas. Uma é sobre a maneira como a pilha cresce quando uma função chama outra (quando um novo quadro é alocado) e a outra é sobre como as variáveis são dispostas em um quadro de função específica.
Nenhum dos dois é especificado pelo padrão C, mas as respostas são um pouco diferentes:
f
o ponteiro do quadro será maior ou menor queg
o ponteiro do quadro? Isso pode acontecer de qualquer maneira - depende do compilador e da arquitetura em particular (pesquise "convenção de chamada"), mas é sempre consistente em uma determinada plataforma (com algumas exceções bizarras, consulte os comentários). Para baixo é mais comum; é o caso em x86, PowerPC, MIPS, SPARC, EE e as SPUs de célula.fonte
A direção que as pilhas crescem é específica da arquitetura. Dito isso, meu entendimento é que apenas algumas poucas arquiteturas de hardware têm pilhas que crescem.
A direção em que uma pilha cresce é independente do layout de um objeto individual. Portanto, embora a pilha possa diminuir, os arrays não irão (ou seja, & array [n] será sempre <& array [n + 1]);
fonte
Não há nada no padrão que determine como as coisas são organizadas na pilha. Na verdade, você poderia construir um compilador em conformidade que não armazenasse elementos da matriz em elementos contíguos na pilha, desde que tivesse a inteligência para ainda fazer aritmética de elementos da matriz corretamente (para que soubesse, por exemplo, que um 1 era 1K de distância de um [0] e poderia ajustar para isso).
A razão pela qual você pode estar obtendo resultados diferentes é porque, embora a pilha possa diminuir para adicionar "objetos" a ela, a matriz é um único "objeto" e pode ter elementos de matriz ascendentes na ordem oposta. Mas não é seguro confiar nesse comportamento, já que a direção pode mudar e as variáveis podem ser trocadas por uma variedade de razões, incluindo, mas não se limitando a:
Veja aqui o meu excelente tratado sobre a direção da pilha :-)
Em resposta às suas perguntas específicas:
Não importa em absoluto (em termos de padrão), mas, desde que você perguntou, pode aumentar ou diminuir na memória, dependendo da implementação.
Não importa de forma alguma (em termos de padrão). Veja acima para possíveis razões.
fonte
Em um x86, a "alocação" de memória de um frame de pilha consiste simplesmente em subtrair o número necessário de bytes do ponteiro de pilha (acredito que outras arquiteturas são semelhantes). Nesse sentido, acho que a pilha cresce "para baixo", na medida em que os endereços ficam progressivamente menores conforme você chama mais profundamente na pilha (mas sempre imagino a memória começando com 0 no canto superior esquerdo e ficando maiores endereços conforme você se move para a direita e empacotar, então na minha imagem mental a pilha cresce ...). A ordem das variáveis declaradas pode não ter qualquer relação com seus endereços - acredito que o padrão permite que o compilador as reordene, desde que não cause efeitos colaterais (alguém, por favor, me corrija se eu estiver errado) . Eles'
A lacuna em torno da matriz pode ser algum tipo de preenchimento, mas é misteriosa para mim.
fonte
Em primeiro lugar, são 8 bytes de espaço não utilizado na memória (não é 12, lembre-se de que a pilha cresce para baixo, então o espaço que não é alocado é de 604 para 597). e porque?. Porque cada tipo de dado ocupa espaço na memória a partir do endereço divisível por seu tamanho. No nosso caso, o array de 3 inteiros ocupa 12 bytes de espaço de memória e 604 não é divisível por 12. Portanto, ele deixa espaços vazios até encontrar um endereço de memória que é divisível por 12, é 596.
Portanto, o espaço de memória alocado para o array é de 596 a 584. Mas como a alocação do array está em continuação, o primeiro elemento do array começa no endereço 584 e não no 596.
fonte
O compilador é livre para alocar variáveis locais (automáticas) em qualquer lugar no frame da pilha local, você não pode inferir com segurança a direção de crescimento da pilha puramente disso. Você pode inferir a direção de crescimento da pilha comparando os endereços de frames de pilha aninhados, ou seja, comparando o endereço de uma variável local dentro do frame de pilha de uma função em comparação com seu receptor:
#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; }
fonte
Eu não acho que seja determinístico assim. A matriz a parece "crescer" porque essa memória deve ser alocada de forma contígua. No entanto, uma vez que q e s não estão relacionados entre si, o compilador apenas coloca cada um deles em um local de memória livre arbitrário dentro da pilha, provavelmente aquele que se encaixa melhor em um tamanho inteiro.
O que aconteceu entre a [2] eq é que o espaço ao redor da localização de q não era grande o suficiente (ou seja, não era maior que 12 bytes) para alocar um array de 3 inteiros.
fonte
Minha pilha parece se estender para endereços com números menores.
Pode ser diferente em outro computador, ou mesmo no meu próprio computador, se eu usar uma chamada de compilador diferente. ... ou o compilador deve escolher não usar pilha (tudo embutido (funções e variáveis se eu não pegar o endereço delas)).
$ 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; }
fonte
A pilha diminui (em x86). No entanto, a pilha é alocada em um bloco quando a função é carregada e você não tem uma garantia da ordem em que os itens estarão na pilha.
Nesse caso, ele alocou espaço para dois ints e uma matriz de três int na pilha. Ele também alocou 12 bytes adicionais após a matriz, então tem a seguinte aparência:
a [12 bytes]
preenchimento (?) [12 bytes]
s [4 bytes]
q [4 bytes]
Por alguma razão, seu compilador decidiu que precisava alocar 32 bytes para esta função, e possivelmente mais. Isso é opaco para você como um programador C, você não sabe por quê.
Se você quiser saber o porquê, compile o código para a linguagem assembly, acredito que seja -S no gcc e / S no compilador C da MS. Se você olhar as instruções de abertura dessa função, verá o ponteiro da pilha antigo sendo salvo e, em seguida, 32 (ou outra coisa!) Sendo subtraído dele. A partir daí, você pode ver como o código acessa aquele bloco de memória de 32 bytes e descobrir o que seu compilador está fazendo. No final da função, você pode ver o ponteiro da pilha sendo restaurado.
fonte
Depende do seu sistema operacional e do seu compilador.
fonte
A pilha diminui. Portanto, f (g (h ())), a pilha alocada para h começará no endereço menor que g e g's serão menores que f's. Mas as variáveis dentro da pilha devem seguir a especificação C,
http://c0x.coding-guidelines.com/6.5.8.html
1206 Se os objetos apontados são membros do mesmo objeto agregado, ponteiros para membros da estrutura declarados posteriormente são comparados com ponteiros para membros declarados anteriormente na estrutura e ponteiros para elementos da matriz com valores de subscrito maiores são comparados com ponteiros para elementos do mesmo array com valores de subscrito mais baixos.
& a [0] <& a [1], deve sempre ser verdadeiro, independentemente de como 'a' é alocado
fonte
cresce para baixo e isso é devido ao padrão de ordem de bytes little endian quando se trata do conjunto de dados na memória.
Uma maneira de ver isso é que a pilha CRESCE para cima se você olhar para a memória de 0 do topo e máximo de baixo.
A razão para a pilha crescer para baixo é ser capaz de remover a referência da perspectiva da pilha ou do ponteiro da base.
Lembre-se de que a desreferenciação de qualquer tipo aumenta do endereço mais baixo para o mais alto. Como a pilha cresce para baixo (endereço do mais alto para o mais baixo), isso permite que você trate a pilha como memória dinâmica.
Esse é um dos motivos pelos quais tantas linguagens de programação e script usam uma máquina virtual baseada em pilha em vez de uma baseada em registro.
fonte
The reason for the stack growing downward is to be able to dereference from the perspective of the stack or base pointer.
Muito bom raciocínioDepende da arquitetura. Para verificar seu próprio 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; }
fonte