參數入棧的順序以及棧/堆的生長順序


首先,棧的生長方向與操作系統無關,更多是由CPU決定的;其次,棧與堆的生長方向是剛好相反的。為什么棧與堆的生長方向會剛好相反?可參考鏈接的說法:https://www.quora.com/What-is-the-logical-explanation-for-stacks-typically-growing-downward-and-heaps-growing-upward?awc=15748_1571029880_d4826064801376537778f6a4f6f88d15&uiv=6&txtv=8&source=awin&medium=ad&campaign=uad_mkt_en_acq_us_awin&set=awin&pub_id=85386

Assume a fixed, linear address space shared by all of the sections. The text and data sections are of fixed size, while the heap and stack sections size dynamically.

If you think of this as a packing problem, you achieve optimal packing of the sections by placing text and data either at the lowest memory locations or at the highest memory locations, and then by having the heap and stack sections expand toward each other using remaining memory. In principle, this means that one of them expands toward higher memory addresses, while the other expands toward lower memory addresses.

You asked that we avoid historical facts when answering. In the absence of historical facts: it could be done either way; the stack could grow up, or it could grow down; the heap could grow up, or it could grow down.

Logically, though, the heap is an extension of data while the stack has both control and data functionality, and so there's a weak argument for placing the heap section adjacent to the data section, and the stack section at the other end of memory.

Now, to break the rules....
Many early processors loaded code and data sequentially to increasing addresses starting at address 0x0000 (sometimes including interrupt / trap vectors), as this was supported by hardware bootstrapping mechanisms from a switch panel, card reader, or paper tape. This placed the text and data sections in low memory.
Many early processors had hardware stacks, e.g. for subroutine calls, parameters and local variables, but not hardware heaps. The CPU designer made a choice which direction the stack would grow, and how big it could be (for example, a 6502 microprocessor had only 256 bytes of stack).
Many early processors allowed only unsigned offsets from a base register when referencing memory, and so, address calculations relative the the stack pointer were more efficient if the stack grew downward.

 

各個處理器對應的棧生長方向總結如下:https://stackoverflow.com/questions/664744/what-is-the-direction-of-stack-growth-in-most-modern-systems

The processors and their direction are:

  • x86: down.
  • SPARC: selectable. The standard ABI uses down.
  • PPC: down, I think.
  • System z: in a linked list, I kid you not (but still down, at least for zLinux).
  • ARM: selectable, but Thumb2 has compact encodings only for down (LDMIA = increment after, STMDB = decrement before).
  • 6502: down (but only 256 bytes).
  • RCA 1802A: any way you want, subject to SCRT implementation.
  • PDP11: down.
  • 8051: up.

注意,常見的CPU的棧都是往下生長的;8051單片機不同,它的棧是往上長的。

函數調用時,變量是如何入棧的呢?下面以MIPS向下生長的棧為例子說明:https://stackoverflow.com/questions/1677415/does-stack-grow-upward-or-downward

Let us consider that function 'fn1' calls 'fn2'. Now the stack frame as seen by 'fn2' is as follows:

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  |  // 注1
      |          +---------------------------------+ <-- 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)               |  // 注2
                 +---------------------------------+ 
                 | Arguments to functions called   |
                 | by fn2                          |
                 +---------------------------------+ <- Current SP after stack frame is allocated
 
注1/注2:direction of growth is oppsite to/same as direction of stack growth。direction of stack growth應理解為,既然棧是向下往低地址一側生長,即先出現的變量的地址應該更高。與棧生長方向相反,應理解為,先出現的變量占據低位地址。
主調函數傳遞的參數,在棧內生長方向與棧生長方向相反,即后出現的參數先入棧。
這是為了實現可變長參數的函數。
這樣一來,以下例子及其結果就容易理解了:

#include <stdio.h>

void func(int fmt_1, int fmt_2)
{
    int local_var_1;
    int local_var_2;

    printf("addr of fmt_1: %p\n", &fmt_1);
    printf("addr of fmt_2: %p\n", &fmt_2);
    printf("addr of local_var_1: %p\n", &local_var_1);
    printf("addr of local_var_2: %p\n", &local_var_2);
}

int main(void)
{
    int main_var_1;
    int main_var_2;

    printf("addr of main_var_1: %p\n", &main_var_1);
    printf("addr of main_var_2: %p\n", &main_var_2);

    func(1, 2);
}

結果:

image

1) 棧向下生長;

2) 自動變量入棧順序與棧生長方向相同,即先出現的變量占高位地址,因此main_var_1的地址比main_var_2的地址高。

3) main調用func時,傳遞的參數的入棧順序與棧生長方向相反,即先出現的變量的地址更低,也即參數從右到左入棧,因此fmt_2的地址比fmt_1的地址高。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM