堆栈本身涉及到从高地址向低地址增长和从低地址向高地址增长的问题,而大端字节序和小端字节序也包含了地址从高到低还是从低到高的定义,很容易引起混沌,但其实二者的定义范围完全是两个不同的方面。
堆栈
大家都知道所谓的堆从低到高增长,栈从高到低增长,不过首先明确一个概念,无论从高到低还是从低到高进行增长,一个指向某个变量的指针,指向的总是变量的低地址,从高到低和从低到高指的是连续定义多个变量,变量之间的增长方向,而非某个变量占用多个字节的空间,这个变量在这几个字节空间中是如何增长的。
void heapTest()
{
int * tmpC = new int(1);
int * tmpD = new int(1);
cout<<"tmpC----:"<<tmpC<<endl;
cout<<"tmpD----:"<<tmpD<<endl;
}
int main()
{
int a;
int b;
cout<<"a----:"<<&a<<endl;
cout<<"b----:"<<&b<<endl;
stackTest();
int* c = new int(1);
int *d = new int(1);
cout<<"c----:"<<c<<endl;
cout<<"d----:"<<d<<endl;
heapTest();
return 0;
}
这样定义栈上地址:a>b>tmpA>tmpB(注意这是正常情况下,如果使用了栈溢出保护会改变变量的入栈顺序)
堆上地址:tmpD>tmpC>d>c,这一情况一般不会改变。下面是我们的输出,由于存在栈溢出保护,main和stackTest中定义的变量增长方向没变(函数入栈顺序是无法改变的),但是对于main和stackTest函数来说其中包含的变量入栈顺序已经发生了改变(出现b>a和tmpB>tmpA),堆中地址增长顺序和预期完全一致。有些IDE默认是不开启栈溢出保护的,那么输出会和我所贴出的有些区别。
大端字节序和小端字节序
大小端字节序定义了某一个变量在其所占空间中生长的顺序,比如0x12345678这样一个十六进制数,十六进制数每个数位占四个二进制位,而内存地址单位是字节,每个字节包含8个二进制位的空间,因此可以存储两个十六进制位,0x12345678共占用四个字节的空间。大小端字节序到这里和堆栈的增长方向已经无关了,小端字节序是指0x12345678这一变量的高位字节(1开始)存储在地址的高地址处,低位字节(从8开始)存储在地址的低地址处。大端字节序相反,高位字节存储在低地址处,低位字节存储在高地址处。
可以通过如下方式测试自己的机器是大端字节序还是小端字节序,(测试数可以随便选,只要大小端存储方式有区分度就可以)
void byteorder()
{
union{
short value;
char union_bytes[sizeof(short)];
}test;
test.value = 0x0102;
if((test.union_bytes[0]==1)&&(test.union_bytes[1]==2))
printf("big endian\n");
else if((test.union_bytes[0]==2)&&(test.union_bytes[1]==1))
printf("little endian\n");
else
printf("unknown");
}
这里定义一个short变量存储0x0102,变量高位是0x01,低位是0x02,地址低位是union_bytes,高位是union_bytes+1,因此对于小端序来说union_bytes[0] = 0x02 == 2,union_bytes[1] = 0x01==1;大端序相反。
注:union是一种共用内存的结构,union_bytes和value具有共同的初始地址,union的空间大小由union内定义的占用空间最大的变量决定。
堆栈和大小端的区别
这里一定要区分开的是堆栈描述的增长顺序分别指变量、函数入栈的顺序和在堆中创建的顺序。对于栈来说晚定义的变量,后调用的函数晚入栈,因此地址低,对于堆,晚定义的变量地址高,而大小端是对单个变量在内存中存储方式的描述。