Ao compilar o código C e observar o assembly, tudo faz com que a pilha cresça para trás da seguinte maneira:
_main:
pushq %rbp
movl $5, -4(%rbp)
popq %rbp
ret
-4(%rbp)
- isso significa que o ponteiro base ou o ponteiro da pilha está realmente movendo-se pelos endereços de memória em vez de subir? Por que é que?
Mudei $5, -4(%rbp)
para $5, +4(%rbp)
, compilei e executei o código e não houve erros. Então, por que ainda precisamos retroceder na pilha de memória?
-4(%rbp)
não move o ponteiro da base e+4(%rbp)
não poderia ter funcionado.malloc
aumenta a pilha para trásmain
clobbers sua RBP. Isso é certamente possível. (E sim, a escrita4(%rbp)
pisaria no valor RBP salvo). Na verdade, esse principal nunca funcionamov %rsp, %rbp
, portanto o acesso à memória é relativo ao RBP do chamador , se é isso que o OP realmente testou !!! Se isso foi realmente copiado da saída do compilador, algumas instruções foram deixadas de fora!Respostas:
Sim, as
push
instruções diminuem o ponteiro da pilha e gravam na pilha, enquantopop
fazem o inverso, leem da pilha e aumentam o ponteiro da pilha.Isso é um pouco histórico, pois para máquinas com memória limitada, a pilha foi colocada alta e cresceu para baixo, enquanto a pilha foi colocada baixa e cresceu para cima. Há apenas uma lacuna de "memória livre" - entre a pilha e a pilha, e essa lacuna é compartilhada; qualquer um pode crescer na lacuna conforme necessário individualmente. Portanto, o programa fica sem memória quando a pilha e o heap colidem, deixando sem memória livre.
Se a pilha e a pilha crescem na mesma direção, há duas lacunas, e a pilha não pode realmente crescer na lacuna da pilha (o vice-versa também é problemático).
Originalmente, os processadores não tinham instruções de manipulação de pilha dedicadas. No entanto, como o suporte à pilha foi adicionado ao hardware, ele assumiu esse padrão de crescimento descendente e os processadores ainda seguem esse padrão hoje.
Alguém poderia argumentar que em uma máquina de 64 bits há espaço de endereço suficiente para permitir várias lacunas - e, como evidência, várias lacunas são necessariamente o caso quando um processo possui vários encadeamentos. Embora isso não seja motivação suficiente para mudar tudo, uma vez que, com sistemas com múltiplos hiatos, a direção do crescimento é indiscutivelmente arbitrária, então a tradição / compatibilidade diminui a escala.
Você teria que mudar as instruções de manuseamento da pilha CPU, a fim de mudar a direção da pilha, ou então desistir de uso das empurrando & popping instruções (por exemplo, dedicados
push
,pop
,call
,ret
, outros).Observe que a arquitetura do conjunto de instruções MIPS não possui
push
& dedicadopop
; portanto, é prático aumentar a pilha em qualquer direção - você ainda pode querer um layout de memória de uma lacuna para um processo de encadeamento único, mas pode aumentar a pilha para cima e para a pilha. para baixo. Se você fez isso, no entanto, algum código C varargs pode exigir ajuste na origem ou na passagem de parâmetro oculto.(De fato, como não há manipulação de pilha dedicada no MIPS, poderíamos usar pré ou pós incremento ou pré ou pós decremento para empurrar a pilha, desde que usássemos o reverso exato para saltar da pilha e também assumindo que o O sistema operacional respeita o modelo de uso de pilha escolhido. De fato, em alguns sistemas embarcados e em alguns sistemas educacionais, a pilha MIPS é aumentada.)
fonte
push
epop
na maioria das arquiteturas, mas também o mais importante de interrupção-assistência,call
,ret
, e tudo aquilo que tem cozido em interação com a pilha.gets()
, ou fizer umstrcpy()
que não seja incorporado, o retorno nessas funções da biblioteca usará o endereço de retorno substituído. Atualmente, com pilhas de crescimento descendente, é quando o chamador retorna.strcpy
), em um arco em que o endereço de retorno é mantido em um registro, a menos que precise ser derramado, não há acesso para clobber o retorno endereço.No seu sistema específico, a pilha começa do endereço de memória alta e "cresce" para baixo, para endereços de memória baixa. (o caso simétrico de baixo para alto também existe)
E como você mudou de -4 e +4 e foi executado, isso não significa que está correto. O layout da memória de um programa em execução é mais complexo e depende de muitos outros fatores que podem contribuir para o fato de você não ter travado instantaneamente neste programa extremamente simples.
fonte
O ponteiro da pilha aponta para o limite entre a memória da pilha alocada e a não alocada. Aumentá-lo para baixo significa que aponta para o início da primeira estrutura no espaço de pilha alocado, com outros itens alocados seguindo em endereços maiores. Ter ponteiros apontando para o início das estruturas alocadas é muito mais comum do que o contrário.
Atualmente, em muitos sistemas hoje em dia, existe um registro separado para os quadros de pilha que podem ser desenrolados de maneira confiável para descobrir a cadeia de chamadas, com o armazenamento variável local intercalado. A maneira como esse registro de quadro de pilha é configurado em algumas arquiteturas significa que ele acaba apontando para trás do armazenamento da variável local, em oposição ao ponteiro da pilha antes dele. Portanto, o uso desse registro de quadro de pilha requer indexação negativa.
Observe que os quadros de pilha e sua indexação são um aspecto lateral das linguagens de computador compiladas; portanto, é o gerador de código do compilador que precisa lidar com a "falta de naturalidade" em vez de com um programador de linguagem assembly ruim.
Portanto, embora existam boas razões históricas para a escolha de pilhas para crescer (e algumas delas são mantidas se você programar em linguagem assembly e não se incomodar em configurar um quadro de pilha adequado), elas se tornam menos visíveis.
fonte