欺骗以避免需要初始化数组

时间:2015-10-02 04:14:35

标签: c arrays malloc calloc

通常如果我想分配零初始化数组,我会做这样的事情:

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);
}

3 个答案:

答案 0 :(得分:3)

在我所知道的大多数编译器/标准库中,根据操作系统的批量内存请求逻辑实现了大calloc个请求(以及malloc)。在Linux上,这意味着零页面的写时复制mmap,在Windows上它意味着VirtualAlloc。在这两种情况下,操作系统都会为您提供已经为零的内存,而calloc会识别出这一点;如果它从小分配堆中执行一个小的calloc,它只显式地将内存清零。所以在你写入分配中的任何给定页面之前,它是“免费”的零。不需要明确地懒惰;分配器对你来说很懒惰。

对于小分配,它需要memset来清除内存,但是,memset几千字节(或数万)字节相当便宜。对于真正庞大的分配,其中归零将是昂贵的,你得到操作系统提供的内存,免费零(与堆的其余部分分开);例如对于典型配置中的dlmalloc,超过256 KB的分配将始终是mmap - ed和munmap - ed,这意味着您将获得新映射的写时复制映射。零页面(在页面中的某处执行写入之前将其延迟归零的成本,并通过malloccalloc获得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的最大数量。即如果您的算法可以保证,0max ara的所有元素都已初始化,您可以使用简单的检查if (0 <= i && i <= max)或类似的内容。

但是如果您的算法需要初始化任意元素(即随机访问),则需要通用解决方案。例如,更有效的数据结构(不是简单的数组,而是稀疏数组或类似的东西)。

因此,请添加有关任务的更多详细信息。我希望我们能找到最佳解决方案。