使用malloc和不使用malloc创建结构体的区别

7

请问有人能向我解释使用 malloc 和不使用 malloc 创建结构的区别吗?什么情况下应该使用 malloc,什么情况下应该使用常规初始化方法?

例如:

struct person {

    char* name;

};

struct person p = {.name="apple"};

struct person* p_tr = malloc(sizeof(struct person));
p_tr->name = "apple";

什么是它们之间的真正区别?什么时候会使用其中一种方法而不是其他方法?

1
sizeof(struct person) or sizeof *p_tr are valid, but sizeof(person) is a syntax error. Best practice would be to use sizeof *p_tr - William Pursell
2
我认为最大的实际区别是 struct person p = {.name="apple"}; 创建的变量在封闭块结束时超出作用域(例如,如果这是在函数中,则内存在函数返回时变为无效)。而对于 struct person *p,变量 p 在封闭作用域结束时也变得无效,但它所指向的数据仍然有效。 - William Pursell
@WilliamPursell 如果我将指针 *ptr 存储在某个队列中,即使函数结束后尝试检索它,它仍将存在,但对于 person p 不是这种情况吗? - Amanda
当你需要更多地控制对象的生命周期时,可以使用malloc,而第一个版本无法给你这种控制。 - M.M
@WilliamPursell: 自动对象的生命周期在其关联块的执行结束时结束。这与执行离开作用域不同,因为执行可能通过子例程或中断/信号离开作用域。生存期是指程序执行期间对象存在的时间。范围是指标识符在源文本中可见的位置。 - Eric Postpischil
4个回答

6

拥有类似的数据结构;

struct myStruct {
    int a;
    char *b;
};

struct myStruct p;  // alternative 1
struct myStruct *q = malloc(sizeof(struct myStruct));  // alternative 2
  • 方案 1:在上分配myStruct内存空间,并将结构体的内存地址(即&p)返回给你,但是如果它是在函数中声明的,则其生命周期会在函数退出时结束(例如,如果函数超出范围,就无法访问它)。

  • 方案 2:在上分配myStruct宽度的内存空间,并在上分配类型为(struct myStruct*) 的指针宽度内存空间。指向的指针值被赋予结构体的内存地址(在堆上),并将此指针地址(而不是实际的struct地址)返回给您。它的生命周期直到使用free(q)来释放内存。

在后一种情况下,假设myStruct位于内存地址0xabcd0000,而q位于内存地址0xdddd0000;那么,内存地址0xdddd0000上的指针值被分配为0xabcd0000 ,并将其返回给您。

printf("%p\n", &p); // will print "0xabcd0000" (the address of struct)

printf("%p\n", q);  // will print "0xabcd0000" (the address of struct)
printf("%p\n", &q); // will print "0xdddd0000" (the address of pointer)

针对你提出的第二个问题;何时使用哪种方式进行如下解答:

  • 如果该结构体在函数内部并且你需要在函数退出后继续使用它,则需要使用malloc进行内存分配。可以通过返回指针的方式来使用该结构体的值,例如:return q;
  • 如果该结构体是临时的,并且在使用之后不再需要其值,则无需使用malloc进行内存分配。

示例用法:

struct myStruct {
    int a;
    char *b;
};

struct myStruct *foo() {
    struct myStruct p;
    p.a = 5;
    return &p; // after this point, it's out of scope; possible warning
}

struct myStruct *bar() {
    struct myStruct *q = malloc(sizeof(struct myStruct));
    q->a = 5;
    return q;
}

int main() {
    struct myStruct *pMain = foo();
    // memory is allocated in foo. p.a was assigned as '5'.
    // a memory address is returned.
    // but be careful!!!
    // memory is susceptible to be overwritten.
    // it is out of your control.

    struct myStruct *qMain = bar();
    // memory is allocated in bar. q->a was assigned as '5'.
    // a memory address is returned.
    // memory is *not* susceptible to be overwritten
    // until you use 'free(qMain);'
}

1
最好使用C标准的术语来回答C语言问题。malloc提供的内存是分配的。是一种特定的数据结构,而malloc的实现不一定使用堆。硬件栈在通用C实现中被广泛用于实现自动存储,但它们也不是C标准所必需的,并且对于资源受限的特殊系统(例如,特殊目的系统)的奇特实现可能不使用硬件栈。 - Eric Postpischil
@EricPostpischil:是的,你可能是对的。我在编译器内部方面并不是很专业。 - ssd

5

如果我们假设这两个例子都出现在一个函数内部,那么在:

struct person p = {.name="apple"};

