使用Malloc在C语言中定义结构体

7

我之前在Stack Overflow上提出了一个使用malloc定义结构体的问题。

大多数人给我的答案是:

struct retValue* st = malloc(sizeof(*st));

我正在向朋友展示我的代码,但我们遇到了一个难题。请问为什么这段代码能够运行?从我的角度来看,在你分配内存时,*st并没有被定义,所以里面可能会有任何一种垃圾数据。正确的应该是malloc(sizeof(struct retValue)) 感谢任何帮助。

你回答了自己的问题。sizeof(struct retValue)是正确的。 - Sam Post
1
抱歉,问题不是“这是否正确,或者什么是正确的?”而是“为什么这个有效?” - Blackbinary
2
如果您更改术语,可能会有所帮助。您不是使用malloc来定义结构,而是使用malloc来分配结构。您正在定义指针st(而不是结构)。该指针已经定义并可在初始化表达式中使用(在等号右侧),只是它没有值,因此大多数用途都无效。这个是可以的,因为sizeof不使用该值。 - Steve Jessop
4个回答

19

sizeof操作符只关注所给表达式的类型,不会对表达式进行求值。因此,您只需要确保在表达式中使用的变量已经声明,这样编译器就能推断出它们的类型。

在您的示例中,st已经声明为指向结构体retValue的指针。因此,编译器可以推断出表达式“*st”的类型。

尽管在您的代码中看起来似乎还没有声明,但编译器已经替您处理了这个问题。您的代码块中的所有声明都将被编译器移到其所在块的开头。假设您写了如下代码

展示编译器可用的知识的一种方法是查看它生成的中间输出。考虑以下示例代码...

struct retValue {long int a, long int b};
...
printf("Hello World!\n");
struct retValue* st = malloc(sizeof(*st));

以gcc为例,使用在test.c文件的main()函数中的上述代码,让我们来查看通过运行...得到的中间输出。

gcc -fdump-tree-cfg test.c
编译器会生成文件test.c.022t.cfg - 看一下,你就会看到。
[ ... removed internal stuff ...]
;; Function main (main)

Merging blocks 2 and 3
main (argc, argv)
{
  struct retValue * st;
  int D.3097;
  void * D.3096;

  # BLOCK 2
  # PRED: ENTRY (fallthru)
  __builtin_puts (&"Hello World!"[0]);
  D.3096 = malloc (16);
  st = (struct retValue *) D.3096;
  D.3097 = 0;
  return D.3097;
  # SUCC: EXIT

}
注意声明已经移动到块的开头,并且malloc的参数已经被替换为表示表达式求值类型大小的实际值。正如评论中指出的那样,声明被移动到块的顶部是编译器的实现细节。然而,编译器能够做到这一点,并且还能将正确的大小插入到malloc中,这表明编译器能够从输入中推断出必要的信息。
我个人更喜欢将实际类型名称作为sizeof的参数,但这可能是一种编码风格的问题,在这里我会说始终保持一致性比个人喜好更重要。

1
他的问题是关于 *st 做了“坏事”。如果 st 没有指向有效数据,那么 *st 是无效的。但由于 sizeof 不评估其参数,因此即使 stNULL,将 *st 用作 sizeof 的操作数也是可以的。 - Alok Singhal
实际上,问题是:“尽管在malloc需要知道要分配多少空间的时候,st不能被解引用,但它为什么/如何工作?” - VoidPointer
1
在代码的解析版本中,在块的开头放置声明是一项实现细节。编译器不需要这样做,即使你的编译器偶然这样做了,提问者的代码也可以正常工作。以下代码无法编译,表明声明并没有“真正”移动:sizeof(*st); char *st = 0; - Steve Jessop
除了Steve Jessop所说的,让我补充一点,决定sizeof是否有效的不是代码重排,而是在范围内的声明/定义。你的答案错误地将标准行为归因于实现细节。 - dirkgently
我已经重新表述了答案,使其不再强调重新排序是原因,而只是作为编译器能够从输入中推断的示例。感谢大家的反馈。 - VoidPointer

19

sizeof 运算符不会实际评估其操作数 - 它只是查看其类型。这是在编译时而不是运行时完成的。因此,它可以在变量被赋值之前安全地执行。


我想我明白了。所以你的意思是 sizeof 看着 *st 说:“哦,那是一个指针!”然后为指针分配足够的内存。它不在乎 *st 实际上持有什么。对吗? - Blackbinary
5
不,恰恰相反。*st的意思是“st指向的那个东西”,所以编译器返回的是结构体的大小,而不是指针的大小。 - Graeme Perrow
3
@Blackbinary 说的意思是:st 是一个指针,但 *st 是一个结构体。所以它查看 *st 并说“哦,那是一个 struct retValue!”然后分配足够的内存来容纳一个 retValue 结构体。*st 的实际内容并不重要。 - interjay
啊,就差一点了。谢谢。可能是我在打字时因为脑子里想着结构体而有些混淆了! - Blackbinary
sizeof操作符查看表达式的类型。st是“指向结构体retValue的指针”类型。*st是“结构体retValue”类型(*是指针解引用运算符)。因此,表达式的类型是“结构体retValue”,编译器将计算sizeof语句的该类型大小。 - VoidPointer
1
参数评估与此无关。 - dirkgently

1

重要的是结构类型的声明/定义,而不是这个类的对象的定义。当你到达malloc时,编译器已经遇到了一个声明/定义,否则你会遇到编译器错误。

sizeof不评估其操作数是一个次要问题。

一个小细节:记住当我们向sizeof提供类型名称时需要使用括号,例如:

sizeof(struct retValue);

而对于对象,我们只需执行:

sizeof *st;

请参阅标准:

6.5.3 一元运算符语法

unary-expression:
[...]
sizeof unary-expression
sizeof ( type-name )

0
在C语言中,sizeof是一个运算符,并且不会评估其参数。这可能会导致一些“有趣”的影响,对于新手来说可能无法预料。我在我的回答中详细提到了这一点,回答了“最奇怪的语言特性”问题。

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