C:我对堆和堆栈分配细节的理解是不是正确?

Posted

技术标签:

【中文标题】C:我对堆和堆栈分配细节的理解是不是正确?【英文标题】:C: Is my understanding about the specifics of heap and stack allocation correct?C:我对堆和堆栈分配细节的理解是否正确? 【发布时间】:2021-12-04 12:58:14 【问题描述】:

我在 C 中实现了一种链表(代码在底部)(这有明显的问题,但我不是在询问这些或链表;例如,我知道没有对 @ 的调用987654321@分配的内存)下面给出了我所期望的(据我检查)。我的问题是关于 addnodeto() 函数的前几行以及它对堆/堆栈的作用。


我的理解是调用malloc()会在堆上留出一些内存,然后返回分配给struct node *newnode的那个内存的地址(指向开头),它本身就在堆栈上。第一次调用函数时,*nodetoaddto 是指向struct node first 的指针,两者都在堆栈上。因此(*nodeaddto)->next = newnode 设置first.next 等于newnode 的值,即新分配内存的地址。

当我们离开这个函数并继续执行main() 函数时,*newnode 从堆栈中移除(不确定“deallocated”是否是正确的词),只留下指向“下一个”的 struct node first node struct 在堆上?如果是这样,这个“下一个”struct node 在堆栈或堆上是否也有一个变量名,或者它也只是指向一些内存?此外,是否真的说struct node first 在堆栈上,而所有后续节点都将在堆上,并且就在main() returns 0 之前,堆栈上除了struct node first 之外没有结构/变量?还是/是否有 1 个/超过 1 个 *newnode 仍在堆栈中?

我确实尝试过使用 GDB,它显示 struct node *newnode 在两次调用 addnodeto() 时都位于同一个内存地址(所以它被删除然后碰巧被重新定义/分配到同一个位置,或者也许编译器很聪明,即使第一次退出函数,还是把它留在那里,还是其他?),但我无法具体解决任何其他问题。谢谢。


代码:

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

#define STR_LEN 5

struct node 
    char message[STR_LEN];
    struct node *next;
;

void addnodeto(struct node **nodeaddto, char letter, int *num_of_nodes)

    struct node *newnode = malloc(sizeof(struct node));
    (*nodeaddto)->next = newnode;
    newnode->message[0] = letter;

    (*nodeaddto) = newnode;

    *num_of_nodes += 1;


int main(void)
    struct node first = "F", NULL;
    struct node *last = &first;
    int num_nodes = 1;

    addnodeto(&last, 'S', &num_nodes);
    addnodeto(&last, 'T', &num_nodes);
    addnodeto(&last, 'I', &num_nodes);

    printf("Node: %d holds the char: %c\n", num_nodes-3, first.message[0]);
    printf("Node: %d holds the char: %c\n", num_nodes-2, (first.next)->message[0]);
    printf("Node: %d holds the char: %c\n", num_nodes-1, ((first.next)->next)->message[0]);
    printf("Node: %d holds the char: %c\n", num_nodes, (last)->message[0]);

    return 0;

运行时输出:

Node: 1 holds the char: F
Node: 2 holds the char: S
Node: 3 holds the char: T
Node: 4 holds the char: I

正如预期的那样。

【问题讨论】:

(*nodeaddto)-&gt;next = newnode; 替换为newnode-&gt;next = *nodeaddto; 为什么选择@wildplasser?代码按预期工作(如问题中所述),直到我进行更改,此时程序会导致段错误。 【参考方案1】:

我的理解是调用malloc()会在堆上留出一些内存,然后返回那个内存的地址(指向开头)……

是的,但称其为“堆”的人对术语的使用过于草率。堆是一种数据结构,如链表、二叉树或哈希表。堆可以用于跟踪可用内存以外的其他事情,可用内存可以使用堆以外的数据结构进行跟踪。

我实际上并不知道内存管理例程管理的内存的特定术语。实际上有几组不同的记忆我们可能需要术语:

到目前为止他们从操作系统获得并正在管理的所有内存,包括当前分配给客户端的内存和已释放(尚未返回给操作系统)且可供重用的内存; 当前分配给客户端的内存; 当前可重用的内存;和 正在管理的整个内存范围,包括为将来需要从操作系统请求更多内存时进行映射而保留的部分虚拟地址空间。

我见过用来描述这种内存的“池”,但还没有看到它的具体定义。

