参数入栈的顺序以及栈/堆的生长顺序

首先,栈的生长方向与操作系统无关,更多是由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的地址高。

posted on 2019-10-14 14:43  freshair_cn  阅读(1211)  评论(0编辑  收藏  举报

导航