在论坛中,人们常说的64位版本的方案消耗的内存和堆栈用量较大。所以说,他们通常认为,数据的大小已成为两次大。但是,这种说法是毫无根据的,因为大多数类型在C / C + +语言(CHAR,短,整数,浮点)的大小保持在64位系统相同。当然,例如,指针的大小增加,但远不是所有在程序中的数据的指针组成。之所以方案消耗的内存量增加的原因较为复杂。我决定这个问题的详细调查。
在这篇文章中,我将谈论堆栈和未来,我打算讨论的内存分配和二进制代码的大小。我想也马上要注意的文章,涵盖的语言C / C和微软的Visual Studio开发环境。
直到最近,我相信一个64位的应用程序代码不能在32位代码比较消耗堆栈更快的两倍。依托这一假设,在我的文章,我建议增加程序堆栈中的情况下两次。但现在我必须探索一种不愉快的事情:堆栈的消费量可能增长远远超过两次。我感到很惊讶,因为我认为最坏的情况下两次堆栈增长。晚了一点,我毫无根据的希望之所以会成为明确。但现在让我们来看看参数是如何在64位程序通过调用函数时。
开发x86 - 64架构的调用约定时,他们决定把各种版本的函数调用结束。在Win32there广泛的调用约定:在Win64上STDCALL,CDECL,fastcall,thiscall等,只有一个"本地调用约定??。像__cdecl修饰符被编译器忽略。我想大家都同意这样的调用约定AX是高尚的。
x86 - 64平台上调用约定类似于现有的x86 fastcall公约。前四个整数参数(从左至右)在x64约定,通过在64位,为此专门寄存器选择:RCX的:第一整数参数RDX的:第二整数参数奥迪R8:第三整数参数R9:第四整数参数
其余的整数参数通过堆栈传递。指针??代码>??被认为是一个整数参数,所以它总是放置到RCX寄存器。如果传递浮点值,其中前4位是通过在寄存器XMM0 XMM3,而未来通过堆栈传递。
依托这方面的资料,我得出的结论是一个64位程序可以在许多情况下保存不像一个32位的堆栈内存。对于如果参数是通过寄存器的功能代码是短暂的,没有必要保存在内存(堆栈)的论点,然后被消耗栈内存的大小必须要小。但并非如此。
虽然可以在寄存器中传递参数,编译器将所有的堆栈空间,为他们减少RSP寄存器(堆栈指针)的值相同的储备。每个函数都必须保留在堆栈的至少32个字节(对应寄存器RCX,RDX,R8,R9的4个64位值)。这在栈空间允许轻松地传递到堆栈中的功能寄存器的内容保存。被调用的函数不需要输入参数通过寄存器保存到堆栈,但保留在栈空间允许这样做如果有必要的。如果超过4个整数参数传递,一些额外的空间,必须保留在堆栈中。
让我们考虑一个例子。有些功能两个整型参数传递到一个子功能。编译器的参数的地方??值到寄存器RCX和RDX,同时从RSP寄存器中减去32字节。被调用函数的地址通过寄存器RCX和RDX的参数。如果该函数的代码需要某种目的而这些寄存器,它可以复制到堆栈中预留空间的大小32个字节的内容。
描述的功能,导致一个堆栈的消耗速度显著增长。即使该函数没有参数,将32个字节"有点过??堆栈反正,他们不会被用来无论如何然后。我没有找到这样的浪费机制的原因。有一些关于统一和简化调试解释,但这种信息过于空泛。
注意另一件事。堆栈指针可吸入悬浮粒子,必须在16字节边界对齐之前,下一个函数调用。因此,在64位代码调用不带参数的函数时,正在使用的堆栈的总大小是:8(返回地址)8(对齐)32(参数预留空间)= 48个字节!
让我们来看看它可能会导致在实践中。这里,进一步,我会为我的实验中使用的Visual Studio 2010。让我们这样一个递归函数:void StackUse(size_t *depth)
{
volatile size_t *ptr = 0;
if (depth != NULL)
ptr = depth;
cout << *ptr << endl;
(*ptr)++;
StackUse(depth);
(*ptr)--;
}
功能是故意混淆,以防止它转变成"什么?优化一个位??这里最主要的是:函数具有指针类型的参数和局部变量,指针类型。让我们来看看功能在32位和64位版本,以及如何可以递归调用时堆栈的大小是1兆字节(默认大小)多次使用的堆栈是多少。
推出32位:最后显示的数字(堆栈深度) - 51331
调用此函数时,编译器使用20个字节。
推出64位:最后显示的数字 - 21288
调用此函数时,编译器使用48个字节。
因此,64位版本的StackUse功能是超过两次超过32位的贪婪。
请注意,数据对齐规则的变化,也可能会影响消耗堆栈的大小。假设该函数采用以下结构作为一个参数:{C}
大小从12字节到24字节时,在64位版本重新编译??代码> S??结构增加,由于对齐规则的改变和变化??代码> B? ??成员的大小。结构是值传递到函数。 ,相应,结构也将需要两倍多的堆栈内存。
这一切如此糟糕?不要忘记,64位编译器可以处理比32位的寄存器。让复杂的实验功能的代码:void StackUse(size_t *depth, char a, int b)
{
volatile size_t *ptr = 0;
int c = 1;
int d = -1;
for (int i = 0; i < b; i++)
for (char j = 0; j < a; j++)
for (char k = 0; k < 5; k++)
if (*depth > 10 && k > 2)
{
c += j * k - i;
d -= (i - j) * c;
}
if (depth != NULL)
ptr = depth;
cout << c << " " << d << " " << *ptr << endl;
(*ptr)++;
StackUse(depth, a, b);
(*ptr)--;
}
这里是它的执行结果:
推出32位:最后显示的号码 - 16060
编译器使用64个字节的这个时候,当调用这个函数。
推出64位:最后显示的数字 - 21310
编译器仍然使用48个字节时,调用这个函数。
64位编译器使用此范例的额外的寄存器,并建立一个更高效的代码,使我们能够减少被消耗的堆栈内存量!结论人们无法预见一个程序的64位版本将在比较消耗到32位的堆栈内存有多少。它可能会减少(不太可能)等等。对于64位程序,你应该保留堆栈数量增加2-3倍。 3次比较好 - 只是为了安心。要做到这一点,看到的参数堆栈保留大小(/堆栈:储备开关)在项目设置。默认情况下,栈的大小是1兆字节。你不应该担心,如果您的64位程序消耗更多的堆栈内存。有更多的物理内存在64位系统。与64位系统上的大小2兆字节,8 GB内存的堆栈需要1兆字节的栈与2 GB的32位系统的内存少于%。