【问题标题】:malloc-ing a structure in C that has structures as elements?在 C 中分配结构作为元素的结构?
【发布时间】:2019-04-04 01:57:01
【问题描述】:

说我有

struct node {
  struct example *left;
  struct example *right;
  int whatever;
};

struct example { 
  struct example *foo;
  struct example *bar;
}

当我这样做的时候

struct node *example_node = malloc(sizeof(struct node));

初始化我的节点结构,这个malloc实际上是做什么的?我知道它应该分配内存,以便 example_node 可以指向某个地址,该地址包含整个结构节点的足够字节......但是打开了什么?

是吗

a) 为结构的空白模板启动足够的空间

b) 结构节点内部的两个结构是否也已启动?那么我可以开始做类似 example_node->left->foo 的事情了吗?

c) left 和 right 的结构示例 *foo 是否也已启动?

我只是对我可以访问什么、我需要释放什么等感到困惑。

【问题讨论】:

  • malloc 获取未初始化内存的字节数;仅此而已
  • 另外你的标题与代码不匹配:你的结构有 pointers 作为元素,和一个 int,而不是结构
  • 对不起,我的意思是说结构指针。因此,如果 malloc 获得未初始化内存的字节数,这意味着只有指向结构节点 (example_node) 的指针被“打开”,对吗?但我对它的含义感到困惑。这是否意味着据 C 所知,struct node (example_node) 内部可能有一百万个数组,或者只是一个字符,或者 4 个字符串,无论如何,它只是一个指针?那我怎么能写 example_node->left = NULL;马上而不是必须为左边分配内存?我也可以做 example_node->right = NULL;
  • 我建议避免使用“为左侧分配内存”之类的短语,不清楚您是指left 的存储空间,还是left 指向的存储空间。也许您对这两者之间的区别也在您的脑海中感到困惑。您可以使用x->left = NULL; 的原因与您可以使用x->whatever = 0; 的原因相同
  • NULL 和 0 是否不需要任何内存?

标签: c


【解决方案1】:

考虑一下

  • sizeof(struct node) 给出了 node 结构的大小
  • malloc( N ) 从内存中分配 N 个字节

因此malloc(sizeof(struct node)) 至少分配了存储struct node 所需的字节数。

结构内部node

struct example *left;
struct example *right;
int whatever;

这是两个指针指向结构example 和一个整数。

因此分配的内存空间足够大,可以包含这两个指针和一个int。不是整个 example 结构,只有指针。

为了完成分配,你可能还想分配两个内部结构,比如

struct node *example_node = malloc(sizeof(struct node));

example_node->left  = malloc(sizeof(struct example));
example_node->right = malloc(sizeof(struct example));

你以相反的顺序释放这些分配,

  1. 开始免费leftright
  2. 然后释放node结构

一旦您释放了某些东西,它就不再可用。这可能有效,但这是未定义的行为。

因此,如果您首先释放 node,则无法可靠地访问依赖于 node(在内部)的 leftright 成员。

free (example_node->left);
free (example_node->right);

free (example_node);

【讨论】:

  • 嗯...我开始有点理解了,所以当我初始化“example_node”时,我根本没有具体说明,我只是想“好吧,打开足够的内存来存储有足够的空间容纳 2 个指针和一个 int”,而不是“为结构节点打开足够的内存,其中包括其他两个结构,其中包括其他结构和一个整数”
  • 没有递归分配。您所做的 malloc 仅分配 node 结构。 那么你必须单独分配内部成员。已编辑。
  • 好酷,当我做 free(example_node) 时,我也必须做 free(example_node->left);并且免费(example_node->right);
  • 哦,对了,我就是这个意思。这让我很困惑,为什么当我分配内存时,我所要做的就是 struct node *example_node = malloc(sizeof(struct node));,但是当我必须释放内存时,我必须单独释放所有东西?一旦我初始化了 example_node->left->foo,我也必须单独释放 foo?
  • 编辑了答案。
【解决方案2】:

假设你在一个 x64 环境中打包但没有对齐,那么sizeof( struct node ) == 20 因为8 + 8 + 4 == 20

