使用 C 进行试验 - 为什么我不能分配和使用 2GB 内存?
Experimenting with C - Why can't I allocate and use 2GB of memory?
我继续尝试使用 C。我有这个程序可以让您决定要占用多少 RAM。
char * eatRAM()
{
unsigned long long toEat;
unsigned long long i = 0;
float input;
char * pMemory = NULL;
int megaByte = 1048576;
puts("How much RAM do you want to eat? (in Mega Bytes)");
puts("NOTE: If you want to eat more RAM than you have available\nin your system, the program will crash");
printf("\n>> MB: ");
scanf("%f", &input);
toEat = (unsigned long long)(input * megaByte);
pMemory = malloc(toEat);
printf("\n\nEating in total: %llu Bytes\n", toEat);
puts("Check your task manager!\n");
if(pMemory != NULL)
{
printf("\n\nEating in total: %llu Bytes\n", toEat);
puts("Check your task manager!\n");
for(i; i < toEat; i++)
{
pMemory[i] = 'x';
}
}
else
{
puts("\nSeems like that amount of memory couldn't be allocated :( \n");
}
return pMemory;
}
更新问题:
事情是...如果我输入例如 1024MB 它有效,我可以在任务管理器中看到它正在使用 1GB 的 RAM。即使我输入 1500MB 它也有效..
但是如果我输入 2048MB 它会显示
Seems like that amount of memory couldn't be allocated :(
或者即使我输入 1756MB
记住我是 C 的新手,也许我遗漏了一些与 OS 如何允许我访问内存相关的重要内容,可能是什么?
如果剩余可用内存量小于您尝试分配的数量,分配将永远不会工作。
此外,就在这一行之后:
pMemory = (char *) malloc(toEat);
添加以下内容:
if (!pMemory){
printf("Can't allocate memory\n");
return NULL;
}
这样,您将不会收到 "segmentation fault" 相关消息,而是会看到一条 "Can't allocate memory" 消息,并且您的函数将 return NULL。
确保在调用 eatRam 函数的函数中进行类似的值检查,否则您将收到 "segmentation fault" 消息。并且,使用像 gdb 这样的调试器。
这是一个 OS 限制而不是 C 限制。
要寻址超过 4Gb 的系统范围,您需要 运行 64 位 OS 并且要寻址超过 4Gb 的单个进程,它必须构建为 64 位应用程序。 Win32 的每个进程内存限制为 2Gb。 5Gb 的物理 RAM 在很大程度上是无关紧要的,因为内存是虚拟化的。
除了 32 位和 64 位系统和应用程序的理论限制外,OS 可能仍会施加限制。例如,Windows 的不同版本和版本(家庭版、专业版、服务器版等)出于商业原因施加了特定限制。
您的案例的具体答案需要有关您的系统、工具链和应用的构建选项的信息。如果您使用 Windows 和 VC++,您需要考虑启用 /LARGEADDRESAWARE
option; it is not enabled by default in the 32 bit compiler, but Win32 has a 2Gb default limit in any case unless physical address extension。
我相信 Win64 上的 32 位进程 运行 可以寻址完整的 4Gb 32 位地址 space,但在那种情况下您肯定需要使用 /LARGEADDRESAWARE
进行构建。即使那样,也不是所有 space 都可用于堆,并且任何单个分配都必须是连续的,因此可能会受到先前分配和堆碎片的限制。
Windows 上的 32 位进程默认有一个 2 GB 的地址 space。完整 pow(2, 32) 地址的下半部分 space,前 2 GB 由操作系统使用。由于几乎没有人再使用 32 位 OS,当您 link 使用 /LARGEADDRESSAWARE 的程序时,您可以获得 4 GB。
2 GB VM space 需要由代码和数据共享。您的程序通常在 0x00400000 加载,您使用的任何操作系统 DLL(如 kernel32.dll 和 ntdll.dll)都具有高加载地址(超过 0x7F000000)。至少启动线程的堆栈和默认进程堆是在你的程序启动之前创建的运行,它们的地址通常是不可预测的。
您的程序将在大多数 OS 安装中受到收缩包装病毒攻击,您将注入提供 "services" 反恶意软件和云存储之类的 DLL。这些 DLL 的加载地址是不可预测的。还有任何你自己 link 编写并在程序启动时隐式加载的 DLL。很少有程序员会注意他们的首选基地址并将其保留为默认值 0x1000000。您可以从调试器的模块 window 中看到这些 DLL。这样的 DLL 通常有自己的 CRT,并且倾向于创建自己的堆。
您自己进行的分配,尤其是不会来自低碎片堆的非常大的分配,需要在现有代码和数据分配之间留下的漏洞中找到地址 space。如果您获得 1500 MB,那么您的 VM 就非常干净了。一般来说,超过 650 MB 就会遇到麻烦,当程序已经 运行 一段时间并且 VM 碎片 space 时,很快就会变小。分配失败几乎总是因为 OS 找不到足够大的空洞,而不是因为您没有足够的 VM。漏洞的总和可能比您失败的分配请求大得多。
这些细节正在迅速成为一个民间故事,几乎没有剩余的理由仍然以 x86 为目标。目标 x64 和地址 space 碎片在未来 20 年内不会成为问题,很难对 8 TB 的 VM 进行碎片化。有很大的发展空间。
所以很明显为什么你不能得到 2048 MB,你不能全部得到。从 SysInternals 的 VMMap utility, it shows you how the VM is carved up. And Mark Russinovich' blog post 中获得更深入的见解,本书提供了大量背景知识。
我继续尝试使用 C。我有这个程序可以让您决定要占用多少 RAM。
char * eatRAM()
{
unsigned long long toEat;
unsigned long long i = 0;
float input;
char * pMemory = NULL;
int megaByte = 1048576;
puts("How much RAM do you want to eat? (in Mega Bytes)");
puts("NOTE: If you want to eat more RAM than you have available\nin your system, the program will crash");
printf("\n>> MB: ");
scanf("%f", &input);
toEat = (unsigned long long)(input * megaByte);
pMemory = malloc(toEat);
printf("\n\nEating in total: %llu Bytes\n", toEat);
puts("Check your task manager!\n");
if(pMemory != NULL)
{
printf("\n\nEating in total: %llu Bytes\n", toEat);
puts("Check your task manager!\n");
for(i; i < toEat; i++)
{
pMemory[i] = 'x';
}
}
else
{
puts("\nSeems like that amount of memory couldn't be allocated :( \n");
}
return pMemory;
}
更新问题:
事情是...如果我输入例如 1024MB 它有效,我可以在任务管理器中看到它正在使用 1GB 的 RAM。即使我输入 1500MB 它也有效..
但是如果我输入 2048MB 它会显示
Seems like that amount of memory couldn't be allocated :(
或者即使我输入 1756MB
记住我是 C 的新手,也许我遗漏了一些与 OS 如何允许我访问内存相关的重要内容,可能是什么?
如果剩余可用内存量小于您尝试分配的数量,分配将永远不会工作。
此外,就在这一行之后:
pMemory = (char *) malloc(toEat);
添加以下内容:
if (!pMemory){
printf("Can't allocate memory\n");
return NULL;
}
这样,您将不会收到 "segmentation fault" 相关消息,而是会看到一条 "Can't allocate memory" 消息,并且您的函数将 return NULL。
确保在调用 eatRam 函数的函数中进行类似的值检查,否则您将收到 "segmentation fault" 消息。并且,使用像 gdb 这样的调试器。
这是一个 OS 限制而不是 C 限制。
要寻址超过 4Gb 的系统范围,您需要 运行 64 位 OS 并且要寻址超过 4Gb 的单个进程,它必须构建为 64 位应用程序。 Win32 的每个进程内存限制为 2Gb。 5Gb 的物理 RAM 在很大程度上是无关紧要的,因为内存是虚拟化的。
除了 32 位和 64 位系统和应用程序的理论限制外,OS 可能仍会施加限制。例如,Windows 的不同版本和版本(家庭版、专业版、服务器版等)出于商业原因施加了特定限制。
您的案例的具体答案需要有关您的系统、工具链和应用的构建选项的信息。如果您使用 Windows 和 VC++,您需要考虑启用 /LARGEADDRESAWARE
option; it is not enabled by default in the 32 bit compiler, but Win32 has a 2Gb default limit in any case unless physical address extension。
我相信 Win64 上的 32 位进程 运行 可以寻址完整的 4Gb 32 位地址 space,但在那种情况下您肯定需要使用 /LARGEADDRESAWARE
进行构建。即使那样,也不是所有 space 都可用于堆,并且任何单个分配都必须是连续的,因此可能会受到先前分配和堆碎片的限制。
Windows 上的 32 位进程默认有一个 2 GB 的地址 space。完整 pow(2, 32) 地址的下半部分 space,前 2 GB 由操作系统使用。由于几乎没有人再使用 32 位 OS,当您 link 使用 /LARGEADDRESSAWARE 的程序时,您可以获得 4 GB。
2 GB VM space 需要由代码和数据共享。您的程序通常在 0x00400000 加载,您使用的任何操作系统 DLL(如 kernel32.dll 和 ntdll.dll)都具有高加载地址(超过 0x7F000000)。至少启动线程的堆栈和默认进程堆是在你的程序启动之前创建的运行,它们的地址通常是不可预测的。
您的程序将在大多数 OS 安装中受到收缩包装病毒攻击,您将注入提供 "services" 反恶意软件和云存储之类的 DLL。这些 DLL 的加载地址是不可预测的。还有任何你自己 link 编写并在程序启动时隐式加载的 DLL。很少有程序员会注意他们的首选基地址并将其保留为默认值 0x1000000。您可以从调试器的模块 window 中看到这些 DLL。这样的 DLL 通常有自己的 CRT,并且倾向于创建自己的堆。
您自己进行的分配,尤其是不会来自低碎片堆的非常大的分配,需要在现有代码和数据分配之间留下的漏洞中找到地址 space。如果您获得 1500 MB,那么您的 VM 就非常干净了。一般来说,超过 650 MB 就会遇到麻烦,当程序已经 运行 一段时间并且 VM 碎片 space 时,很快就会变小。分配失败几乎总是因为 OS 找不到足够大的空洞,而不是因为您没有足够的 VM。漏洞的总和可能比您失败的分配请求大得多。
这些细节正在迅速成为一个民间故事,几乎没有剩余的理由仍然以 x86 为目标。目标 x64 和地址 space 碎片在未来 20 年内不会成为问题,很难对 8 TB 的 VM 进行碎片化。有很大的发展空间。
所以很明显为什么你不能得到 2048 MB,你不能全部得到。从 SysInternals 的 VMMap utility, it shows you how the VM is carved up. And Mark Russinovich' blog post 中获得更深入的见解,本书提供了大量背景知识。