【问题标题】:What's the time complexity of realloc function in C?C中realloc函数的时间复杂度是多少?
【发布时间】:2018-11-02 11:57:08
【问题描述】:

我有一个问题:realloc 函数的时间复杂度是多少? 例如,我有一个整数数组:a[10]。当然,数组已经通过这种方式动态分配了=>

int *a = (int*)malloc(10*sizeof(int));

然后我想将此数组的大小调整为 11,以便向数组 a 插入一个附加值,所以我这样做 =>

a = (int*)realloc(a, 11*sizeof(int));

我的问题是:重新分配的时间复杂度是多少? realloc 是否只是简单地在数组中添加一个额外的单元格,然后它需要 O(1) 或者它重新复制整个数组 a,添加额外的第 11 个单元格并返回新大小的数组,在这种情况下,这个动作的时间复杂度是 O (n)?哪个假设是正确的?

【问题讨论】:

  • 你怎么看?问题的原因。
  • 这两种情况都可能发生,具体取决于数组是否可以扩展(例如:同一连续内存中有更多可用空间),请参阅:en.cppreference.com/w/c/memory/realloc
  • @DaveNewton OP 确实解释了这个问题。
  • @nicomp 我还要补充一点,(对我而言)推理也意味着分享您自己的调查结果,并且需要进行调查以显示对问题的尽职调查。我的意思是,如果您足够聪明地提出问题,那么您肯定足够聪明,可以进行大量研究,并可能得出正确的结论(“取决于”)。但要要求验证关于 SO 的假设,IMO 设置了相当高的标准,以表明尽职调查 YMMV。
  • @nicomp 我并不是想成为好斗或分裂的人——我只是认为这个问题是合理的,可以通过适当的神经元应用自行回答。

标签: c arrays memory allocation


【解决方案1】:

首先,您的代码(以您问题的原始形式)是错误的。至少应该是

a = realloc(a, 11*sizeof(int));

(否则你可能有未定义的行为,通常情况下sizeof(int) 大于一)。顺便说一句,realloc 不应在 C 中强制转换。

那么,你的问题

C中realloc函数的时间复杂度是多少?

没有意义,除非你说一些你知道实现的特定 realloc 函数。

注意realloc 允许失败。这是一个高效但无用且持续时间的实现(另请参阅this 答案;它是符合realloc 的标准,遵循标准n1570 的字母,但不是精神):

void *realloc( void *ptr, size_t new_size ) { 
  errno = ENOMEM;
  return NULL;
}

最后,在实践中,您可以将 mallocrealloc 视为某种昂贵的操作(典型的运行时间可能是几微秒,因此比加法慢数千倍),并且在许多情况下您不希望在每个循环中发送到 realloc。所以你宁愿为realloc使用一些几何增长(比如here

realloc 是否只是简单地在数组中添加一个额外的单元格,然后它需要 O(1) 或者它重新复制整个数组 a,添加额外的第 11 个单元格并返回新大小的数组,在这种情况下是时间复杂度动作是 O(n)?

两者都可能发生。您可以想象malloc 将分配的大小保持在某个位置(由实现四舍五入...),并且在好的情况下realloc 返回相同的指针(所以 O(1))并且在不太愉快的情况下它需要复制其他地方的记忆区。在不知道您的特定实现(mallocreallocfree)的情况下,您无法知道最常见的情况或它们的概率分布。顺便说一句,似乎 总是 复制数据(或失败)的realloc 实现符合标准(但效率低下)。如果您需要了解更多信息,您应该进行基准测试。

最后,C standard library 的许多实现是free software(例如GNU libcmusl-libc、...),您可以研究它们的源代码mallocfreerealloc (在operating system 原语之上——通常是system calls——正在增长virtual address space;在Linux 上,类似于mmap(2)

【讨论】:

  • 是的,我忘记添加类型的大小以避免错误和未定义的行为。但是在我在该主题中解释的这个特殊情况下,如果我采用相同的数组并将其调整为另一个单元格,那么 realloc 函数有多少个动作?
  • 这就是我所说的。我不采用另一个指针并通过新指针重新定位数组 a 的内存。我使用一个指针来保持数组的地址,并像这样在同一个指针上执行 realloc => a = (int*) realloc (a, 11*sizeof(int));
  • 这被认为是 C 语言中的最佳实践 not to cast the result of malloc or realloc
【解决方案2】:

realloc 等价于:

void *
realloc(void *old, size_t new_size)
{
    size_t old_size = internal_function_that_knows_old_size(old);
    void *new = malloc(new_size);
    if (new == NULL)
        return NULL;
    size_t sz = old_size;
    if (new_size < old_size)
        sz = new_size;
    memcpy(new, old, sz);
    free(old);
    return new;
}

realloc 有可能进行优化,在某些情况下使其速度更快,但我很确定不可能让这些优化始终有效,因此后备将始终是一个执行类似上面,所以你应该考虑这是你最坏的情况。

现在,当涉及到时间复杂度时,标准中没有任何内容要求 mallocfree 具有任何合理的行为,因此它们可能会主导此函数的运行时(或 internal_function_that_knows_old_size 将),但是由于系统的这些位通常写得很好,这是不太可能的。占主导地位的部分(至少对于大 n 而言,这是有趣的复杂性)将是 memcpy

因此,在一些合理的假设下,realloc 必须是 O(n)(其中 n 是分配的旧大小或新大小,以较小者为准)。

【讨论】:

  • 不完全是。也许一些if (old_size == new_size) return old; 可能会发生(但未指定)。所以它并不完全等价。 BTW internal_function_that_knows_old_size 可能不是恒定时间!
  • @BasileStarynkevitch “realloc 有可能进行优化,在某些情况下使其更快”的哪一部分不清楚?您的评论与一般情况有何关系?每个具体案例都是 O(1),这就是为什么谈论具体案例的大 O 是无关紧要的。
  • 你声称realloc 等价于你的代码,没有等价,只有一个假设。我的回答中的realloc 是一个可能(但很愚蠢)的实现,并且有很多变体(例如,对地址进行一些位计算等......)
  • 如果您写道:“realloc 通常使用类似于”的代码实现,我会赞成您的回答,但由于您声称“realloc 是等效”,我否决了你的答案。例如,realloc 的一些合理实现可能以if (new_size &gt; SOME_LARGE_LIMIT) return NULL; 开头;顺便说一句,realloc 在实践中不要调用malloc(但两者都调用了一些内部函数
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-08-07
  • 1970-01-01
  • 2016-12-12
  • 1970-01-01
  • 2020-12-03
  • 2017-09-11
  • 2020-03-13
相关资源
最近更新 更多