在内存管理中,与栈对应是堆。对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方式是向下的,是向着内存地址减小的方向增长。在内存中,“堆”和“栈”共用全部的自由空间,只不过各自的起始地址和增长方向不同,它们之间并没有一个固定的界限,如果在运行时,“堆”和 “栈”增长到发生了相互覆盖时,称为“栈堆冲突”,系统肯定垮台。
在常见的x86中内存中栈的增长方向就是从高地址向低地址增长。
我们可以通过一些代码来判断栈的增长方向:
#include<stdio.h>
static int stack_dir;
static void find_stack_direction (void) {
static char *addr = NULL; /* address of first
`dummy', once known */
char dummy; /* to get stack address */
if (addr == NULL)
{ /* initial entry */
addr = &dummy;
find_stack_direction (); /* recurse once */
}
else /* second entry */
if (&dummy > addr)
stack_dir = 1; /* stack grew upward */
else
stack_dir = -1; /* stack grew downward */
}
int main(void)
{
find_stack_direction();
if(stack_dir==1)
puts("stack grew upward");
else
puts("stack grew downward");
return 0;
}
find_stack_direction函数使用函数递归的方法
第一次进入,由于addr为NULL,所以将字符变量dummy的地址赋值给静态变量addr
第二次进入,由于静态变量addr已赋了值,所以进入 "Second entry."
接着,将第二次进入的dummy地址和第一次进入的dummy地址相比较
如果值为正,则堆栈向高地址增长;否则,堆栈向低地址增长
大端/小端就是Big-Endian/Little-Endian问题
大端:高位字节存在高地址上,低位字节存在低地址上
小端:低位字节存在高地址上,高位字节存在低地址上
有两种常见的方法来判断是大端还是小端
方法一:使用指针
int x=1;方法二:使用联合
if(*(char*)&x==1)
printf("little-endian\n");
else
printf("big-endian\n");
union{
int i;
char c;
}x;
x.i=1;
if(x.c==1)
printf("little-endian\n");
else
printf("big-endian\n");
int x2=0;
char *p=(char*)&x;
int i=sizeof(int)-1;
while(i>=0){
x2|=*p<<(i*8);
i--;
p++;
}
return x2;
}
在小端的情况下,下面函数输出什么:
#include
int main()
{
}
首先说下大端小端
LE little-endian 最符合人的思维的字节序 地址低位存储值的低位 地址高位存储值的高位 怎么讲是最符合人的思维的字节序,是因为从人的第一观感来说 低位值小,就应该放在内存地址小的地方,也即内存地址低位 反之,高位值就应该放在内存地址大的地方,也即内存地址高位
BE big-endian 最直观的字节序 地址低位存储值的高位 地址高位存储值的低位 为什么说直观,不要考虑对应关系 只需要把内存地址从左到右按照由低到高的顺序写出 把值按照通常的高位到低位的顺序写出 两者对照,一个字节一个字节的填充进去
例子:在内存中双字0x01020304(DWORD)的存储方式 内存地址 4000 4001 4002 4003 LE 04 03 02 01 BE 01 02 03 04 考虑在32位机器上面,long long为64位所以上面a,b,c三个变量在地址中存放的格式分别是:高地址-----低地址
0000
0000
0000
下面就到了关键的地方了
首先需要知道的是c语言中函数的参数是从右往左压栈的,然后c语言中函数的参数是值拷贝,因此在printf中a b c并不是main函数中的a b c,而是其内部自己的变量,只不过其中的值是copy的参数中a b c的值
因此在printf函数内部栈中的实际情况应该是(注意压栈的顺序是c b a,还有压栈的时候是先进高地址还是先进低地址根据栈的增长方向,如果栈的增长方向是从高向低增长,那么先压高地址,一般都是高地址是栈底):
当printf函数进行输出的时候是根据"%d %d %d"来决定的,一个int类型就从栈中弹出4个字节,所以第一次会将0001弹出,第二次将0000弹出,第三次将0002弹出。
printf函数中可以允许前面%的个数与后面变量的参数个数不匹配,可以用上面的例子做实验:
#include
int main()
{
}
程序中只将long long 类型的a压栈,但是输出的时候使用两个%d,这样正好可以将8个字节的a输出,结果为:1