sizeof( struct example* ) == 8        // Remember this struct member is a pointer, not a value
sizeof( struct example* ) == 8        // Ditto
sizeof( int             ) == 4        // `int` is usually 4 bytes

这个 malloc 实际上是做什么的?我知道它应该分配内存,以便 example_node 可以指向某个地址,该地址包含足够用于整个结构节点的字节

这是正确的。

在典型的桌面操作系统的用户空间中,您的代码将在进程的内存空间中运行。内存(通常)以操作系统提供的称为“页面”的大块的形式出现,C 运行时的malloc 将请求这些页面,然后管理这些页面的数据分配。

在桌面操作系统环境中,malloc( 20 ) 执行如下操作:

  • 可用页面中是否有足够的空间容纳连续的 20 个字节?
    • 是吗?然后使用我们的内部内存映射找到一个连续的 20 字节区域,并返回该区域第一个字节的地址。
    • 没有?然后向操作系统请求一个新页面,将其添加到我们的内部内存映射中,并返回该新页面中某个区域的地址
      • 如果新页面请求失败,则返回NULL

a) 是否有足够的空间来启动结构的空白模板

简短回答:是的 - 因为你做了malloc( sizeof( struct node ) )然而 malloc 可能会失败(在这种情况下,它会返回 NULL (0),重要的是你在之后检查这个每次分配。这可能是由于内存不足、碎片过多等造成的。

struct node *example_node = malloc(sizeof(struct node));
if( !example_node ) {
    puts( "Allocation failed. Exiting." );
    exit( 1 );
}

b) 结构节点内部的两个结构是否也已启动?那么我可以开始做类似 example_node->left->foo 的事情了吗?

没有。这些成员是指针。您需要自己初始化它们。您可以通过进一步调用malloc 或将它们分配给内存中其他现有的struct node 对象来初始化它们。

c) left 和 right 的结构示例 *foo 是否也已启动?

没有。看我上面的答案。

您的 struct nodestruct example 案例可以无限递归地填充 - 走多远取决于您。

创建一个 3 个节点深的简单二叉树:

struct node* allocateNode() {
    struct node* newNode = malloc( sizeof( struct node ) );
    if( !newNode ) exit( 1 ); // fast-fail
    newNode.left  = NULL; // zero-out the pointer members because `malloc` does not zero out memory for you!
    newNode.right = NULL;
    return newNode;
}

void initializeNode( struct node* parent ) {

    parent.left  = malloc( sizeof( struct node ) );
    parent.right = malloc( sizeof( struct node ) );
}

void createTree( struct node* parent, int depth ) {

    if( depth <= 0 ) return NULL;

    parent.left  = allocateNode();

    createTree( parent.left, depth - 1 );

    parent.right = allocateNode();

    createTree( parent.right, depth - 1 );
}

void destroyTree( struct node* node ) {

    if( node == NULL ) return;

    destroyTree( node.left );
    destroyTree( node.right );

    free( node );
}

int main( int argc, char* argv[] ) {

    struct node* root = allocateNode();

    createTree( root, 3 );

    // (do stuff here)

    destroyTree( root );

    return 0;
}

我注意到您可以通过使用calloc 并一次分配所有节点来更有效地做到这一点:

int main( int argc, char* argv[] ) {

    const size_t n = 7; // a tree 3 layers deep has 7 nodes: 1, 2L, 2R, 3LL, 3LR, 3RL, 3RR
    struct node[] allNodes = calloc( n, sizeof(struct node) ); 
    if( !allNodes ) exit( 1 );

    // Binary heap array representation algorithm:
    size_t lastParent = 3;
    for( size_t i = 0; i < n; i++ ) {

        if( i < lastParent ) {
            allNodes[i].left  = allNodes[ ( 2 * i ) + 1 ];
            allNodes[i].right = allNodes[ ( 2 * i ) + 2 ];
        }
        else {
            allNodes[i].left  = NULL;
            allNodes[i].right = NULL;
        }
    }

    return 0;
}

重要提示!!!!!!11111!

永远不要忘记为每次成功的malloccalloc 呼叫拨打free!否则会泄露内存!

