【问题标题】:Defining a Structure in C with Malloc用 Malloc 定义 C 中的结构
【发布时间】:2011-01-11 19:36:45
【问题描述】:

我向question earlier 询问了关于使用 malloc 定义结构的问题。 这是大多数人给我的答案:

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

我正在向一位朋友展示我的代码,但我们遇到了一个绊脚石。 有人可以解释为什么这段代码有效吗? 从我的角度来看, *st 在你 malloc 时没有被定义,所以那里可能有任何类型的垃圾。应该是malloc(sizeof(struct retValue))

感谢您的帮助

【问题讨论】:

  • 您回答了自己的问题。 sizeof(struct retValue) 是正确的
  • 抱歉,问题不是“这是正确的,还是正确的?”它是“为什么这行得通?”
  • 如果您更改术语可能有助于理解。您不是使用 malloc 定义结构,而是使用 malloc 分配结构。您正在定义st,它是一个指针(不是结构)。该指针已经定义并且可用于初始化表达式(在等号的 RHS 上),它只是没有值,因此大多数使用都是无效的。不过这个没关系,因为 sizeof 不使用该值。

标签: c memory malloc structure


【解决方案1】:

Sizeof 查看给它的表达式的类型,它不计算表达式。因此,您只需要确保声明了表达式中使用的变量,以便编译器可以推断出它们的类型。

在您的示例中,st 已被声明为指向 struct-retValue 的指针。因此编译器能够推断出表达式“*st”的类型。

虽然它看起来不像已经在您的代码中声明,但编译器已经为您处理好了。代码中的所有声明都被编译器移动到它们出现的块的开头。假设你写

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

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

以gcc为例,上面的代码在test.cmain()函数中,我们通过运行看看中间输出... p>

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 的参数已经替换为表示表达式评估为的类型大小的实际值。正如 cmets 中所指出的,将声明移到块顶部的事实是编译器的实现细节。然而,编译器能够做到这一点,并且能够在 malloc 中插入正确的大小,这一切都表明编译器能够从输入中推断出必要的信息。

我个人更喜欢将实际类型名称作为 sizeof 的参数,但这可能是编码风格的问题,我会说一致性胜过个人偏好。

【讨论】:

  • 他的问题是关于 *st 做“坏事”。如果st 未指向有效数据,则*st 无效。但是由于sizeof 不评估它的参数,所以可以使用*st 作为sizeof 的操作数,即使stNULL
  • 实际上,问题是“尽管 st 在 malloc 需要知道要分配多少空间时无法取消引用,但它如何/为什么工作”...
  • 在代码的解析版本中将声明放在块的开头是一个实现细节。编译器不需要这样做,即使您碰巧这样做,并且无论编译器是否这样做,提问者的代码都可以工作。以下代码无法编译,表明声明并未“真正”移动:sizeof(*st); char *st = 0;.
  • 除了 Steve Jessop 所说的之外,让我补充一点,确定sizeof 是否有效的不是代码重新排列,而是在范围内 的声明/定义。您的回答错误地将标准行为归因于实现细节。
  • 我已经改写了答案,以便它不再强调重新排序作为原因,而只是作为编译器能够从输入中推断出的示例。感谢您的反馈。
【解决方案2】:

在 C 中,sizeof 是一个运算符,不计算它的参数。这可能会导致“有趣”的效果,C 的新手不一定会预料到。我在my answer“最奇怪的语言功能”问题中更详细地提到了这一点。

【讨论】:

    【解决方案3】:

    重要的是结构类型的声明/定义,而不是此类对象的定义。当您到达malloc 时,编译器将遇到声明/定义,否则您会遇到编译器错误。

    sizeof 不计算其操作数这一事实是一个附带问题。

    一个小问题:请记住,当我们向sizeof 提供类型名称时,我们需要括号,如下所示:

    sizeof(struct retValue);
    

    而不是在对象的情况下,我们只是这样做:

    sizeof *st;
    

    查看标准:

    6.5.3 一元运算符语法

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

    【讨论】:

      【解决方案4】:

      sizeof 运算符实际上并不计算它的操作数——它只是查看它的类型。这是在编译时而不是运行时完成的。因此可以在分配变量之前安全地执行它。

      【讨论】:

      • 我想我明白了。所以你说sizeof 看着*st 说'哦,那是一个指针!然后为指针分配足够的内存。它并不关心 *st 实际上持有什么。对吗?
      • 不,恰恰相反。 *st 表示“st 指向的东西”,因此编译器返回结构的大小,而不是指针的大小。
      • @Blackbinary: Close: st 是一个指针,但*st 是一个结构。所以它看着*st 并说“哦,那是struct retValue!”然后为 retValue 结构分配足够的内存。 *st的实际内容无所谓。
      • 啊,这么近。谢谢。我打字的时候一定很困惑,因为我脑子里在想结构!
      • 参数评估与此无关。
      猜你喜欢
      • 2021-12-08
      • 2012-10-02
      • 2012-01-19
      • 2011-02-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-23
      • 1970-01-01
      相关资源
      最近更新 更多