char x[256]与char* = malloc(256*sizeof(char))的区别是什么?

13

最近有个人在我的一段代码中指出了一个问题

char* name = malloc(256*sizeof(char));
// more code
free(name);

我曾以为这种设定数组的方式与使用

char name[256];

我认为两种方法都需要使用free()函数,如果我错了,能否有人用浅显易懂的术语解释一下区别?


Ned Batchelder是正确的。然而,通常你会看到char *name和char name[]混合使用。或者你可能会看到一个以char *array为参数的函数被分配为char name[256]的名称所调用。基本上在C语言中,数组和指针是相同的,因此你经常会看到这些线条模糊不清。 - Cervo
1
关键在于数组指向堆栈还是堆。你可以使用malloc/calloc/realloc来分配堆内存,使用free来释放它。堆栈会在变量声明时自动分配,并在超出作用域时被释放。永远不要释放堆栈变量或指向它们的指针。 - Cervo
6个回答

29
在第一段代码中,内存在堆上动态分配,需要使用free()释放。它的生命周期是任意的:可以跨越函数边界等。
在第二段代码中,256个字节在栈上分配,并在函数返回时自动回收(如果它在所有函数之外则在程序终止时回收)。因此,您不需要(也不能)对其调用free()。它不会泄漏,但也不会存在于函数结束之后。
根据内存的要求选择两者之间的选择。
补充说明(Pax):
如果我可以补充一下,Ned,大多数实现通常会提供比堆栈更多的堆(至少默认情况下)。这对于256字节通常不重要,除非您已经耗尽了堆栈或正在进行大量递归处理。
另外,sizeof(char)始终根据标准为1,因此您不需要那个多余的乘法。即使编译器可能会优化它,但我认为它使代码变丑。
结尾附加说明(Pax)。

您也可以在所有函数之外声明内存,这样它就不会占用堆栈空间。同样,这取决于缓冲区所需的生命周期,以确定正确的分配方式。 - jmucchiello
如果在函数内部使用char[] foo,并且函数返回foo,那么会发生糟糕的事情。有一个陷阱的例子可能会很好。 - Calyth
2
在使用malloc时,我认为包含sizeof(type)是一个好习惯。如果使用sizeof()而不是您所暗示的方式,当类型更改时,某人很少会忘记更改分配的大小。 - strager
另外需要注意的是,在大多数机器上,第一种情况下name的大小为4(32位指针的大小)。而在第二种情况下,name的大小为256(256个字符数组的大小)。 - user3458
2
@strager:另一个常见的模式是使用char *foo = malloc(sizeof(*foo)),以使其更不可能。 - rpetrich
请原谅我的无知。如果我使用char *foo = malloc(sizeof(foo))来创建一个字符数组,并在使用后调用free,这会导致内存泄漏吗?sizeof( foo)返回1。因此分配的内存大小为1。如果我用它来存储字符串,然后释放,那么整个字符串会被释放还是只有第一个内存位置? - arundevma

7
不,只有第一个需要使用free()。第二个变量是在栈上分配的。这使得它分配速度非常快。请看这里:
void doit() {
    /* ... */
    /* SP += 10 * sizeof(int) */
    int a[10];
    /* ... (using a) */

} /* SP -= 10 */

当你创建一个数组时,编译器在编译时知道它的大小,并为其在栈上分配正确的大小。栈是位于某处的大块连续内存。将某物放在栈上只会增加(或减少,取决于平台)堆栈指针。超出作用域将执行相反操作,您的数组将被释放。这将自动发生。因此,以这种方式创建的变量具有自动存储期。
使用malloc则不同。它将从一个称为“freestore”的地方订购一些任意大的内存块。运行时将查找一个相当大的内存块。大小可以在运行时确定,因此编译器通常无法在编译时对其进行优化。由于指针可能超出作用域或被复制,因此内存分配和内存地址分配的指针之间没有固有的耦合关系,因此即使您很久以前就离开了函数,内存仍然分配。如果需要释放内存,则必须手动调用free并传递从malloc获得的地址。
一些名为C99的“最近”形式允许您为数组提供运行时大小。也就是说,您可以这样做:
void doit(int n) {
    int a[n]; // allocate n * sizeof(int) size on the stack */
}

但是如果没有使用它的理由,最好避免使用此功能。一个原因是它不是故障安全的:如果没有可用的内存,则可能发生任何事情。另一个原因是,C99在编译器之间不太具有可移植性。

