在内存中复制非常大的字符串

时间:2013-12-25 17:17:51

标签: c memory

我正在尝试实现一种解决方案,以便在C中复制内存中的大字符串。

您能否就实施或参考提供任何建议?

我正在考虑逐字节复制,因为我不知道长度(可能我无法使用strlen()计算它,因为字符串非常大)。

另一个问题是我将不得不在每一步重新分配内存,我不知道最好的方法是如何做到这一点。有没有办法只使用已经分配和填充的内存的最后位置的引用来重新分配?因此,如果内存分配失败,它将不会影响已经填充的其余内存。

从此功能返回的最佳值是多少?我应该返回成功复制的字节数吗?

如果内存分配失败,realloc()是否设置了在调用复制功能后我可以在main函数中检查的任何全局变量?因为我不想在某个时候NULL失败而只返回realloc(),但我想返回一个更有用的值。

4 个答案:

答案 0 :(得分:3)

strlen()不会失败,因为它使用size_t来描述字符串的大小,而size_t足以容纳程序运行的机器上任何对象的大小

所以简单地做

#define _XOPEN_SOURCE 500 /* for strdup */
#include <string.h>

int duplicate_string(const char * src, char ** pdst)
{
  int result = 0;

  if (NULL == ((*pdst) = strdup(src)))
  {
    result = -1;
  }

  return result;
}

如果失败,请尝试使用更聪明的结构来保存数据,例如将其切成片:

#define _XOPEN_SOURCE 700 /* for strndup */
#include <string.h>

int slice_string(const char * src, char *** ppdst, size_t s)
{
  int result = 0;

  size_t s_internal = s + 1; /* Add one for the 0-terminator. */
  size_t len = strlen(src) + 1;
  size_t n =len/s_internal + (len%s_internal ?1 :0);

  *ppdst = calloc(n + 1, sizeof(**ppdst)); /* +1 to have a stopper element. */
  if (NULL == (*ppdst))
  {
    result = -1;
    goto lblExit;
  }

  for (size_t i = 0; i < n; ++i)
  {
    (*ppdst)[i] = strndup(src, s);
    if (NULL == (*ppdst)[i])
    {
      result = -1;

      while (--i > 0)
      {
        free((*ppdst)[i]);
      }

      free(*ppdst);

      *ppdst = NULL;

      goto lblExit;
    }

    src += s;
  }

lblExit:
  return result;
} 

首先尝试转储复制来使用此类函数,如果通过切割字符串失败,则使用此类函数。

int main(void)
{
  char * s = NULL;

  read_big_string(&s);

  int result = 0;
  char * d = NULL;
  char ** pd = NULL;

  /* 1st try dump copy. */
  result = duplicate_string(s, &d);
  if (0 != result)
  {
    /*2ndly try to slice it. */
    {
      size_t len = strlen(s);

      do
      {
        len = len/2 + (len%2 ?1 :0);
        result = slice_string(s, &pd, len);
      } while ((0 != result) || (1 == len));
    } 
  }

  if (0 != result)
  {
    fprintf(stderr, "Duplicating the string failed.\n");         
  }

/* Use copies. */

  if (NULL != d)
  {
    /* USe result from simple duplication. */
  }

  if (NULL != pd)
  {
    /* Use result from sliced duplication. */
  }

  /* Free the copies. */
  if (NULL != pd)
  {
    for (size_t i = 0; pd[i]; ++i)
    {
      free(pd[i]);
    }
  }

  free(pd);
  free(d);

  return 0;
}

答案 1 :(得分:2)

realloc()失败

  

如果内存分配失败,realloc()是否设置了在调用复制功能后我可以在main函数中检查的任何全局变量?因为我不想在某个时候NULL失败而只返回realloc(),但我想返回一个更有用的值。

如果正确使用realloc(),则realloc()返回null没有问题。如果您错误地使用realloc(),则会得到您应得的结果。

realloc()

的使用不正确
char *space = malloc(large_number);

space = realloc(space, even_larger_number);

如果realloc()失败,则此代码已覆盖对以前分配的空格的唯一引用,因此您不仅没有分配新空间,而且因为丢失了也无法释放旧空间指向它的指针。

