通常如果我想分配零初始化数组,我会做这样的事情:
int size = 1000;
int* i = (int*)calloc(sizeof int, size));
稍后我的代码可以执行此操作来检查数组中的元素是否已初始化:
if(!i[10]) {
// i[10] has not been initialized
}
然而,在这种情况下,我不想支付零初始化阵列的前期成本,因为阵列可能非常大(即演出)。但在这种情况下,我可以使用尽可能多的内存,因为我想要内存。
我想我记得有一种技术可以跟踪已经初始化的数组中的元素,而无需支付任何前期成本,这也允许O(1)成本(不使用哈希表进行摊销)。我的回忆是该技术需要一个相同大小的额外数组。
我认为是这样的:
int size = 1000;
int* i = (int*)malloc(size*sizeof int));
int* i_markers = (int*)malloc(size*sizeof int));
如果使用数组中的条目,则记录如下:
i_markers[10] = &i[10];
然后可以像以后一样检查它的用途:
if(i_markers[10] != &i[10]) {
// i[10] has not been initialized
}
当然这不是正确的,因为i_markers[10]
可以随机设置为&i[10]
。
那里的任何人都可以提醒我这项技术吗?
谢谢!
我想我记得它。 这是正确的吗?有更好的方法还是有变化? 再次感谢。 (这已更新为正确的答案)
struct lazy_array {
int size;
int* values;
int* used;
int* back_references;
int num_used;
};
struct lazy_array* create_lazy_array(int size) {
struct lazy_array* lazy = (struct lazy_array*)malloc(sizeof(lazy_array));
lazy->size = 1000;
lazy->values = (int*)malloc(size*sizeof int));
lazy->used = (int*)malloc(size*sizeof int));
lazy->back_references = (int*)malloc(size*sizeof int));
lazy->num_used = 0;
return lazy;
}
void use_index(struct lazy_array* lazy, int index, int value) {
lazy->values[index] = value;
if(is_index_used(lazy, index))
return;
lazy->used[index] = lazy->used;
lazy->back_references[lazy->used[index]] = index;
++lazy->used;
}
int is_index_used(struct lazy_array* lazy, int index) {
return lazy->used[index] < lazy->num_used &&
lazy->back_references[lazy->used[index]] == index);
}
答案 0 :(得分:3)
在我所知道的大多数编译器/标准库中,根据操作系统的批量内存请求逻辑实现了大calloc
个请求(以及malloc
)。在Linux上,这意味着零页面的写时复制mmap
,在Windows上它意味着VirtualAlloc
。在这两种情况下,操作系统都会为您提供已经为零的内存,而calloc
会识别出这一点;如果它从小分配堆中执行一个小的calloc
,它只显式地将内存清零。所以在你写入分配中的任何给定页面之前,它是“免费”的零。不需要明确地懒惰;分配器对你来说很懒惰。
对于小分配,它需要memset
来清除内存,但是,memset
几千字节(或数万)字节相当便宜。对于真正庞大的分配,其中归零将是昂贵的,你得到操作系统提供的内存,免费零(与堆的其余部分分开);例如对于典型配置中的dlmalloc
,超过256 KB的分配将始终是mmap
- ed和munmap
- ed,这意味着您将获得新映射的写时复制映射。零页面(在页面中的某处执行写入之前将其延迟归零的成本,并通过malloc
或calloc
获得256 KB的费用。)
如果你想要更好地保证归零,或者在较小的分配上获得免费归零(尽管你越接近一个页面就越浪费),你可以明确地做malloc
/ calloc
隐式地使用操作系统提供的零值内存,例如替换:
sometype *x = calloc(num, sizeof(*x)); // Or the similar malloc(num * sizeof(*x));
if (!x) { ... do error handling stuff ... }
...
free(x);
使用:
sometype *x = mmap(NULL, num * sizeof(*x), PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
if (x == MAP_FAILED) { ... do error handling stuff ... }
...
munmap(x, num * sizeof(*x));
或在Windows上:
sometype *x = VirtualAlloc(NULL, num * sizeof(*x), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!x) { ... do error handling stuff ... }
...
VirtualFree(x, 0, MEM_RELEASE); // VirtualFree with MEM_RELEASE only takes size of 0
它会让你进行相同的延迟初始化(虽然在Windows上,这可能意味着这些页面在请求之间的背景中只是被懒散地归零,因此当你获得它们时它们会是“真正的”零,而不是* NIX,它们是从零页面开始编辑的,所以当你写它们时,它会被置零。)
答案 1 :(得分:2)
虽然它依赖于未定义的行为,但可以这样做。它被称为懒惰数组。
诀窍是使用反向查找表。每次存储值时,都将其索引存储在惰性数组中:
void store(int value)
{
if (is_stored(value)) return;
lazy_array[value] = next_index;
table[next_index] = value;
++next_index;
}
int is_stored(int value)
{
if (lazy_array[value]<0) return 0;
if (lazy_array[value]>=next_index) return 0;
if (table[lazy_array[value]]!=value) return 0;
return 1;
}
这个想法是,如果该值尚未存储在惰性数组中,那么lazy_array[value]
将是垃圾。它的值将是无效索引或反向查找表中的有效索引。如果它是无效索引,那么您立即知道那里没有存储任何内容。如果它是一个有效的索引,那么你检查你的表。如果你有一个匹配,则存储该值,否则不存储。
缺点是从未初始化的内存中读取是未定义的行为。根据我的经验,它可能会奏效,但没有任何保证。
答案 2 :(得分:1)
有许多可能的技术。一切都取决于你的任务。例如,您可以记住数组的初始化元素max
的最大数量。即如果您的算法可以保证,0
到max
ara的所有元素都已初始化,您可以使用简单的检查if (0 <= i && i <= max)
或类似的内容。
但是如果您的算法需要初始化任意元素(即随机访问),则需要通用解决方案。例如,更有效的数据结构(不是简单的数组,而是稀疏数组或类似的东西)。
因此,请添加有关任务的更多详细信息。我希望我们能找到最佳解决方案。