我在前面总结过大端小端的基本概念,一句话说就是对于变量的二进制表示,如果低地址存放的是二进制位的高位,那么说明CPU是大端模式,反之则为小端模式。的确有了这句话的总结之后很容易记忆,我也自以为得其真谛,但是在接下来看题目,做笔试题的过程中发现对于大端小端的理解,还得结合其它的知识点来看。
首先就是栈生长的方向问题,栈生长的方向是由高地址向低地址生长,也就是说栈底的地址高一些,而堆则相反,下面结合一个具体的例子来说明,请看如下代码:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
int a=0x9abc0e0d;
int b=0x12345678;
char *p=(char*)&b;
char *q=p+2;
int x=*q;
int y=*(int *)q;
printf("&a=%x,&b=%x\n",&a,&b );
printf("x=%x,y=%x\n",x,y );
int *pc=(int*)malloc(sizeof(int));
int *pd=(int*)malloc(sizeof(int));
*pc=0x12345678;
*pd=0x9abc0e0d;
char *c=(char*)pc;
char *d=c+2;
int m=*d;
int n=*(int*)d;
printf("pc=%x,pd=%x\n",pc,pd );
printf("m=%x,n=%x\n",m,n );
return 0;
}
输出如下:
现在对结果进行分析,首先,很清楚地可以看到栈地址是从高往低生长的,一般而言,在debug模式下面,生长不一定连续,会有一些无用字符填充,但在release模式下一般是连续的。
而在我用gcc编译运行得出的结果中我们可以看到貌似是连续的。而堆的结果说明堆是由低到高生长的,而且是不连续的。
现在分析大端小端问题。对于a,b连续存放的这种情况,如果CPU是小端模式,正如我手头的x86机器,我们可以画出栈中数据如下:
低地址高地址
x表示q指向的一个字符,于是其值就为0x34,y表示q指向的整数,自然就会被解析为0xe0d1234.
其次是参数传递的问题,腾讯笔试中出现了这么一道题目,代码如下:
#include <cstdio>
int main(int argc, char **argv)
{
long long a = 1;
long long b = 2;
long long c = 3;
printf("%d,%d,%d", a, b, c);
}
求输出多少?
首先需要明确的是printf只是解释所指的内存单元里面有些啥,不会强制类型转换,其次long long 类型是64位的,但是我就是傻掉了,以为会强制转换,加上自己不知道long long 类型是个什么类型,所以只好认为会强制转换(虽然潜意识里面第一眼就觉得B:102是答案,最后还是没忍住,改了答案)。然后需要明确的是默认参数入栈顺序是从右至左。有了这个之后我们很容易就能获得栈中的状态:
低地址(栈顶) | %d%d%d |
1 | 0 |
2 | 0 |
3 | 0 |
高地址(栈底) |
因此可以很清楚地看到答案为102.下面给出另一位仁兄(文章链接)反汇编得出的代码:
// 从开始执行main函数体内的第一条赋值语句(long long a=1;)开始。
PUSH EBP // 保存ebp
MOV EBP,ESP // 设置ebp
SUB ESP,0F0 // 分配栈空间
PUSH EBX // 保护寄存器 ebx,esi,edi
PUSH ESI
PUSH EDI
LEA EDI,DWORD PTR SS:[EBP-F0] // 将分配的栈空间赋初值,初始化为CCCCCCCC
MOV ECX,3C // ecx用作下面rep stos指令的计数器
MOV EAX,CCCCCCCC
REP STOS DWORD PTR ES:[EDI]
MOV DWORD PTR SS:[EBP-C],1 // long long a=1;
MOV DWORD PTR SS:[EBP-8],0
MOV DWORD PTR SS:[EBP-1C],2 // long long b=2;
MOV DWORD PTR SS:[EBP-18],0
MOV DWORD PTR SS:[EBP-2C],3 // long long c=3;
MOV DWORD PTR SS:[EBP-28],0
MOV ESI,ESP
MOV EAX,DWORD PTR SS:[EBP-28] // 参数c入栈
PUSH EAX
MOV ECX,DWORD PTR SS:[EBP-2C]
PUSH ECX
MOV EDX,DWORD PTR SS:[EBP-18] // 参数b入栈
PUSH EDX
MOV EAX,DWORD PTR SS:[EBP-1C]
PUSH EAX
MOV ECX,DWORD PTR SS:[EBP-8] // 参数a入栈
PUSH ECX
MOV EDX,DWORD PTR SS:[EBP-C]
PUSH EDX
PUSH test4.0041573C // “%d,%d,%d”入栈(0041573C地址开始的位置存放“%d,%d,%d”)
CALL printf // 开始调用printf函数
ADD ESP,1C // 恢复栈地址