【问题标题】:Doubts about memory allocation good practices对内存分配良好实践的质疑
【发布时间】:2021-10-31 17:24:48
【问题描述】:

假设我有一个声明类似于此的结构

typedef struct {
    char *name;
    int age;
    double *numbers; // A pointer to a list of doubles
} Person;

假设我有一个“初始化”函数Person_new,它创建一个新的Person,它将char *nameint agedouble *numbers 作为参数,为新的Person 分配内存并分配其参数值对应新Person的元素。

Person *Person_new(char *name, int age, double *numbers) {
    Person *self;
    self = malloc(sizeof(Person));
    if (self != NULL) {
        self->name = name;
        self->age = age;
        self->numbers = numbers;
    } 
    return self;
}

使用此功能的正确方法是什么?它还应该为结构的元素分配内存吗?我对此的理解是,由于age 不是一个指针,我不应该为此分配任何其他内存(因为我malloc 它时mallocing self)。因此我应该为其他两个参数分配一些内存,因为我的函数只为它们的指针分配了内存。那么,这个mallocs 应该是由使用该功能的人还是来自该功能本身?

【问题讨论】:

  • 这取决于您是要存储提供给函数的指针,还是复制它们指向的数据。您的代码执行前者。如果要复制数据,请使用mallocstrdup。例如,如果您正在解析文本文件并将指针传递给输入缓冲区数据,则需要复制该数据,因为行缓冲区将被覆盖。但是,如果您将字符串文字传递给函数,则可以将指针存储为给定的。
  • @WeatherVane 知识不够,无法批判性分析,这两种方法的优缺点是什么?如果我只是保留指针,然后函数的用户更改了它们指向的内容,那不会让一切都崩溃吗?
  • @Giuppox 这完全取决于你想做什么......你的功能对某些用例很好,而对另一些用例则错误......所以我们不能真正给你答案。
  • OT:你怎么知道numbers指向的内存中有多少个double....
  • @4386427 在我的实际实现中,还有另一个带有该信息的 int 参数

标签: c memory-management malloc


【解决方案1】:

一般来说,设计依赖“软拷贝”的数据结构是个坏主意。也就是说,您实际上并没有对传递的数据进行“硬拷贝”,您只是指向它,实际分配是在其他地方完成的。由于许多原因,这是有问题的 - 例如,数据可能会变为无效。

在某些情况下,“软拷贝”可能有意义,例如创建一个指向只读数据(如字符串文字)的容器。但一般来说,如果你正在创建一个具有读/写访问权限的容器,你应该复制所有传递的值,以便容器“拥有”它使用的所有内存。

在您的情况下,您似乎正在做一些简单的数据库,在这种情况下,您绝对应该分配传递的数据的副本。一般来说,分配代码的部分也负责释放它,因此您还需要提供清理功能。

【讨论】:

    【解决方案2】:

    严格来说你说的是对的(age不用分配两次,namenumbers需要分配)。

    但是,如果您愿意对这些字段施加长度限制,我建议您这样做:

    typedef struct {
        char name[MAX_NAME_LENGTH];
        int age;
        double numbers[MAX_NUMBERS_LENGTH];
    } Person;
    

    在这种情况下,当你 malloc 这个结构时,namenumbers 的内存也会被分配。然后,在您的Person_new 函数中,您可以将收到的指针中的namenumbers 值复制到结构的字段中,例如使用memcpy。 这样,结构使用的所有内存都归结构所有,这可以防止潜在的问题(如果在结构仍在使用时释放numbersname 的内存怎么办?)。这也意味着当您释放 Person 时,其字段的内存也将被释放,这也是 IMO 的一种良好行为。

    【讨论】:

      【解决方案3】:

      是否也应该为结构的元素分配内存?

      对此没有“一刀切”的答案。

      这取决于您的程序在做什么,例如数据来自哪里以及这些数据是如何读入系统的。

      作为一个例子,数据可能来自一个被其他函数读取的文件,每当从文件中读取“人”时,该函数调用你的Person_new。在这种情况下,文件读取器函数很可能在读取名称和数字时已经使用了动态分配。如果是这样,那么在Person_new 内分配内存并再次复制所有数据是没有意义的。相反,只需将指针存储到“已分配”内存即可。

      当你写一个函数的时候,你总是需要描述函数在做什么,描述函数调用的规则,即制定一个合约。可以这样写 namenumbers 必须是指向使用 malloc 获得的内存的指针,并且所有权作为调用函数的一部分进行转移。 (非标准但广泛使用的strdup 是转移已分配内存所有权的示例。

      但请注意,这会限制您的函数的使用方式。例如,您的函数不能与指向具有自动存储持续时间的数组的指针(即局部变量)一起使用,更糟糕的是:如果有人违反了您的合同并确实传递了指向局部变量的指针,那么这是一个无法被检测到的错误编译器。

      如果您的函数分配内存并复制数据,则无需对函数的调用方式设置此类限制。

      另一方面,如果“必须是指向动态分配内存的指针”限制适合您的用例,您可以避免不必要的分配和数据复制。

      所以 - 只是重复一遍 - 没有适合所有用例的单一解决方案 - 它取决于您程序的其余部分。

      【讨论】:

        猜你喜欢
        • 2015-04-01
        • 2013-12-05
        • 2023-03-25
        • 1970-01-01
        • 2011-02-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-09-14
        相关资源
        最近更新 更多