【讨论】:

  • 所以我在上面评论了这个,但是为什么我被允许做类似 example_node->left = NULL 和 example_node->whatever = 0 之类的事情; ??当我空闲时,说我初始化 example_node->left,我必须说 free(example_node->left);免费(example_node); ????
  • 你说我可以走得很深,所以我可能不得不写一个循环来释放我分配的所有内容,对吧?
  • 你被“允许”在 C 中做任何事情,即使它没有任何意义——这只是意味着你的程序会崩溃(如果你幸运的话)或者你会损坏你的进程内存(“未定义的行为”)。
  • @ming 在处理树时,通常使用递归而不是循环。看看我提供的destroyTree函数。
  • 嗯,好的,谢谢。当我执行 sizeof(example) 时,我得到 24,而不是 20...
【解决方案3】:

您的代码将分配足够的内存来存储两个指针和一个整数。它不会为指向的两个指针分配内存。

那我怎么能写 example_node->left = NULL;立即而不是必须为 left 分配内存?

这将通过将NULL 写入分配给指针的内存来更改指针,就像example_node-&gt;whatever = 00 写入分配给整数的内存一样。它不会对节点结构本身之外的任何内存做任何事情。然而,如果你说example_node-&gt;left-&gt;foo = NULL,你会做坏事(tm)。


编辑:我认为您仍然有关于指针内存和指针下内存的问题。让我们尝试一个类比。想象一下你住在一栋公寓楼里,在那里你可以租用地下室的存储空间。还可以想象一下,每间公寓都是预先装修好的,其中一部分是入口旁边的钥匙架。钥匙架上有一个带有“存储钥匙”标签的挂钩。

如果你租了一个储藏室,你可以把它的钥匙放在合适的架子挂钩上。如果你没有储藏室,那么钩子是空的。无论您是否有储藏室,您都为储藏室分配了空间key(机架挂钩),它与储藏室本身是分开的。密钥钩子独立于存储空间而存在。

试图不使用钥匙打开储藏室是违法的。

试图用你碰巧发现建筑车库的钥匙打开陌生人的储藏室是非法的。

您只能打开一个您拥有的房间


node 是你的公寓:如果你分配了node(如果你有公寓),那么你就有了指针left 的空间(你有储藏室钥匙架挂钩)。 node-&gt;leftnode 分配中为其分配了内存(无论您是否租用存储空间,您都会获得存储密钥挂钩)。

您可以将NULL 分配给left(什么都不放在挂钩上),或者您可以分配一个指针值(在挂钩上放一个键)。

如果leftNULL,您不能执行example_node-&gt;left-&gt;foo = ...,因为空指针明确表示它没有指向任何分配的内存(无法打开任何没有一个键)。

可以example_node-&gt;left = 0xdeadbeef(把别人的储藏室钥匙挂在你的钩子上),但是你不能然后example_node-&gt;left-&gt;foo = ...因为@987654337的空间@ 很可能不是你可以篡改的(使用别人的储藏室)。

可以example_node-&gt;left = malloc(...)(租一个储藏室,把钥匙放在储藏室挂钩上),然后你可以example_node-&gt;left-&gt;foo = ...(因为你拥有房间和钥匙,如果你愿意,你可以在那里放新东西)。

【讨论】:

  • 那么.. example_node-left->NULL 与 example_node 一起初始化了吗?如果允许我将 NULL 写入“分配给指针的内存”?那么如果它与example_node一起初始化,那么example_node->left->foo为什么没有与example_node->left一起初始化
  • NULL 只是一个指针值,0x00000000。它标记了一个无效的指针,或者没有数据的指针;它在指针的目标处没有与之关联的任何内存。 (你不能写信给0x00000000)。您可以随意使用指针本身的内存(写入0x00000000 或写入例如0xdeadbeef);但除非该地址的内存已正确分配给您(例如malloc 返回0xdeadbeef),否则使用它是非法的。
猜你喜欢
  • 2012-12-05
  • 1970-01-01
  • 2012-10-30
  • 2017-06-12
  • 1970-01-01
  • 2019-07-19
  • 2013-06-05
  • 1970-01-01
相关资源
最近更新 更多