在 C++ 中实现埃拉托色尼筛法时,内存错误低于预期
Memory error at lower limit than expected when implementing Sieve of Eratosthenes in C++
我的问题与描述的问题有关 here。我已经编写了埃拉托色尼筛法的 C++ 实现,如果我将目标值设置得太高,它会发生内存溢出。正如该问题中所建议的,我可以通过使用布尔值 <vector>
而不是普通数组来解决问题。
但是,我在 n = 1 200 000
附近 遇到的内存溢出值比预期低得多。上面链接的线程中的讨论表明,普通的 C++ 布尔数组为每个条目使用一个字节,因此使用 2 GB 的 RAM,我希望能够到达 n = 2 000 000 000
顺序的某个位置。 为什么实际内存限制这么小很多?
为什么使用 <vector>
将布尔值编码为位而不是字节,产生 超过 八倍的可计算极限?
这是我的代码的一个工作示例,n
设置为一个小值。
#include <iostream>
#include <cmath>
#include <vector>
using namespace std;
int main() {
// Count and sum of primes below target
const int target = 100000;
// Code I want to use:
bool is_idx_prime[target];
for (unsigned int i = 0; i < target; i++) {
// initialize by assuming prime
is_idx_prime[i] = true;
}
// But doesn't work for target larger than ~1200000
// Have to use this instead
// vector <bool> is_idx_prime(target, true);
for (unsigned int i = 2; i < sqrt(target); i++) {
// All multiples of i * i are nonprime
// If i itself is nonprime, no need to check
if (is_idx_prime[i]) {
for (int j = i; i * j < target; j++) {
is_idx_prime[i * j] = 0;
}
}
}
// 0 and 1 are nonprime by definition
is_idx_prime[0] = 0; is_idx_prime[1] = 0;
unsigned long long int total = 0;
unsigned int count = 0;
for (int i = 0; i < target; i++) {
// cout << "\n" << i << ": " << is_idx_prime[i];
if (is_idx_prime[i]) {
total += i;
count++;
}
}
cout << "\nCount: " << count;
cout << "\nTotal: " << total;
return 0;
}
产出
Count: 9592
Total: 454396537
C:\Users\[...].exe (process 1004) exited with code 0.
Press any key to close this window . . .
或者,更改 n = 1 200 000
会得到
C:\Users\[...].exe (process 3144) exited with code -1073741571.
Press any key to close this window . . .
我在 Windows 上使用默认设置的 Microsoft Visual Studio 解释器。
将评论转为完整答案:
您的操作系统在内存中保留了一个特殊的区域来表示您程序的调用堆栈。每个函数调用都会将一个新的堆栈帧压入堆栈。如果函数returns,栈帧从栈中移除。堆栈帧包括函数参数的内存和函数的局部变量。剩余的内存称为堆。在堆上,可以进行任意内存分配,而堆栈的结构由程序的控制流控制。为堆栈保留有限数量的内存,当它变满时(例如,由于嵌套函数调用太多或本地对象太大),您会发生堆栈溢出。为此,大对象应该分配在堆上。
关于 stack/heap 的一般参考:Link, Link
要在 C++ 中的堆上分配内存,您可以:
使用vector<bool> is_idx_prime(target);
,它在内部执行堆分配并在向量超出范围时为您释放内存。这是最方便的方法。
使用智能指针来管理分配:auto is_idx_prime = std::make_unique<bool[]>(target);
这也会在数组超出范围时自动释放内存。
手动分配内存。我提到这个只是为了教育目的。正如 Paul 在评论中提到的,手动分配内存通常是不可取的,因为您必须再次手动释放内存。如果你有一个有很多内存分配的大程序,你不可避免地会忘记释放一些分配,造成内存泄漏。当你有一个长运行的程序时,比如系统服务,重复的内存泄漏最终会填满整个内存(根据个人经验,这在实践中绝对会发生)。但理论上,如果您想进行手动内存分配,您可以使用 bool *is_idx_prime = new bool[target];
然后再使用 delete [] is_idx_prime
.
重新分配
我的问题与描述的问题有关 here。我已经编写了埃拉托色尼筛法的 C++ 实现,如果我将目标值设置得太高,它会发生内存溢出。正如该问题中所建议的,我可以通过使用布尔值 <vector>
而不是普通数组来解决问题。
但是,我在 n = 1 200 000
附近 遇到的内存溢出值比预期低得多。上面链接的线程中的讨论表明,普通的 C++ 布尔数组为每个条目使用一个字节,因此使用 2 GB 的 RAM,我希望能够到达 n = 2 000 000 000
顺序的某个位置。 为什么实际内存限制这么小很多?
为什么使用 <vector>
将布尔值编码为位而不是字节,产生 超过 八倍的可计算极限?
这是我的代码的一个工作示例,n
设置为一个小值。
#include <iostream>
#include <cmath>
#include <vector>
using namespace std;
int main() {
// Count and sum of primes below target
const int target = 100000;
// Code I want to use:
bool is_idx_prime[target];
for (unsigned int i = 0; i < target; i++) {
// initialize by assuming prime
is_idx_prime[i] = true;
}
// But doesn't work for target larger than ~1200000
// Have to use this instead
// vector <bool> is_idx_prime(target, true);
for (unsigned int i = 2; i < sqrt(target); i++) {
// All multiples of i * i are nonprime
// If i itself is nonprime, no need to check
if (is_idx_prime[i]) {
for (int j = i; i * j < target; j++) {
is_idx_prime[i * j] = 0;
}
}
}
// 0 and 1 are nonprime by definition
is_idx_prime[0] = 0; is_idx_prime[1] = 0;
unsigned long long int total = 0;
unsigned int count = 0;
for (int i = 0; i < target; i++) {
// cout << "\n" << i << ": " << is_idx_prime[i];
if (is_idx_prime[i]) {
total += i;
count++;
}
}
cout << "\nCount: " << count;
cout << "\nTotal: " << total;
return 0;
}
产出
Count: 9592
Total: 454396537
C:\Users\[...].exe (process 1004) exited with code 0.
Press any key to close this window . . .
或者,更改 n = 1 200 000
会得到
C:\Users\[...].exe (process 3144) exited with code -1073741571.
Press any key to close this window . . .
我在 Windows 上使用默认设置的 Microsoft Visual Studio 解释器。
将评论转为完整答案:
您的操作系统在内存中保留了一个特殊的区域来表示您程序的调用堆栈。每个函数调用都会将一个新的堆栈帧压入堆栈。如果函数returns,栈帧从栈中移除。堆栈帧包括函数参数的内存和函数的局部变量。剩余的内存称为堆。在堆上,可以进行任意内存分配,而堆栈的结构由程序的控制流控制。为堆栈保留有限数量的内存,当它变满时(例如,由于嵌套函数调用太多或本地对象太大),您会发生堆栈溢出。为此,大对象应该分配在堆上。
关于 stack/heap 的一般参考:Link, Link
要在 C++ 中的堆上分配内存,您可以:
使用
vector<bool> is_idx_prime(target);
,它在内部执行堆分配并在向量超出范围时为您释放内存。这是最方便的方法。使用智能指针来管理分配:
auto is_idx_prime = std::make_unique<bool[]>(target);
这也会在数组超出范围时自动释放内存。手动分配内存。我提到这个只是为了教育目的。正如 Paul 在评论中提到的,手动分配内存通常是不可取的,因为您必须再次手动释放内存。如果你有一个有很多内存分配的大程序,你不可避免地会忘记释放一些分配,造成内存泄漏。当你有一个长运行的程序时,比如系统服务,重复的内存泄漏最终会填满整个内存(根据个人经验,这在实践中绝对会发生)。但理论上,如果您想进行手动内存分配,您可以使用
重新分配bool *is_idx_prime = new bool[target];
然后再使用delete [] is_idx_prime
.