简体   繁体   English

C中简单可变参数字符串连接的内存分配

[英]Memory allocation for simple variadic string concatenation in C

I have the following test function to copy and concatenate a variable number of string arguments, allocating automatically: 我有以下测试函数可复制并连接可变数量的字符串参数,并自动分配:

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

Problem is, if I do this: 问题是,如果我这样做:

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

it should print out a 16-byte string, "herpderphurrdurr" . 它应该打印出一个16字节的字符串"herpderphurrdurr" Instead, it prints out a 42-byte string, which is the correct 16 bytes plus 26 more bytes of junk characters. 而是打印出一个42字节的字符串,它是正确的16字节加上26个字节的垃圾字符。

I'm not quite sure why yet. 我不太确定为什么。 Any ideas? 有任何想法吗?

The variable-argument-list functions don't magically know how many arguments there are, so you're most likely walking the stack until you happen to hit a NULL . variable-argument-list函数无法神奇地知道有多少个参数,因此,您很可能会遍历堆栈,直到碰巧碰到NULL为止。

You either need an argument numStrings , or supply an explicit null-terminator argument after your list of strings. 您要么需要一个numStrings参数,要么在字符串列表之后提供一个明确的null终止符参数。

You need a sentinel marker on your list: 您的清单上需要一个标记标记:

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

Otherwise, va_arg doesn't actually know when to stop. 否则, va_arg实际上不知道何时停止。 That fact that you're getting junk is pure accident since you're invoking undefined behaviour. 由于您正在调用未定义的行为,因此您将成为垃圾的事实纯属偶然。 For example, when I ran your code as-is, I got a segmentation fault. 例如,当我按原样运行您的代码时,我遇到了段错误。

Variable argument functions, such as printf need some sort of indication as to how many items are passed in: printf itself uses the format string up front to figure this out. 可变参数函数(例如printf需要某种指示来指示要传入多少个项目: printf本身使用前面的格式字符串来弄清楚这一点。

The two general methods are a count (or format string) which is useful when you can't use one of the possible values as a sentinel (a marker at the end). 两种通用方法是计数(或格式字符串),当您不能将可能的值之一用作标记(末尾有一个标记)时,此方法很有用。

If you can use a sentinel (like NULL in the case of pointers, or -1 in the case of non-negative signed integers, that's usually better so you don't have to count the elements (and possible get the element count and element list out of step). 如果您可以使用前哨(例如,对于指针,则为NULL;对于非负有符号整数,则为-1),通常情况下更好,因此您不必对元素进行计数(并且可以获取元素计数和元素列表不对)。


Keep in mind that puts(copycat("herp", "derp", "hurr", "durr")); 请记住, puts(copycat("herp", "derp", "hurr", "durr")); is a memory leak since you're allocating memory then losing the pointer to it. 这是内存泄漏,因为要分配内存然后丢失指向它的指针。 Using: 使用方法:

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

is one way to fix that, and you may want to put in error checking code in case the allocations fail. 是解决此问题的一种方法,如果分配失败,您可能需要放入错误检查代码。

What I understand from your code is that you assume va_next will return NULL once each argument has been "popped". 我从您的代码中了解到,您假设一旦“弹出”每个参数,va_next将返回NULL。 That's wrong as va_next has absolutely no way to determine the number of arguments : your while loop will keep running until a NULL is randomly hit. 这是错误的,因为va_next绝对无法确定参数的数量:您的while循环将一直运行,直到随机命中NULL。

Solution : either provide the number of arguments, or add call your function with an additional "NULL" argument. 解决方案:提供参数数量,或添加带有附加“ NULL”参数的函数调用。

PS: if you are wondering why printf doesn't require such an additional argument, it's because the number of expected arguments is deduced from the format string (the number of '%flag') PS:如果您想知道为什么printf不需要这样的附加参数,那是因为期望的参数数量是从格式字符串(“%flag”的数量)推导出来的

As an addition to the other answers, you should cast the NULL to the expected type when using it as an argument to a variadic function: (char *)NULL . 作为其他答案的补充,将NULL用作可变参数的参数时,应将NULL转换为期望的类型: (char *)NULL If NULL is defined as 0, then an int will be stored instead, which will accidentally work when int has the sime size as the pointer and NULL is represented by all bits 0. But none of this is guaranteed, so you may run into strange behaviour that's hard to debug when porting the code or even when only changing the compiler. 如果将NULL定义为0,则将存储一个int,当int的大小为sime且指针由所有位0表示NULL时,该int会意外地工作。但这不能保证,因此您可能会遇到奇怪的情况移植代码或什至仅更改编译器时很难调试的行为。

As others have mentioned, va_arg does not know when to stop. 正如其他人提到的, va_arg不知道何时停止。 It is up to you to provide NULL (or some other marker) when you call the function. 调用函数时,您可以提供NULL (或其他标记)。 Just a few side notes: 只是一些注意事项:

  • You must call free on pointers you obtain from malloc and realloc . 您必须在从mallocrealloc获得的指针上调用free
  • There is no reason to cast the result of malloc or realloc in C. 没有理由在C中realloc mallocrealloc的结果。
  • When calling realloc , it is best to store the return value into a temporary variable. 调用realloc ,最好将返回值存储到一个临时变量中。 If realloc is unable to reallocate enough memory, it returns NULL but the original pointer is not freed. 如果realloc无法重新分配足够的内存,它将返回NULL但原始指针不会释放。 If you use realloc the way you do, and it is unable to reallocate the memory, then you have lost the original pointer and your subsequent call to strcat will likely fail. 如果您使用realloc你的方式,它是无法重新分配内存,那么你已经失去了原有的指针和你的后续调用strcat很可能会失败。 You could use it like this: 您可以这样使用它:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM