代码之家  ›  专栏  ›  技术社区  ›  Delan Azabani

C语言中简单变量串连接的内存分配

  •  1
  • Delan Azabani  · 技术社区  · 14 年前

    我有以下测试函数来复制和连接一个可变数目的字符串参数,并自动分配:

    char *copycat(char *first, ...) {
        va_list vl;
        va_start(vl, first);
        char *result = (char *) malloc(strlen(first) + 1);
        char *next;
        strcpy(result, first);
        while (next = va_arg(vl, char *)) {
            result = (char *) realloc(result, strlen(result) + strlen(next) + 1);
            strcat(result, next);
        }
        return result;
    }
    

    puts(copycat("herp", "derp", "hurr", "durr"));
    

    它应该打印出一个16字节的字符串, "herpderphurrdurr" . 相反,它会打印出一个42字节的字符串,这是正确的16字节加上26字节的垃圾字符。

    5 回复  |  直到 14 年前
        1
  •  5
  •   Oliver Charlesworth    14 年前

    变量参数列表函数无法神奇地知道有多少个参数,因此您很可能遍历堆栈,直到碰巧碰到 NULL .

    numStrings ,或在字符串列表后提供显式的空终止符参数。

        2
  •  5
  •   paxdiablo    14 年前

    你需要一个哨兵标记:

    puts(copycat("herp", "derp", "hurr", "durr", NULL));
    

    va_arg 不知道什么时候该停下来。你得到垃圾的事实纯属偶然,因为你调用了未定义的行为。例如,当我按原样运行代码时,出现了分段错误。

    变量参数函数,例如 printf 需要某种指示,说明传入了多少项: 它本身使用前面的格式字符串来解决这个问题。

    这两个常规方法是一个count(或format string),当您不能使用其中一个可能的值作为哨兵(结尾的标记)时,它很有用。

    如果你 使用sentinel(比如指针为NULL,非负有符号整数为-1,这通常更好,这样就不必对元素进行计数(可能会使元素计数和元素列表不同步)。


    记住 puts(copycat("herp", "derp", "hurr", "durr")); 是内存泄漏,因为您正在分配内存,然后丢失指向它的指针。使用:

    char *s = copycat("herp", "derp", "hurr", "durr");
    puts(s);
    free (s);
    

        3
  •  3
  •   icecrime    14 年前

    我从您的代码中了解到的是,您假设在每个参数被“弹出”后,va_next将返回NULL。这是错误的,因为va next绝对无法确定参数的数量:while循环将一直运行,直到随机命中空值。

    解决方案:要么提供参数的数量,要么添加一个额外的“NULL”参数来调用函数。

        4
  •  2
  •   Secure    14 年前

    作为对其他答案的补充,您应该将 NULL (char *)NULL . 如果将NULL定义为0,则将存储一个int,当int以sime size作为指针并且NULL由所有位0表示时,它将意外地工作。但这一切都无法保证,因此在移植代码或仅更改编译器时,可能会遇到难以调试的奇怪行为。

        5
  •  1
  •   dreamlax    14 年前

    正如其他人所说, va_arg NULL (或其他标记)调用函数时。只需几句话:

    • 你必须打电话 free 从中获取的指针 malloc realloc .
    • 马洛克 重新分配
    • 打电话时 ,最好将返回值存储到临时变量中。如果 无法重新分配足够的内存,返回 但原始指针是 重新分配 strcat 可能会失败。你可以这样使用它:

      char *tmp = realloc(result, strlen(result) + strlen(next) + 1);
      if (tmp == NULL)
      {
          // handle error here and free the memory
          free(result);
      }
      else
      {
          // reallocation was successful, re-assign the original pointer
          result = tmp;
      }