【问题标题】:Grow array in C without using realloc?在不使用 realloc 的情况下在 C 中增长数组?
【发布时间】:2016-01-08 17:51:07
【问题描述】:

所以我试图一次从标准输入读取一个字节。 while 循环的每次迭代我都试图重新分配缓冲区,但我不想使用 realloc。这是我尝试过的:

   char tempChar = '\0';
   char *buffer;
   int bufferSize = 0;


   buffer = (char*) malloc(sizeof(char));
   while((tempChar = getc(stdin)) != EOF)
   {
      buffer[bufferSize] = tempChar;
      bufferSize++;

      char *temp = buffer;
      buffer = (char*)malloc(sizeof(char)*bufferSize);
      memcpy(buffer, temp, sizeof(temp));
      free(temp);
   }
   buffer[bufferSize] = '\0';

我遇到了分段错误。知道为什么会这样吗?

编辑:好的,我修复了其他人所说的两个错误。这是固定版本:

  char tempChar = '\0';
  char *buffer;
  int bufferSize = 1;
  int count = 0; 
  buffer = malloc(sizeof(char));

  while((tempChar = getc(stdin)) != EOF){
    buffer[count] = tempChar;
    count++;

    if(count >= bufferSize){
      bufferSize *= 2;
      char *temp = buffer;
      buffer = malloc(sizeof(char)*bufferSize);
      memcpy(buffer, temp, count);
      free(temp); 
    }
  }

  buffer[count - 1] = '\0';

【问题讨论】:

  • 你为什么不想使用realloc
  • 你差了一个 - 读完一个字符后,你创建了一个新的缓冲区 - 大小为 one。而你的memcpy() 是错误的。
  • 仅从实际的角度来看,我想不出一种效率较低的内存分配方式。您至少应该分配一些合理的字节数,然后在每次需要时增加一些合理的数量。为什么不3264256等...
  • sizeof(temp) 是一个常数;可能是 4 或 8 个。
  • @DavidC.Rankin:你从哪里得到这个数字 32?我希望fgets 几乎立即变得更快。 fgets 的开销不应大于 OP 的循环,并且每个字符的成本应该显着降低(每个字符 1 个循环与每个字符调用整个函数相比)。

标签: c memory-management segmentation-fault malloc


【解决方案1】:

您的代码中有两个错误。

1) memcpy(buffer, temp, sizeof(temp)); 这一行每次都复制固定数量的字节。 sizeof(temp) 不是数组的分配大小,而是指针的大小,很可能是 4 或 8 个字节。因此,在最初的几次迭代中,您实际上写入的空间超过了分配的空间,之后只是没有复制足够的空间。您想将 sizeof(temp) 替换为 bufferSize,与分配缓冲区时使用的值相同。

2) 最后你有buffer[bufferSize] = '\0';。然而,这是在分配缓冲区末尾写入一个字节。 bufferbufferSize 字节,这意味着它的可寻址索引值为 0 - bufferSize-1。但是用buffer[bufferSize-1] 替换这一行将用零终止符替换最后一个字符,因此您将始终丢失输入的最后一个字符。相反,您需要再分配 1 个字节。 我个人会将分配行替换为:

buffer = (char*)malloc(bufferSize+1);

【讨论】:

    【解决方案2】:

    您会在这里遇到分段错误有两个原因。在开始之前,我不得不提一下,每次读取一个额外的字节时分配一个新的缓冲区是非常低效的。分配一些合理大小的缓冲区,然后将其扩展为合理大小的块几乎总是更可取的。

    也就是说,您的代码中的第一个问题是memcpy(buffer, temp, sizeof(temp))。当您执行sizeof(temp) 时,您将获得系统上指针的字节数。可能是 4 或 8。您真正想要的是旧缓冲区的长度。这最初是一个问题,因为您的缓冲区开始时的大小为 1 字节。然后,您尝试复制总共 4 或 8 个字节(可能),但并非所有这些都是缓冲区的一部分。

    其次,当您执行buffer[bufferSize] = '\0' 时,实际上是在将\0 写入缓冲区结束后的字节。为此,您需要使用 bufferSize - 1

    【讨论】:

    • "第二,当您执行buffer[bufferSize] = '\0' 时,您实际上是在将\0 写入缓冲区结束后的字节。您可能希望为此使用bufferSize - 1。"代码的编写方式,当循环中断时,bufferSize 字符已被读入数组。所以buffer[bufferSize - 1] = '\0' 会截断最后读取的字符。
    • 这是真的。您仍然希望使用bufferSize - 1 在缓冲区范围内写入\0。但是,重要的是要考虑该位置可能已经存在哪些数据。正如 Andrew 指出的那样,根据 OP 的代码,该内存位置将包含一个已读入的字符。
    【解决方案3】:

    您的缓冲区太小了一个字节。这一行

      buffer = (char*)malloc(sizeof(char)*bufferSize);
    

    应该阅读

      buffer = malloc(bufferSize + 1);
    

    不要在 C 中从 malloc() 转换返回值,而 sizeof(char) 是定义中的一个。

    另外,这是错误的:

      memcpy(buffer, temp, sizeof(temp));
    

    复制的字节数等于char * 的大小。

    【讨论】:

    • 只是想指出sizeof(char) 不是一个定义。 char 的大小完全由实现定义。如果我写一个架构说字符是 5 个字节......那么sizeof(char) 是五个。尽管如此,从所有意图和目的来看,一个 char 几乎总是一个字节。
    • @hexturtle 不,sizeof(char) == 1。总是。也在你假设的机器上。如果您的char 是五个八位字节,而您的int 是十个八位字节,那么sizeof(char) == 1sizeof(int) == 2
    • @hexturtle 按照 C 标准 (open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf) 6.5.3.4 sizeof 运算符:“当应用于类型为 charunsigned char 的操作数时,或signed char,(或其合格版本)结果为1。”
    • 并非所有机器和架构都符合 C 标准。我的观点是迂腐的,但尽管如此,如果你想为 n 个字符分配空间,那么最好写 sizeof(char) * n 而不是假设一个字符是一个字节。同样,对于几乎所有实际目的,一个 char 将是一个字节。大多数人可能会在他们的代码中做出这个假设。但是,如果我在具有稍微不同架构的机器上运行您的代码,其中字符未定义为单个字节,那么您的代码将无法按预期工作。
    • @hexturtle 如果你用 C 编写代码,你不是在编写硬件,而是在编写 C 标准指定的抽象机器。该标准对sizeof(char) 非常精确。编译器的工作是将抽象机器的程序转换为真实硬件可以执行的二进制文件,并且仍然完全按照标准要求运行。如果没有,那么你的 C 实现就坏了。这个问题被标记为“C”,所以我们可以假设一个符合 C 的实现。
    【解决方案4】:

    我遇到了分段错误。知道为什么会这样吗?

    因为您的写入超出了分配的内存。

    这就是realloc 的用途。

    您无需在每次读取新字节时都调用realloc 来增大缓冲区,而是在每次需要增大时将缓冲区大小加倍,直到达到某个实际最大值,然后通知您的用户。这就是像 C++ 的 vector<T> 这样的数据结构的工作原理。

    【讨论】:

    • 谁知道为什么有些白痴会不顾一切地投反对票。我想这会让他们觉得自己更重要。答案没有任何问题,仅仅因为它引用了向量的工作方式。
    猜你喜欢
    • 2014-10-15
    • 2013-07-18
    • 1970-01-01
    • 2014-07-19
    • 2019-08-28
    • 2022-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多