关于内存分配良好做法的疑惑
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 *name
、int age
和 double *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
它是 malloc
ing self
时)。因此,我应该为其他两个参数分配一些内存,因为在我的函数中,我只为它们的指针分配了内存。那么,这个 malloc
应该由使用该功能的人还是由该功能本身制作?
通常,设计依赖于“软拷贝”的数据结构不是一个好主意。也就是说,您实际上并不是在制作传递数据的“硬拷贝”,您只是指向它,而实际分配是在别处完成的。由于多种原因,这是有问题的 - 例如,数据可能会变得无效。
有些情况下“软拷贝”可能有意义,例如使容器指向只读数据(如字符串文字)。但一般来说,如果您正在创建一个具有 read/write 访问权限的容器,您应该复制所有传递的值,以便容器“拥有”它使用的所有内存。
在您的情况下,您似乎在做一些简单的数据库,在这种情况下,您绝对应该分配传递的数据的副本。通常分配的代码部分也负责释放它,因此您还需要提供清理功能。
严格来说你说的是对的(不需要分配两次age
,需要分配name
和numbers
)
但是,如果您愿意对这些字段施加长度限制,我建议您这样做:
typedef struct {
char name[MAX_NAME_LENGTH];
int age;
double numbers[MAX_NUMBERS_LENGTH];
} Person;
在这种情况下,当您 malloc
这个结构时,name
和 numbers
的内存也会被分配。然后,在您的 Person_new
函数中,您可以将收到的指针中的 name
和 numbers
值复制到结构的字段中,例如使用 memcpy
。
这样,该结构使用的所有内存都归该结构所有,这可以防止潜在的问题(如果 numbers
或 name
的内存在该结构仍在使用时被释放怎么办?)。这也意味着当您释放 Person
时,其字段的内存也将被释放,这也是 IMO 的良好行为。
Should it also be allocating memory for the elements of the struct?
对此没有“一刀切”的答案。
这取决于你的程序在做什么,例如数据来自哪里以及这些数据是如何读入系统的。
例如,数据可能来自某个其他函数读取的文件,每当从文件中读取“人”时,该函数就会调用您的 Person_new
。在这种情况下,很可能 file-reader 函数在读取名称和数字时已经使用了动态分配。如果是这样,在 Person_new
内分配内存并再次复制所有数据将毫无意义。相反,只需将指针存储到“已分配”内存就可以了。
你在写函数的时候,总是需要描述这个函数是干什么的,描述调用这个函数的规则,即订立契约。可以这样写 name
和 numbers
必须是指向使用 malloc
获得的内存的指针,并且所有权作为调用函数的一部分进行转移。 (没有标准但广泛使用的 strdup
是转移已分配内存所有权的示例)。
但请注意,这会限制您的函数的使用方式。例如,您的函数不能与指向具有自动存储持续时间的数组(即局部变量)的指针一起使用,更糟的是:如果有人违反您的合同并确实传递了指向局部变量的指针,那么这是一个无法被检测到的错误编译器。
如果您的函数分配内存并复制数据,则无需对函数的调用方式设置此类限制。
另一方面,如果“必须是指向动态分配内存的指针”限制适合您的用例,您可以避免不必要的分配和数据复制。
因此 - 重复一遍 - 没有适合所有用例的单一解决方案 - 这取决于您程序的其余部分。
假设我有一个声明类似于此的结构
typedef struct {
char *name;
int age;
double *numbers; // A pointer to a list of doubles
} Person;
假设我有一个“初始化”函数 Person_new
,它创建一个新的 Person
,它将 char *name
、int age
和 double *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
它是 malloc
ing self
时)。因此,我应该为其他两个参数分配一些内存,因为在我的函数中,我只为它们的指针分配了内存。那么,这个 malloc
应该由使用该功能的人还是由该功能本身制作?
通常,设计依赖于“软拷贝”的数据结构不是一个好主意。也就是说,您实际上并不是在制作传递数据的“硬拷贝”,您只是指向它,而实际分配是在别处完成的。由于多种原因,这是有问题的 - 例如,数据可能会变得无效。
有些情况下“软拷贝”可能有意义,例如使容器指向只读数据(如字符串文字)。但一般来说,如果您正在创建一个具有 read/write 访问权限的容器,您应该复制所有传递的值,以便容器“拥有”它使用的所有内存。
在您的情况下,您似乎在做一些简单的数据库,在这种情况下,您绝对应该分配传递的数据的副本。通常分配的代码部分也负责释放它,因此您还需要提供清理功能。
严格来说你说的是对的(不需要分配两次age
,需要分配name
和numbers
)
但是,如果您愿意对这些字段施加长度限制,我建议您这样做:
typedef struct {
char name[MAX_NAME_LENGTH];
int age;
double numbers[MAX_NUMBERS_LENGTH];
} Person;
在这种情况下,当您 malloc
这个结构时,name
和 numbers
的内存也会被分配。然后,在您的 Person_new
函数中,您可以将收到的指针中的 name
和 numbers
值复制到结构的字段中,例如使用 memcpy
。
这样,该结构使用的所有内存都归该结构所有,这可以防止潜在的问题(如果 numbers
或 name
的内存在该结构仍在使用时被释放怎么办?)。这也意味着当您释放 Person
时,其字段的内存也将被释放,这也是 IMO 的良好行为。
Should it also be allocating memory for the elements of the struct?
对此没有“一刀切”的答案。
这取决于你的程序在做什么,例如数据来自哪里以及这些数据是如何读入系统的。
例如,数据可能来自某个其他函数读取的文件,每当从文件中读取“人”时,该函数就会调用您的 Person_new
。在这种情况下,很可能 file-reader 函数在读取名称和数字时已经使用了动态分配。如果是这样,在 Person_new
内分配内存并再次复制所有数据将毫无意义。相反,只需将指针存储到“已分配”内存就可以了。
你在写函数的时候,总是需要描述这个函数是干什么的,描述调用这个函数的规则,即订立契约。可以这样写 name
和 numbers
必须是指向使用 malloc
获得的内存的指针,并且所有权作为调用函数的一部分进行转移。 (没有标准但广泛使用的 strdup
是转移已分配内存所有权的示例)。
但请注意,这会限制您的函数的使用方式。例如,您的函数不能与指向具有自动存储持续时间的数组(即局部变量)的指针一起使用,更糟的是:如果有人违反您的合同并确实传递了指向局部变量的指针,那么这是一个无法被检测到的错误编译器。
如果您的函数分配内存并复制数据,则无需对函数的调用方式设置此类限制。
另一方面,如果“必须是指向动态分配内存的指针”限制适合您的用例,您可以避免不必要的分配和数据复制。
因此 - 重复一遍 - 没有适合所有用例的单一解决方案 - 这取决于您程序的其余部分。