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

C: Is my understanding about the specifics of heap and stack allocation correct?

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

的作用

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

当我们离开这个函数,继续执行main()函数时,是否*newnode从栈中移除(不确定'deallocated'是否正确),只留下struct node first指向堆上的'next'node struct?如果是这样,这个 'next' struct node 在堆栈或堆上是否也有一个变量名,或者它也只是指向一些内存?此外,是否可以说 struct node first 在堆栈上,而所有后续节点都在堆上,并且在 main() returns 0 之前没有 structs/variables在 struct node first 以外的堆栈上?或者 is/are 还有 1 个/多于 1 个 *newnode 还在堆栈中?

我确实尝试使用 GDB,它显示 struct node *newnode 两次都位于相同的内存地址 addnodeto() 被调用(所以它被删除然后恰好是 re-defined/allocated 在到相同的位置,或者也许是编译器很聪明,甚至在函数第一次退出后就把它留在那里,或者其他?),但我无法具体地解决其他问题。谢谢。


代码:

#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

符合预期。

My understanding is that calling malloc() sets aside some memory on the heap, and then returns the address of that memory (pointing to the beginning)…

是的,但是称其为“堆”的人对术语的理解很草率。堆是一种数据结构,如 linked 列表、二叉树或散列 table。堆可用于跟踪可用内存以外的其他用途,并且可以使用堆以外的数据结构来跟踪可用内存。

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

  • 他们目前从操作系统获取并正在管理的所有内存,包括当前分配给客户端的内存和已释放(尚未归还给操作系统)且可用于重用;
  • 当前分配给客户端的内存;
  • 当前可重用的内存;和
  • 正在管理的整个内存范围,包括部分虚拟地址 space 为将来映射保留,以便在必要时从操作系统请求更多内存。

我看到过用“pool”来形容这种内存,但是没有看到具体的定义。

… which is assigned to struct node *newnode which is itself on the stack.

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

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

When the function is first called, *nodetoaddto is a pointer to struct node first, both of which are on the stack.

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

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

Thus the (*nodeaddto)->next = newnode sets first.next equal to the value of newnode which is the address of the newly allocated memory.

是:在main中,last被初始化为指向first的指针。然后&last传递给addnodeto,所以nodeaddto是指向last的指针。所以 *nodeaddto 是指向 first 的指针。所以 (*nodeaddto)->next 是 `first.

中的 next 成员

When we leave this function, and continue executing the main() function, is *newnode removed from the stack (not sure if 'deallocated' is the correct word), leaving only struct node first pointing to the 'next' node struct on the heap?

newnode是一个在addnodeto里面自动存储时长的对象,所以它的内存会在addnodeto结束时自动释放。

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

If so, does this 'next' struct node have a variable name also on the stack or heap, or it is merely some memory pointed [to]?

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

Moreover, is it true to say that struct node first is on the stack, whilst all subsequent nodes will be on the heap,…

是的,受上面关于堆栈和“堆”的警告约束。

… and that just before main() returns 0 there are no structs/variables on the stack other than struct node first?

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

Or is/are there 1/more than 1 *newnode still on the stack?

没有