这个C语言实现会自动为p分配内存,在函数执行结束时释放(或者,如果该语句在嵌套于该函数中的代码块中,则在该块执行结束时释放)。这对以下情况很有用:

  • 你正在处理大小适中的对象。(对于大型对象,使用许多kibibytes的内存,使用malloc可能更好。阈值取决于具体情况。)
  • 你一次只需处理少量对象。

在:

struct person* p_tr = malloc(sizeof(struct person));
p_tr->name = "apple";

程序显式请求对象的内存,通常应该在完成对象后使用 free 释放该内存。这在以下情况下很有用:
- 对象必须返回给函数调用方。自动对象在函数执行结束时将停止存在(在 C 的计算模型中;实际计算机内存并未停止存在,而是不再保留用于该对象),但是分配的对象将继续存在,直到程序释放它(或结束执行)。 - 对象非常大。(通常,C 实现提供更多的内存以供 malloc 分配,而不是为自动对象分配。) - 程序将创建变量数量的此类对象,具体取决于某些情况,例如从输入创建链表、树或其他结构,其大小在读取之前不知道。
注意,struct person p = {.name="apple"}; 使用 "apple" 初始化了 name 成员,并将所有其他成员初始化为零。然而,使用 malloc 并将其分配给 p_tr->name 的代码未初始化其他成员。
如果 struct person p = {.name="apple"}; 出现在函数外部,则会创建具有静态存储持续时间的对象。它将存在于程序执行期间。
我们建议使用 struct person *p_tr = malloc(sizeof *p_tr); 而不是使用 struct person* p_tr = malloc(sizeof(struct person));。对于前者,仅更改 p_tr 类型的一个地方仍将请求正确的大小,而在使用后者时,更改 p_tr 需要在两个位置进行编辑,这会让人容易犯错。

1
根据您的评论,我猜您对何时使用其中一种感兴趣。请注意,所有类型的分配都保留了足以容纳变量值的计算机内存。大小取决于变量的类型。编译器将静态分配的变量固定到内存中的一个位置。同样的编译器会将自动分配的变量固定到堆栈中的一个位置。在程序开始之前,动态分配的变量不存在,并且在它们被'malloc'或其他函数分配之前,在内存中没有任何位置。
所有具有名称的变量都是静态或自动分配的。动态变量由程序分配,但为了能够访问它们,仍然需要一个命名变量,即指针。指针是一个足够大的变量,可保存另一个变量的地址。后者可以动态、静态或自动分配。
问题是,如果您的程序在执行期间不知道需要使用多少个对象该怎么办。例如,如果您从文件中读取一些数据并在程序中创建动态结构,如列表或树,则不知道此类结构的成员数量。这是动态分配变量的主要用途。您可以创建所需数量的变量并将所有变量放在列表中。在最简单的情况下,您只需要一个命名变量,它指向列表的开头,以了解列表上的所有对象。
另一个有趣的用途是从函数返回复杂结构。如果自动分配在堆栈上,它将在从函数返回后停止存在。动态分配的数据将持久存在,直到显式释放为止。因此,在这里使用动态分配会有所帮助。
还有其他用途。
在您的简单示例中,两种情况之间没有太大区别。第二种情况需要额外的计算机操作,调用“malloc”函数来为结构分配内存。而在第一种情况中,结构的内存在程序启动时在静态程序区域中分配。请注意,第二种情况中的指针也是静态分配的。它只保留结构内存区域的地址。
此外,作为一般规则,动态分配的数据应最终由“free”函数释放。您无法释放静态数据。

1
struct person p = {.name="apple"};

^这是为类型为person的变量/实例自动分配内存。

struct person* p_tr = malloc(sizeof(person));

^这是一个类型为person的变量/实例的动态分配。

静态内存分配发生在编译时。 动态内存分配意味着当程序执行该指令时,它会在运行时分配内存。


我应该如何“使用”这个静态分配和动态分配的差别? - Amanda
有什么区别吗? - Amanda
1
你可以前往此链接查看解释:https://dev59.com/omsy5IYBdhLWcg3wsADQ#8385488。基本上,当你知道程序在其生命周期中需要多少内存、变量或实例时,最好预先分配内存,因为有多种原因,例如内存将在编译时预先分配,这将节省在运行时可能花费的分配时间。 - FahimAhmed
1
另一方面,动态内存分配使程序员在程序生命周期中对内存管理拥有更多的控制权。您可以在运行时分配一块内存,在使用完毕后,可以根据您的需求和选择简单地释放它或保留它。因此,它为您提供了更多的控制权,但同时也带来了更多的责任。 - FahimAhmed

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