关于内存分配良好做法的疑惑

Doubts about memory allocation good practices

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

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

假设我有一个“初始化”函数 Person_new,它创建一个新的 Person,它将 char *nameint agedouble *numbers 作为parameters, 为新的 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 时)。因此,我应该为其他两个参数分配一些内存,因为在我的函数中,我只为它们的指针分配了内存。那么,这个 malloc 应该由使用该功能的人还是由该功能本身制作?

通常,设计依赖于“软拷贝”的数据结构不是一个好主意。也就是说,您实际上并不是在制作传递数据的“硬拷贝”,您只是指向它,而实际分配是在别处完成的。由于多种原因,这是有问题的 - 例如,数据可能会变得无效。

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

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

严格来说你说的是对的(不需要分配两次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 的良好行为。

Should it also be allocating memory for the elements of the struct?

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

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

例如,数据可能来自某个其他函数读取的文件,每当从文件中读取“人”时,该函数就会调用您的 Person_new。在这种情况下,很可能 file-reader 函数在读取名称和数字时已经使用了动态分配。如果是这样,在 Person_new 内分配内存并再次复制所有数据将毫无意义。相反,只需将指针存储到“已分配”内存就可以了。

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

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

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

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

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