c/c++ 中难以理解的语法!!为什么我们可以像这样 malloc 缓冲区?

Incomprehensible syntax in c/c++!! why can we malloc a buffer like this?

今天,我注意到一堆代码实例化了一个结构,如下所示,而 ServiceMsg 是一个结构。

ServiceMsg *msg = (ServiceMsg *)malloc(sizeof * msg);

没见过malloc这么用的。谁能给我解释一下??? 正如我所知,缓冲区的大小通常像这样分配 malloc(sizeof(int) * n) ..

在代码段中,

  ServiceMsg *msg = (ServiceMsg *)malloc(sizeof * msg);

相同
 ServiceMsg *msg = (ServiceMsg *)malloc( 1* sizeof(*msg));

最好写成

 ServiceMsg *msg = malloc(sizeof(*msg));

具体来说,malloc() 需要一个大小作为参数,在这个调用中,我们传递的大小是 sizeof(*msg),即 sizeof (ServiceMsg ).

也就是说,please see this discussion on why not to cast the return value of malloc() and family in C..

如果在表达式看起来像乘法表达式的地方重写这个调用会更清楚

malloc(sizeof * msg);

喜欢

malloc(sizeof *msg);

sizeof 运算符定义如下

sizeof unary-expression
sizeof ( type-name )

取消引用指针 msg 的表达式 *msg 是一元表达式。确定表达式的类型(在本例中为 ServiceMsg)并计算该类型对象的大小。不评估表达式本身。编译器只确定它的类型。

您可以将这个一元表达式括在括号中,得到一个像

这样的主表达式
malloc(sizeof ( *msg ) );

在此记录中,此记录与此记录相似

malloc(sizeof ( ServiceMsg ) );

有一些细节可能会让人感到困惑:

  • sizeof 运算符可以有两种使用方式:要么使用括号 sizeof(type),但参数必须是类似于 int 的类型。或者不带括号 sizeof expr,则参数可以是表达式。但是当然,表达式本身可以包含括号...

  • 在你的例子中 sizeof *msg 取表达式 *msg 的大小,这意味着取消引用指针 msg (它不是乘法运算符!)所以我们最终得到一个 ServiceMsg 类型的“左值”临时变量。其大小为 ServiceMsg.

  • 通常情况下,在声明的同一行取消引用一个指针变量是一个很大的禁忌,它没有被初始化并且还没有指向任何合理的位置。要注意的是 sizeof 运算符实际上并不计算(执行)它的操作数,因此表达式 *msg 永远不会被执行并且取消引用永远不会发生。

这就是代码有效的原因。这种风格甚至经常被推荐,因为你不必担心指针指向什么类型 - 无论你分配什么,它总是 type* ptr = malloc(sizeof *ptr);

在您的代码中转换为 (ServiceMsg*) 是多余的。

稍微清理一下,我们得到

ServiceMsg *msg = malloc(sizeof *msg);

完全相同
ServiceMsg *msg = malloc(N * sizeof *msg);

其中 N == 1。这是 C 中用于动态分配内存的相当常见的习惯用法:

T *p = malloc( N * sizeof *p ); // for any non-function type T

T *p;
...
p = malloc( N * sizeof *p );

T *p = calloc( N, sizeof *p );

T *p;
...
p = calloc( N, sizeof *p );

优点是类型信息不重复,维护起来更容易。

转换是不必要的1,并且由于表达式 *msgtypeServiceMessagesizeof *msg == sizeof( ServiceMessage ).

请记住 sizeof 是一个运算符,而不是一个函数 - 只有当操作数是类型名称如 ServiceMessageintchar 时才需要括号,或者如果它是像 1 + 2 这样的算术表达式。由于 sizeof 是一元运算符,它的优先级高于算术运算符,因此 sizeof 1 + 2 被解析为 (sizeof 1) + 2,这可能不是您想要的。


  1. 除非您将此代码编译为 C++ 或使用 ancient K&R 实现。在 C89 之前,*alloc 函数返回 char * 而不是 void *,因此如果目标不是 char *,则需要强制转换。 C++ 不允许从 void * 到其他指针类型的隐式转换,因此它也需要强制转换,但如果您正在编写 C++,则无论如何都不应该使用 C 风格的内存管理。