字节序与堆栈增长方向之间的关系

9
处理器的大小端模式和堆栈增长方向之间是否存在关联?
例如,x86架构是小端模式,堆栈向下增长(即在最高地址开始,并随着每次push操作向低地址增长)。同样,在大端模式的SPARC体系结构中,堆栈从最低地址开始向上增长到更高地址。
几乎所有体系结构都遵循这种关系模式。我相信这种不成文的约定必须有原因。这可以从计算机体系结构或操作系统角度解释吗?这在处理器内部的微代码优化方面有所帮助吗?这对内核是否有所帮助?还是其他原因?
提前致谢!

2
注意:嵌入式PIC24系列处理器似乎是反例:“(堆栈指针)从低地址向高地址增长”,整数是小端序。 - chux - Reinstate Monica
SPARC上的堆栈向下增长(从高地址到低地址),而不是向上增长。 它是大端的,所以至少这一点是正确的。 - Chris Dodd
2个回答

6

堆栈增长方向与整数字节序无关。

在更广泛的整数(字)中,字节顺序和堆栈推送是添加还是减去堆栈指针之间没有任何联系。对于推送而言,存储数据是一个单一的操作。

将寄存器宽度的整数映射到内存中的字节使用与堆栈指针 inc/dec 逻辑不同的硬件; 我假设正常的设计将使用与非 push/pop 存储/加载相同的硬件,并将来自 push 的存储视为“字”的任何其他存储,而不是一次递增堆栈指针的奇怪的一个字节。

这种关系模式在几乎所有体系结构中都可以看到。

呃,其实并不是这样。许多现代 RISC ISA(例如 MIPS、PowerPC、ARM)具有可选择的字节序1,这与堆栈增长方向无关。

大多数现代系统中堆栈增长的方向是什么?表明,在大多数主流系统上,堆栈增长方向通常是向下的,这可能是按惯例或要求而定,包括在大端系统上。

根据该问答中的回答,Sparc上的主流操作系统/ABI选择向下增长堆栈。在Sparc上向上增长是一种选择,但像其他系统一样,向下增长是正常选择。

这可以从计算机架构或操作系统角度解释吗?

我们可以解释为什么向下是事实上的标准。不知道为什么SPARC费心使向上成为一个选项。在没有分页/虚拟内存的情况下,位于可用内存顶部的堆栈,静态代码/数据位于底部的固定地址显然是自然的。 https://softwareengineering.stackexchange.com/questions/137640/why-does-the-stack-grow-downward

这就是我们到达这里的方式。

在一些ISA上(如MIPS),堆栈增长方向完全由软件确定。硬件不会隐式/异步使用堆栈,并且没有推送/弹出指令使得一种方式更加高效。但通常的选择仍然是向下的。
其他的ISA(例如x86)会像异步中断将东西推到内核堆栈上,从而迫使一个方向。或者至少对一个方向有偏好,通过提供有效的推送/弹出指令(例如ARM Thumb以及x86的push/pop)。更不用说x86的call/ret指令,它们推送/弹出返回地址而不仅仅是写链接寄存器。
如果没有方向选择,在大多数ISA中一个固定的方向是向下的,但是@chux评论说PIC24具有向上的堆栈增长。

我非常确定这些都有大端和小端的例子,或者至少有可以配置为大端或小端的双端系统。


注1:一些特定的ARM或MIPS CPU硬连了它们的字节序,无法在运行时选择,因为对于嵌入式系统来说这基本上是一个无用的功能,也是硅片浪费。不确定现代POWER硬件是否也是如此;Godbolt编译器浏览器(https://godbolt.org/)有PowerPC64和PowerPC64le编译器,但这并不能告诉我们它们是否仍然相关。


-1

字节序是数据的字节顺序。

考虑十六进制值0x0A0B0C0D,可分为4个字节:0x0A,0x0B,0x0C,0x0D

就内存而言:

假设我们有一个内存地址o

对于大端(Big Endian)字节序,第一个字节始终在最前面:

memory[o] = 0x0A

memory[o+1] = 0x0B

memory[o+2] = 0x0C

memory[o+3] = 0x0D

对于小端(Little Endian)字节序,第一个字节放在最远处的偏移量上:

memory[o] = 0x0D

memory[o+1] = 0x0C

memory[o+2] = 0x0B

memory[o+3] = 0x0A

使用栈时,采用相反的字节序,以便在从栈中弹出数据时,重新采用正确的字节序。以下是另一个示例,但使用栈。

小端栈

unsigned data = 0x0A0B0C0D;

&data = 0x0D

&data+1 = 0x0C

&data+2 = 0x0B

&data+3 = 0x0A

当我们push data时,数据的小端字节序被存储为大端字节序,因为栈是LIFO/FILO,因此弹出保留了字节序,因此:

[ebp] == 0x0A

[ebp-1] == 0x0B

[ebp-2] == 0x0C

[ebp-3] == 0x0D

简而言之:栈是系统的相反字节序。


这完全是胡说八道。栈增长方向与整数字节序正交,而你的x86示例([ebp-0..3])是不对齐的,在地址[ebp-3]处存储了32位。如果我们假设传统的堆栈帧,那么这将重叠一个字节到保存的EBP值中。实际上,如果在堆栈帧设置的一部分之后立即执行push,则数据将保留在[ebp-4]=[esp]处。 - Peter Cordes
@PeterCordes 这并不是无意义的,沿着相反的字节序方向生长堆栈更有意义。如果您拥有一个大端机器,并使堆栈向下生长像x86一样,如果您解除引用堆栈的那部分以获取一个字节会发生什么?其次,x86示例只是一个视觉示例,你在无谓地抨击它。 - Nergal
发生的情况是你得到了你要求的字节。例如,如果你从 [esp] 加载一个字节,你就会得到你刚刚压入的整数的最高有效位(MSB)。我完全看不出你的观点。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接