我很好奇... C99特性和alloca()之间有什么关系?我知道大多数资源都建议避免使用alloca(),因为它具有不可见/破坏性的故障机制。 - Tom
C99特性只是一个内置的alloca,其优点在于内存会自动释放。但是所有相同的危险都存在 - 你必须非常小心地保持分配的内存量较小。 - James Hopkin
它的使用有限。例如,PCRE(正则表达式库)可以在内存分配方面使用它,而不是递归,据我所知。如果您不需要那种速度,最好使用malloc或固定大小数组。大多数情况下,这就是您想要的。 - Johannes Schaub - litb

6
这里有第三种可能性,就是数组可以在函数外静态地声明,例如:
// file foo.c
char name[256];

int foo() {
    // do something here.
}

在另一个SO问题的答案中,有人认为这在C语言中是不合适的,这让我感到相当惊讶;但在这里,它甚至没有被提及,我有点困惑和惊讶(就像“他们现在在学校教什么?”)。

如果您使用此定义,则内存是静态分配的,既不在堆上也不在栈上,而是在映像的数据空间中。因此,既不必像使用malloc/free一样进行管理,也不必担心地址会像自动定义一样被重用。

在这里,回想整个“声明”与“定义”的事情是有用的。以下是一个例子:

/* example.c */

char buf1[256] ;           /* declared extern, defined in data space */
static char buf2[256] ;    /* declared static, defined in data space */
char * buf3 ;              /* declared extern, defined one ptr in data space */
int example(int c) {       /* c declared here, defined on stack */
    char buf4[256] ;       /* declared here, defined on stack   */
    char * buf5 = malloc(256)]   /* pointer declared here, defined on stack */
                           /* and buf4 is address of 256 bytes alloc'd on heap */
    buf3 = malloc(256);    /* now buf3 contains address of 256 more bytes on heap */

    return 0;              /* stack unwound; buf4 and buf5 lost.      */
                           /* NOTICE buf4 memory on heap still allocated */
                           /* so this leaks 256 bytes of memory */
}

现在在一个完全不同的文件中

/* example2.c */

extern char buf1[];             /* gets the SAME chunk of memory as from example.c */
static char buf2[256];          /* DIFFERENT 256 char buffer than example.c */
extern char * buf3 ;            /* Same pointer as from example.c */
void undoes() {
     free(buf3);                /* this will work as long as example() called first */
     return ;
}

2

这是错误的 - 数组声明不需要释放。此外,如果这在一个函数内部,它会在堆栈上分配(如果我的记忆没有出错),并且在函数返回时会自动释放 - 不要将其引用传回调用者!


2

分解你的语句

char* name = malloc(256*sizeof(char)); // one statement
char *name; // Step 1 declare pointer to character
name = malloc(256*sizeof(char)); // assign address to pointer of memory from heap
name[2]; // access 3rd item in array
*(name+2); // access 3rd item in array
name++; // move name to item 1

翻译:name现在是一个指向字符的指针,它被赋予了堆上某些内存的地址。

char name[256]; // declare an array on the stack
name++; // error name is a constant pointer
*(name+2); // access 3rd item in array
name[2]; // access 3rd item in array
char *p = name;
p[2]; // access 3rd item in array
*(p+2); // access 3rd item in array
p++; // move p to item 1
p[0]; // item 1 in array

翻译:Name是一个指向堆栈上某个内存的字符常量指针。

C语言中,数组和指针本质上是相同的。数组是指向内存的常量指针。主要区别在于当您调用malloc时,会从堆中获取内存,而从堆中获取的任何内存必须从堆中释放。当您使用大小声明数组时,它会被分配在堆栈上的内存中。您无法释放此内存,因为free是用来释放从堆中获取的内存。在当前程序单元返回时,堆栈上的内存将自动释放。在第二个示例中,也不能释放p。p是指向堆栈上name数组的指针。因此,如果释放p,则会尝试释放堆栈上的内存。

这与以下情况没有区别:

int n = 10;
int *p = &n;

在这种情况下释放p将会是一个错误,因为p指向堆栈上的变量n。因此,p保存了堆栈中的内存位置,不能被释放。

int *p = (int *) malloc(sizeof(int));
*p = 10;
free(p);

在这种情况下,free是正确的,因为p指向在堆上由malloc分配的内存位置。

你是不是想说 int *p = (int *)malloc(sizeof(int)); 呢? :-) - anon
是的,我编辑了帖子,我很久没有使用malloc了 :) - Cervo

0

根据您运行代码的位置,堆栈空间可能非常宝贵。例如,如果您正在为Verizon/Alltel手机编写BREW代码,则通常受到微小堆栈的限制,但具有越来越多的堆访问权限。

此外,由于char[]最常用于字符串,因此允许字符串构造方法为所需的字符串分配内存而不是希望永远只需要256(或您指定的任何数字)并不是一个坏主意。


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