... 分配给 struct node *newnode ,它本身就在堆栈中。

struct node *newnode 在常见的 C 实现中确实名义上在堆栈上。但是,C 标准仅将其归类为自动存储持续时间,这意味着其内存由 C 实现自动管理。堆栈是最常见的实现方式,但专门的 C 实现可能会以其他方式实现。此外,一旦编译器优化了程序,newnode 可能不在堆栈上;编译器可能会生成只将其保存在寄存器中的代码,还有其他可能性。

这里的复杂之处在于,当我们谈论 C 程序中的内存使用时,我们可以谈论 C 标准用于描述程序语义或实际实践中的内存使用的模型计算机中的内存使用。例如,正如 C 标准所描述的那样,每个对象在其生命周期内都会为其保留一些内存。然而,当一个程序被编译时,编译器可以生成它想要的任何代码,并获得与 C 标准要求的结果相同的结果。 (程序的输出必须相同,并且某些其他交互必须表现相同。)因此编译器可能根本不为对象使用内存。优化后,一个对象可能一次在内存中,另一次在寄存器中,或者它可能一直在一个寄存器中,从不在内存中,它可能在不同的时间在不同的寄存器中,它可能不是任何特定的地方因为它可能已被纳入其他事物。例如,在int x = 3; printf("%d\n", 4*x+2); 中,编译器可能会完全消除x,只打印“14”。因此,在询问内存中的内容时,您应该清楚是要讨论 C 标准使用的模型计算机中的语义还是优化程序中的实际实践。

函数第一次调用时,*nodetoaddto首先是指向struct node的指针,两者都在栈上。

nodetoaddto 可能在堆栈上,如上所述,但它也可能在寄存器中。函数参数通常在寄存器中传递。

它指向 a struct nodestruct node 本身就是一个类型,所以它只是一个概念,而不是指向的对象。相反,“a struct node”是该类型的对象。该对象可能在也可能不在堆栈上; addnodeto 不在乎;无论它在内存中的什么位置,它都可以链接到它。您的main 例程确实创建了具有自动存储持续时间的firstlast 节点,但它也可以使用static,然后这些节点可能位于内存的不同部分而不是堆栈中,而addnodeto 不会在意。

因此(*nodeaddto)-&gt;next = newnode 设置first.next 等于newnode 的值,即新分配内存的地址。

是:在main 中,last 被初始化为指向first 的指针。然后&amp;last 被传递给addnodeto,所以nodeaddto 是一个指向last 的指针。所以*nodeaddto 是一个指向first 的指针。所以(*nodeaddto)-&gt;next 是`first 中的next 成员。

当我们离开这个函数并继续执行main() 函数时,*newnode 从堆栈中删除(不确定“deallocated”是否是正确的词),只留下指向“下一个”的 struct node first node struct 在堆上?

newnodeaddnodeto内部自动存储时长的对象,所以addnodeto结束时会自动释放其内存。

*newnode 是一个struct node,具有分配的存储持续时间,因此函数结束时不会释放其内存。它的内存在调用free 时被释放,或者可能是其他一些可能释放内存的例程,如realloc

如果是这样,这个“下一个”struct node 是否在堆栈或堆上也有一个变量名,或者它只是一些指向 [to] 的内存?

堆栈或堆中没有变量名。变量名仅存在于源代码中(在编译器中以及与已编译程序相关的调试信息中,但该调试信息通常与程序的正常执行是分开的)。当我们使用分配的内存时,我们通常只通过指向它的指针来使用它。

此外,是不是说struct node first 在堆栈上,而所有后续节点都在堆上,...

是的,但要遵守上面关于堆栈和“堆”的警告。

...在main() returns 0 之前,堆栈上除了struct node first 之外没有结构/变量?

main 中的所有自动对象都在堆栈上(或以其他方式自动管理):firstlastnum_nodes

或者堆栈中是否还有 1 个/多于 1 个 *newnode

没有。

【讨论】:

以上是关于C:我对堆和堆栈分配细节的理解是不是正确?的主要内容,如果未能解决你的问题,请参考以下文章

Swift 栈和堆的理解

美团面试题解析:用final 考验你对堆和栈的理解

美团面试题解析:用final 考验你对堆和栈的理解

美团面试题解析:用final 考验你对堆和栈的理解

理解JavaScript中的堆和栈

数据结构中的堆和栈 与 内存分配中的堆区和栈区 分析