C 中为 malloc() 分配的指针数组中的 struct 成员赋值时为何崩溃?
崩溃是因为 malloc() 只分配了存放 MORPHEME * 的指针数组,数组元素还没有指向有效的 MORPHEME 对象。把未初始化的 morphemes[morphemesIndex] 传给 rawMor2mor() 后,通过 buffer->form 或 buffer->properties 写入成员会产生未定义行为。
可以保留 rawMor2mor(char *, MORPHEME *) 的参数形式,只需在调用前为对应的结构体分配空间。
为什么分配了数组,仍然不能给成员赋值?
这条语句分配的是 morphemesAmount + 1 个指针的存储空间:
MORPHEME **morphemes =
malloc(sizeof(MORPHEME *) * (morphemesAmount + 1));
这里涉及三层独立的存储:
morphemes指向的指针数组。- 每个
morphemes[i]指向的MORPHEME对象。 - 结构体中
form和properties指向的数据。
当前代码只分配了第一层。malloc() 返回的内存没有初始化,随后这句只设置了最后一个元素:
morphemes[morphemesAmount] = NULL;
前面的元素仍然没有有效值,因此下面的调用没有向函数提供可写的结构体对象:
rawMor2mor(morpheme, morphemes[morphemesIndex]);
函数中的这条赋值语句需要向 buffer 指向的结构体写入一个指针:
buffer->form = flexToStringMalloc(&formAnchor);
即使右侧函数成功分配了字符串,左侧也必须有有效的存储空间。为 properties 分配内存同样无法补上缺失的结构体对象。
未定义行为不一定会给出明确的错误信息,也不一定每次都在同一行崩溃。
保留指针数组:先为每个结构体分配空间
继续使用 MORPHEME ** 时,在循环中先为每个结构体分配空间,再调用 rawMor2mor()。
下面的代码沿用原来的输入处理方式,假定 morphemesAmount 非负,分配大小的计算不会溢出,且输入中存在预期的换行符。分配失败时直接终止程序。
#include <stdio.h>
#include <stdlib.h>
MORPHEME **morphemes =
malloc((morphemesAmount + 1) * sizeof *morphemes);
if (morphemes == NULL) {
perror("malloc");
exit(EXIT_FAILURE);
}
morphemes[morphemesAmount] = NULL;
for (int morphemesIndex = 0;
morphemesIndex < morphemesAmount;
++morphemesIndex) {
morphemes[morphemesIndex] =
malloc(sizeof *morphemes[morphemesIndex]);
if (morphemes[morphemesIndex] == NULL) {
perror("malloc");
exit(EXIT_FAILURE);
}
*morphemes[morphemesIndex] = (MORPHEME){0};
char *morpheme = flexGetStringMalloc(
&morphemesAnchor, 0,
flexSearchChar(&morphemesAnchor, '\n')
);
if (morpheme == NULL) {
fprintf(stderr, "获取 morpheme 失败\n");
exit(EXIT_FAILURE);
}
flexRemoveString(
&morphemesAnchor, 0,
flexSearchChar(&morphemesAnchor, '\n') + 1
);
rawMor2mor(morpheme, morphemes[morphemesIndex]);
free(morpheme);
}
两次分配中的 sizeof 对应不同的对象:
sizeof *morphemes /* 一个 MORPHEME * */
sizeof *morphemes[morphemesIndex] /* 一个 MORPHEME */
(MORPHEME){0} 将结构体中的两个指针成员初始化为空指针,便于后续管理,但不会为字符串或属性数组分配空间。
如果分配失败后需要返回调用者并继续运行,应先释放已经创建的对象,再返回错误。rawMor2mor() 内部也需要检查分配是否成功。如果需要报告失败,可以将返回类型改为状态码,并约定失败时由谁释放已分配的资源。
如果不需要独立分配,可以使用结构体数组
一次分配连续的 MORPHEME 对象,可以省去逐个分配结构体的步骤:
MORPHEME *morphemes = NULL;
if (morphemesAmount > 0) {
morphemes = malloc(morphemesAmount * sizeof *morphemes);
if (morphemes == NULL) {
perror("malloc");
exit(EXIT_FAILURE);
}
}
for (int i = 0; i < morphemesAmount; ++i) {
morphemes[i] = (MORPHEME){0};
/* 在这里保留原来获取 morpheme 的代码 */
rawMor2mor(morpheme, &morphemes[i]);
free(morpheme);
}
这里的数组元素是结构体,传参时使用 &morphemes[i],访问成员时使用:
morphemes[i].properties
这种方式通过 morphemesAmount 记录长度,不能再用 morphemes[morphemesAmount] = NULL 作为结束标记。分配大小的计算同样不能溢出。
参数传递需要改成二级指针吗?
如果调用者已经为结构体分配了空间,原来的函数签名就够用:
void rawMor2mor(char *rawMorpheme, MORPHEME *buffer);
C 按值传递指针。函数得到的是地址的副本,仍然可以通过这个地址修改同一个结构体。
如果在函数内部执行下面的语句,只会改变局部变量 buffer,不会更新调用者的数组元素:
buffer = malloc(sizeof *buffer);
如果希望由函数创建结构体并交回调用者,就需要返回 MORPHEME *,或者接收 MORPHEME ** 并传入 &morphemes[i]。
还需要修正的地方
原来的调试输出还有一个独立的问题:
printf("%i %i %i\n", i, MP_END,
morphemes[morphemesIndex]->properties);
properties 是指针,不能用 %i 输出。打印地址应使用 %p,并将指针转换为 void *:
printf("properties = %p\n",
(void *)morphemes[morphemesIndex]->properties);
格式说明符与实参类型不匹配,也会产生未定义行为。
指针数组使用完后,需要逐层释放内存。下面的代码假定每个成员都独立拥有对应的动态内存,且属性元素内部没有其他需要释放的资源:
for (int i = 0; i < morphemesAmount; ++i) {
free(morphemes[i]->form);
free(morphemes[i]->properties);
free(morphemes[i]);
}
free(morphemes);
如果使用连续的结构体数组,就释放 morphemes[i].form 和 morphemes[i].properties,最后释放整个数组,不要单独 free(&morphemes[i])。
调用后立即执行 free(morpheme),还要求 rawMor2mor() 保存的结果不再引用这块输入内存。仅凭所给片段无法确认这一点,需要检查相关函数是否复制了数据。
备注:内容仅供参考。
