如何准确理解函数malloc()和calloc()的语法

4

我正在学习C语言,并对动态内存分配语法有些疑问。

下面的代码是一个动态内存分配的示例。如果我理解正确,

 (char *) malloc (50 * sizeof (char));

将返回指向char数据类型的指针

  pr = (char *) malloc (50 * sizeof (char));

将指针'pr'分配给动态内存分配时声明的指针。这样我们就可以让一个指针指向另一个指针。

#include <stdio.h>
#include <stdlib.h>

int main()
{
   char *pr;

   pr = (char*)malloc( 50 * sizeof(char) ); 

   return 0;
}

我仍然很难理解用于分配动态内存的语法。能有人详细解释一下吗?谢谢。


1
我需要对 malloc 的结果进行强制类型转换吗? - Sourav Ghosh
请注意,根据6.5.3.4 The sizeof and _Alignof operators, paragraph 4sizeof(char)的大小始终为1。该段落指出:“当sizeof应用于具有类型charunsigned charsigned char(或其限定版本)的操作数时,结果为1。” - Andrew Henle
3个回答

4

将指针“pr”分配给动态内存分配时声明的指针。因此,我们将有一个指针指向另一个指针

不是的,你会将指针变量存储的值存储下来

这与以下代码相同

int x = 5;

或者,在更复杂的情况下

int test(void) { return 5;}
int x = test();

在这种情况下,test()返回一个值5,该值被赋给x。同样的方式。
char * pr = malloc (50 * sizeof (*pr));  // no casting, and sizeof (*pr) is a
                                         // more robust way to have the size defined 

malloc() 返回的指针分配给 pr。这里没有指向指针的指针。
换句话说,pr 持有指向由调用 malloc() 分配的存储器位置的指针,或者在调用失败时持有 NULL(空指针常量)。

3
当您调用 malloc(X) 函数时,系统会尝试分配 X 字节的内存,并返回指向第一个字节的指针(如果分配成功,否则返回 NULL)。
当您调用 calloc(X, Y) 函数时,系统会尝试分配 X * Y 字节的内存,并将分配的内存的所有位都设置为零。这相当于先调用 malloc(X * Y),然后再调用 memset 函数。
我还建议您阅读 Do I cast the result of malloc? 这篇文章,它可以简单概括为:不需要进行强制类型转换。

关于是否应该放置强制转换,存在两种 不同的观点。 - JeremyP

2

首先,让我们澄清一些术语:

所以我们将有一个指针指向另一个指针

这是不正确的。当您将一个指针赋值给另一个指针时,第二个指针并不指向第一个指针,它指向与第一个指针相同的东西。指向指针意味着需要两个级别的解引用 - 在这种情况下 - 才能到达 char,即 char a = **pr;

因此,让我们看看代码。

pr = (char*)malloc( 50 * sizeof(char) ); 

malloc的原型是

void *malloc(size_t size);

malloc 函数分配 size 字节的内存,并返回指向该内存块的指针(如果无法分配内存,则返回NULL)。malloc 函数不知道您想指向什么类型的东西,因此它选择返回一个指向未知数据类型的void * 指针。您的对malloc 的调用前面的(char*)是一种类型转换操作。它将其右侧表达式的类型更改为括号中所包含的类型,在这种情况下为char *(可以在char*之间放入或留出空格而不会有任何影响)。

现在,指针的类型变为char *,随后被赋值给也是char *类型的pr

唯一需要注意的是,只要从void *到另一个指针类型,C会自动完成强制类型转换。因此,您所写的代码实际上与示例中的代码完全等价。

pr = malloc( 50 * sizeof(char) );

通常认为这种写法比直接进行强制类型转换更好。这样更易于阅读,也更加简洁。

过去还存在这样的问题:如果你忘记了包含#include <stdlib.h>,编译器会假定malloc返回一个int。如果你省略了强制类型转换,编译器会将从intchar *的尝试转换标记为错误,但是如果你加入了它,错误会被抑制。由于C11禁止使用未声明的函数,因此这个问题已经不存在了。

还有另一个问题,与上述问题相反。如果你有

char* pr;
// Lots of code
pr = malloc( 50 * sizeof(char) );
pr[49] = 0; 

如果你决定让pr指向int,你最终可能会得到以下结果:

int* pr;
// Lots of code
pr = malloc( 50 * sizeof(char) );
for (int i = 0 ; i < 50 ; ++i)
{
    pr[i] = 0; // buffer overflow!
} 

这段代码可以编译,但是由于分配的内存块不足以容纳50个整数,因此是未定义行为。你可以通过回归显式类型转换(int* pr = (char*)malloc(...) 是错误的)来解决这个问题,但更好的方法是对解引用指针进行sizeof操作。

int* pr;
// Lots of code
pr = malloc( 50 * sizeof *pr );
for (int i = 0 ; i < 50 ; ++i)
{
    pr[i] = 0; // OK if malloc returns non NULL, otherwise undefined behaviour, probably a SIGSEV
} 

callocmalloc类似,不同之处在于你需要分别提供指向的对象数量和指向的对象大小,而不是绝对的字节数。 calloc还会将分配块中的字节清零。

int *pr = calloc(50, sizeof *pr);

这段代码与前面的代码基本相同,唯一不同的是如果无法分配内存,它不会产生未定义行为。


NB: 很多人说你绝对不应该放置显式转换,但实际上有双方都有 争议。此外还有这个


@EricPostpischil 这是我在答案中首先解决的问题。 - JeremyP
1
抱歉,我不知道我怎么会错过那个。你是对的。 - Eric Postpischil

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