(对于苛刻的:原始malloc()可能失败的事实并不重要; space将为NULL,但这是realloc()的有效第一个参数。唯一的区别是没有丢失的先前分配。)

正确使用realloc()

char *space = malloc(large_number);

char *new_space = realloc(space, even_larger_number);

if (new_space != 0)
    space = new_space;

这会在覆盖realloc()中的值之前保存并测试space的结果。

持续增长的记忆

  

另一个问题是我将不得不在每一步重新分配内存,我不知道最好的方法是如何做到这一点。有没有办法只使用对已经分配和填充的内存的最后位置的引用来重新分配?因此,如果内存分配失败,它将不会影响已经填充的其余内存。

避免二次行为的标准技术(在处理兆字节数据时确实很重要)是在需要增长时为工作字符串分配的空间加倍。你通过保留三个值来做到这一点:

  • 指向数据的指针。
  • 分配的数据区域的大小。
  • 正在使用的数据区域的大小。

当传入的数据不适合未使用的空间时,您需要重新分配空间,将分配的数量加倍,除非您需要的空间大于新空间。如果您认为以后要添加更多数据,那么您可以将新数量加倍。这会分摊内存分配的成本,并且可以节省复制不变数据的时间。

struct String
{
    char *data;
    size_t length;
    size_t allocated;
};

int add_data_to_string(struct String *str, char const *data, size_t datalen)
{
    if (str->length + datalen >= str->allocated)
    {
        size_t newlen = 2 * (str->allocated + datalen + 1);
        char *newdata = realloc(str->data, newlen);
        if (newdata == 0)
            return -1;
        str->data = newdata;
        str->allocated = newlen;
    }
    memcpy(str->data + str->length, data, datalen + 1);
    str->length += datalen;
    return 0;
}

完成对字符串的添加后,如果您愿意,可以释放未使用的空间:

void release_unused(struct String *str)
{
     char *data = realloc(str->data, str->length + 1);
     str->data = data;
     str->allocated = str->length + 1;
}

缩小内存块是不太可能移动它,但标准说:

  

realloc函数释放ptr指向的旧对象并返回一个   指向具有size指定大小的新对象的指针。新内容   在解除分配之前,对象应与旧对象的对象相同,直到较小的对象   新旧尺寸。

     

realloc函数返回指向新对象的指针(可能具有相同的指针)   value作为指向旧对象的指针),如果新对象不能,则返回空指针   分配

请注意,“可能与指向旧对象的指针具有相同的值”,也意味着“可能与指向旧对象的指针具有不同的值”。

代码假定它处理空终止字符串;例如,memcpy()代码复制长度加上一个字节以收集终端null,release_unused()代码为终端保留一个字节null。 length元素是strlen()返回的值,但是不要继续对兆字节数据执行strlen()。如果您正在处理二进制数据,则会以微妙的方式处理事物。

答案 2 :(得分:1)

好的,让我们用Cunningham的问题来帮助弄清楚要做什么。坎宁安的问题(或查询 - 你的选择:-)是:

  

什么是最简单的可能有用的东西?
   - Ward Cunningham

IMO最简单的事情可能是分配一个大缓冲区,将字符串吸入缓冲区,将缓冲区重新分配到字符串的实际大小,然后返回指向该缓冲区的指针。调用者有责任释放他们完成缓冲后获得的缓冲区。大概的顺序:

#define BIG_BUFFER_SIZE 100000000

char *read_big_string(FILE *f)  /* read a big string from a file */
  {
  char *buf = malloc(BIG_BUFFER_SIZE);

  fgets(buf, BIG_BUFFER_SIZE, f);

  realloc(buf, strlen(buf)+1);

  return buf;
  }

这只是示例代码。有#includes不包括在内,并且存在大量可能的错误,这些错误在上面没有处理,其实现留给读者练习。你的旅费可能会改变。经销商的贡献可能会影响成本。请咨询您的经销商,了解您所在地区的价格和选项。警告密码。

分享并享受。

答案 3 :(得分:1)

使用智能指针并避免首先复制