在C语言中分配内存并保存字符串

30

我在想为什么下面的代码不能工作

int main(int argc, char **argv)
{
     char *test = (char*) malloc(12*sizeof(char));
     test = "testingonly";
     free(test);
}

经过思考,我的假设是我在内存中为12个字符分配了空间,但是下一行的赋值会在堆栈上创建一个char数组,并将其内存地址传递给test。因此free()尝试释放堆栈上的空间,这是不允许的。这样理解正确吗?

那么正确的方法是什么来保存字符串到堆上?下面这种方式常见吗?

int main(int argc, char **argv)
{
     char *test = (char*) malloc(12*sizeof(char));
     strcpy(test, "testingonly");
     free(test);
}

5
第一种解决方案展示了一个经典的内存泄漏情况;你获得了指向某个已分配内存的指针,然后将这个指针赋值给test中的字符串常量,这样就失去了对这块内存唯一的引用。之后,你就没有合法的方式来引用这块已分配的内存——造成了内存泄漏。 - Jonathan Leffler
2
在C语言中,永远不要将malloc的结果强制转换为其他类型,这是毫无意义的,只会隐藏错误和编译器警告。 - Lundin
2
是的 - 使用 strcpystrncpymemcpy。strncpy 比 strcpy 更好,因为它在最多复制 N 个字符时有助于避免缓冲区溢出问题。 - Agnius Vasiliauskas
@0x69 strncpy对于缓冲区溢出是无济于事的,这是一个谬论。因为strncpy确实会向缓冲区写入n个字符,但如果在该缓冲区中没有更多空间,则不会写入任何字符串的空终止符。因此,它将导致程序崩溃和烧毁,就像strcpy一样。解决任何问题的方法当然是知道你在做什么。使用strcpy没有任何问题,只要你知道源和目标的性质即可。 - Lundin
我认为,不将空终止符写入缓冲区比在缓冲区外部写入空字符(缓冲区溢出)更好。在第一种情况下,通常在打印这样的字符串时,您会看到来自内存的垃圾数据,而在第二种情况下,您的程序可能以任何不可预测的方式崩溃。因此,我认为第一种错误类型比第二种更容易在代码中定位。但我可能是错的。 - Agnius Vasiliauskas
6个回答

85
char *test = (char*) malloc(12*sizeof(char));

        +-+-+-+-+-+-+-+-+-+-+-+-+
test--->|x|x|x|x|x|x|x|x|x|x|x|x|   (uninitialized memory, heap)
        +-+-+-+-+-+-+-+-+-+-+-+-+

test = "testingonly";

        +-+-+-+-+-+-+-+-+-+-+-+-+
test +  |x|x|x|x|x|x|x|x|x|x|x|x|
     |  +-+-+-+-+-+-+-+-+-+-+-+-+
     |  +-+-+-+-+-+-+-+-+-+-+-+-+
     +->|t|e|s|t|i|n|g|o|n|l|y|0|  
        +-+-+-+-+-+-+-+-+-+-+-+-+

free(test); // error, because test is no longer pointing to allocated space.

不要改变指针 test,而是需要使用 strcpystrdup 方法将字符串 "testingonly" 复制到已分配的位置中。请注意,像 mallocstrdup 这样的函数如果可用内存不足则会返回 NULL,因此应进行检查。

char *test = (char*) malloc(12*sizeof(char));
strcpy(test, "testingonly");

        +-+-+-+-+-+-+-+-+-+-+-+-+
test--->|t|e|s|t|i|n|g|o|n|l|y|0|
        +-+-+-+-+-+-+-+-+-+-+-+-+

或者

char *test = strdup("testingonly");

        +-+-+-+-+-+-+-+-+-+-+-+-+
test--->|t|e|s|t|i|n|g|o|n|l|y|0|
        +-+-+-+-+-+-+-+-+-+-+-+-+

test[0] = 0 然后 test = '期望的字符串' 怎么样? - Cătălina Sîrbu
1
假设你在之前分配了test的内存,那么你的第一个语句 test[0] = 0 将会把第一个字符设置成 \0 ,但是一旦你执行了 test = "desired string", test 就会指向新字符串的内存位置,而这个位置是只读的。一旦该字符串不再被使用,test也将不再指向有效的内存空间。 - AndersK

9

你已经回答了自己的问题。基本上,strcpy是复制字符串的合适方式。


1
只要您知道目标字符串中已经分配了适当的内存,并且两个字符串都以空字符结尾。 - Dave

6
第一个版本不会在堆栈上创建字符串,但您是正确的,赋值后不能使用free释放它。字符串字面量通常存储在内存的常量/只读部分中。该赋值不会复制任何内容,只是使test指向该内存区域。您无法释放它。您也无法修改该字符串。
您的第二段代码是正确和常见的。如果您的实现支持,则还可以查看strdup

strncpy不是strcpy的更安全版本。它可能导致目标数组未终止。这很少是正确的解决方案。 - Keith Thompson
@Keith:没错,已经删除了对那个的引用。strdup还是挺好用的(前提是你知道输入符合两个条件:1. 是有效的C字符串;2. 大小在你的应用程序中是可接受的,无论这个大小是多少)。 - Mat

4
好的,你说得对。现在让我们先看一下第一段代码。
char *test = (char*) malloc(12*sizeof(char));

以上代码没有问题。
test = "testingonly";

在这里,您修改了指针test,导致内存泄漏。当您尝试释放时,您并没有释放实际分配的指针,而是一个指向“testingonly”文字的指针。字面量指向无法在通常情况下覆盖的常量内存。

现在谈到第二段代码,这将正常工作,因为您明确地从文字所在的位置复制数据到堆中,其中您的test指向。

对于您的第二个问题,是的,strcpy是一种常见的方法。如果要复制原始字节,则可以使用“memcpy”等其他方法。

注意:字面量不存储在堆栈上。但是您不能修改存储字面量的位置。


“这将很好地工作,因为您明确地将数据从字面值所在的位置复制到堆中,而堆是您的测试指向的位置。” - Amit Singh Tomar

0

这段代码

#include <stdio.h>
int main(int argc, char **argv)
{
     char *test = (char*) malloc(12*sizeof(char));
     strcpy(test, "testingonly");
     printf("string is: %s\n",test);
     free(test);
     return 0;
}

会起作用


0

这是用于分配内存的:

char *string;
string = (char *) malloc(15);

这是用于保存数据的:

strcpy(str, "kavitajain");
printf("String = %s,  Address = %u\n", str, str);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接