不定参数函数牵扯出来的栈的生长方向和大端小端模式

来源:互联网 发布:陕西广电网络是国企吗 编辑:程序博客网 时间:2024/05/17 02:20

主题:CPU的架构决定了大端小端模式和栈的生长方向。
前天参加深信服的专场招聘,问到不定参数函数的实现,当时只记得参数入参是从右到左,但是在栈中的存放地址大小当时就想不起来了,现在专门好好整理一下。
首先参数从右到左压栈的方式,这是C语言决定的,比如说Pascal就是从左到右压栈,所以Pascal不支持不定参数函数。比如:

printf("%s%d%d\n", s, a, b);

格式字符串是确定存在的,后面的变量都是不确定的或者说不知道有多少个。如果格式字符串最后一个入栈,位于栈顶,第一个出栈然后解析格式字符串得到不定参的个数,挨个出栈,这样就很方便。Pascal语言不支持可变长参数,而C语言支持这种特色,正是这个原因使得C语言函数参数入栈顺序为从右至左。C方式参数入栈顺序(从右至左)的好处就是可以动态变化参数个数。通过栈分析可知,自左向右的入栈方式,最前面的参数被压在栈底。除非知道参数个数,否则是无法通过栈指针的相对位移求得最左边的参数。这样就变成了左边参数的个数不确定,正好和动态参数个数的方向相反。
这里其实还涉及到C语言中调用约定所采用的方式:
这里写图片描述
这里第一个得到格式字符串的地址之后,后面的不定参数的地址是比这个大还是小了?这就是栈的生长方式决定的,51的栈是向高地址增长,INTEL的8031、8032、8048、8051系列使用向高地址增长的栈。但同样是INTEL,在x86系列中全部使用向低地址增长的栈。其他公司的CPU中除ARM的结构提供向高地址增长的栈选项外,多数都是使用向低地址增长的栈。顺便提一下,x86使用的向高地址增长的堆,符合我们的使用习惯。
也就是说x86中,栈底是高地址,栈顶是低地址,先入栈到高地址的栈底,然后到低地址的栈顶。所以不定参数的地址比格式字符串的地址大。这样设计可以使得堆和栈能够充分利用空闲的地址空间。如果栈向上涨的话,我们就必须得指定栈和堆的一个严格分界线,但这个分界线怎么确定呢?平均分?但是有的程序使用的堆空间比较多,而有的程序使用的栈空间比较多。所以就可能出现这种情况:一个程序因为栈溢出而崩溃的时候,其实它还有大量闲置的堆空间呢,但是我们却无法使用这些闲置的堆空间。所以呢,最好的办法就是让堆和栈一个向上涨,一个向下涨,这样它们就可以最大程度地共用这块剩余的地址空间,达到利用率的最大化!

所谓的大端模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;
所谓的小端模式,是指数据的低位保存在内存的低地址中,而数据的高位保存在内存的高地址中。
所以数据之间的地址大小看栈的生长方式,数据内的地址大小看大小端模式。在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如果将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
这里引出主机字节序和网络字节序,主机字节序是由CPU决定的,也就是大小端。我们常用的x86结构是小端模式。网络字节序是固定的,就是大端模式,网络字节序这是TCP/IP协议中定义好的一种数据格式,它是与你的机器的CPU,操作系统什么的无关的,这样可以保证数据在网络中传输时,不管怎么样都能正确的解释!所以就有了网络字节序到本机字节序和本机字节序到网络字节序的转换:
htons:把unsigned short类型从主机序转换到网络序
htonl:把unsigned long类型从主机序转换到网络序
ntohs:把unsigned short类型从网络序转换到主机序
ntohl:把unsigned long类型从网络序转换到主机序
反正网络字节序是固定的,这样只要两边主机遵循大端模式发送出去,以大端模式来接收,就不会有问题。

0 0
原创粉丝点击