引言
这篇文章用作个人的学习记录,如有问题欢迎提出,如有不足欢迎指正。
本文将从数据宽度、存储方式等角度解释long long , int , short , char ,数组
这几个基本数据类型,结构体待学到了再补充。值得注意的是,本篇文章的所有内容基于x86体系而非x64!
long long 类型
数据宽度:
在32位计算机中,long long
的数据宽度为8字节。但是32位计算机寄存器大小和内存对齐大小都是4字节,做为局部变量时如何存储以及作为函数返回值时如何存储就是一个值得探究的问题。
存储方式:
局部变量为long long
看看反汇编结果就能知道它局部变量的存储方式了,下面是long long text1 = 1
的反汇编结果
xor eax,eax
mov dword ptr ss:[ebp-C],1
mov dword ptr ss:[ebp-8],eax
由此得出,long long被分配了8个字节的内存空间,而且高位在高地址,低位在低地址。
函数返回值为long long
一个寄存器只有4个字节,所以要存储8字节的数据需要两个寄存器,一般来说函数返回值是存在EAX
寄存器中的,另外用作拓展的寄存器是EDX
。
C源码
long long text()
{
long long text1 = 1;
return text1;
}
从反汇编的结果中看该函数返回值的存储方式
xor eax,eax
mov dword ptr ss:[ebp-C],1
mov dword ptr ss:[ebp-8],eax
mov eax,dword ptr ss:[ebp-C]
mov edx,dword ptr ss:[ebp-8]
从后两行指令中可以知道,低32位的值给了EAX
高32位的值给了EDX
。
int类型
int类型的存储方式和long long几乎一模一样,只是没有高32位的拓展而已,因为它存储进内存的数据宽度只有32位,即4字节。
**注意:**vs2022喜欢给每一个被申明定义的变量12个字节的空间(这里指函数内部的局部变量)。而有一些编译器只会本身大小的内存空间(不足4字节按4字节计算)。
short类型
short的数据宽度只有2个字节,但是当存储的时候计算机会为它分配4字节大小——本机宽度大小的空间。
C源码
short text()
{
short text1 = 1;
short text2 = 2;
int temp = text1;
text2 = temp;
return text1;
}
反汇编结果
mov eax,1
mov word ptr ss:[ebp-8],ax
mov eax,2
mov word ptr ss:[ebp-14],ax
movsx eax,word ptr ss:[ebp-8]
mov dword ptr ss:[ebp-20],eax
mov ax,word ptr ss:[ebp-20]
mov word ptr ss:[ebp-14],ax
mov ax,word ptr ss:[ebp-8]
从反汇编结果得知,计算机2字节将short类型存入内存,但是这一个short类型的变量会占据4个字节。在与int进行类型转换时会进行有符号拓展。作为函数返回值返回时只用到了AX
。
char类型
char类型的数据宽度只有1字节但是它的特性和short一摸一样,数据宽度不足本机宽度但是存储大小会以本机宽度计算。个人认为这种存储方式是用空间换效率。
数组
一维数组
int类型数组
int类型的数组,每一个数都是按照4字节大小存储的,而且是连续空间存储。
C源码
void text()
{
int a[5] = {1,2,3,4,5};
}
反汇编
mov dword ptr ss:[ebp-18],1
mov dword ptr ss:[ebp-14],2
mov dword ptr ss:[ebp-10],3
mov dword ptr ss:[ebp-C],4
mov dword ptr ss:[ebp-8],5
从反汇编结果就能看出,数组的存储在内存中是连续的,而且是从高位到低位的存储方式。
**补充:**假设我需要取a[2]
的值,那么在寻址时会有[ebp + 2*0x4 - 0x18]
。以此类推,可得到数组的寻址公式。
char类型数组
讲char类型的数组是为了补充一点很重要的知识——数组的存储大小和类型有关,而且数组的总存储大小是本机宽度的倍数。下面给出一个源代码示例就能体会到了。
C源码
void text()
{
char a[4] = { 1,2,3,4 };
char b[3] = { 1,2,3 };
char c = 1;
}
反汇编
mov byte ptr ss:[ebp-8],1
mov byte ptr ss:[ebp-7],2
mov byte ptr ss:[ebp-6],3
mov byte ptr ss:[ebp-5],4
mov byte ptr ss:[ebp-14],1
mov byte ptr ss:[ebp-13],2
mov byte ptr ss:[ebp-12],3
mov byte ptr ss:[ebp-1D],1
不同的编译器对空间的使用有不同,但是a[3]
和a[4]
这两个字符数组所占用的内存空间应该是相同的。而且由汇编代码可知,编译器会留足够的空间为存储数据。
二维及多维数组
对于数组的存储,无论是一维还是二维多维,在存储方式上没有任何区别,只在寻址时有区别。下面给一个简单的程序示例,以此类推可以得到数组在内存中的存储方式
C源码
void text()
{
char a[2][3] = { 1,2,3,4,5,6 };
}
反汇编
mov byte ptr ss:[ebp-C],1
mov byte ptr ss:[ebp-B],2
mov byte ptr ss:[ebp-A],3
mov byte ptr ss:[ebp-9],4
mov byte ptr ss:[ebp-8],5
mov byte ptr ss:[ebp-7],6
存储方式和一维数组一摸一样,寻址方式很容易计算,不多讲了。
数组越界的有趣用法
大家闲的没事干的时候可以玩玩,通过越界修改数组,从而修改到EIP
的值从而使某个函数调用调用另外一个函数
C源码
#include<stdio.h>
void fun()
{
printf("hello world!\n");
}
void text()
{
int a[4] = { 1,2,3,4 };
a[6] = (int)fun;
}
int main()
{
text();
return 0;
}
这个很有意思,通过。但是实际上有点问题,因为调用完fun函数后,EIP
变成了text函数ret
后的指令地址,就会导致无法返回到main函数中,这个问题现在不想思考解决了(懒鬼一个),之后有时间完善。
总结
- 感觉写这篇博客时状态很差,有很多问题。之后有时间会回来完善这篇博客,如有问题欢迎提出,有不足之处欢迎指出。
- 这里没有讨论浮点型的内存存储方式。实际上我们只需要记住一点,无论什么类型它都是以二进制方式存储就行了。float占32位,它的二进制解释是依据浮IEEE(电气和电子工程师协会)格式,double与之类似。
- 原谅懒狗,阿门~