C语言联合体内存值异常变更:未操作成员为何非空?
你遇到的问题核心在于union(共用体)的内存共享特性。
在C语言中,union的所有成员会共享同一块内存空间——也就是说,obj->x.string和obj->x.filename其实指向的是内存中的同一个位置。当你在modify_struct里给obj->x.filename分配内存并赋值后,obj->x.string的值也会被覆盖成这个新的内存地址,不再是你初始化时的NULL了。
所以在free_all函数里,你同时检查string和filename是否非空,这两个条件都会成立,相当于尝试两次释放同一块内存(第一次释放string,第二次释放filename,但它们指向的是同一个地址),这就导致了重复释放内存的未定义行为,程序自然会出错。
另外还要提一下你代码里的一个小bug:malloc(strlen(value))只分配了和字符串长度相等的内存,但C语言的字符串需要额外的一个字节来存储终止符'\0',所以这里应该改成malloc(strlen(value) + 1),否则后续的memcpy会没有空间存放终止符,导致字符串操作出现未定义行为。
针对你的需求,有两种常见的修复方式:
方案一:把Union改成Struct(最直接)
如果你需要同时独立维护string和filename两个指针,那union并不适合你的场景,应该换成struct(结构体)——结构体的每个成员都有独立的内存空间,不会互相干扰:
// 修改结构体定义 typedef struct my_struct { struct { char *string; char *filename; } x; } my_struct_t;
这样修改后,obj->x.string和obj->x.filename是两个独立的指针,初始化时都设为NULL,修改其中一个不会影响另一个。你的free_all函数可以保持不变,因为现在两个指针是独立的,各自管理自己的内存。
同时记得修复modify_struct里的malloc问题:
void modify_struct(my_struct_t *obj, const char *value){ // 多分配一个字节存'\0' char *temp = malloc(strlen(value) + 1); // 用strcpy更方便,自动处理终止符 strcpy(temp, value); obj->x.filename = temp; }
方案二:保留Union,但添加标记位(如果必须用Union)
如果你确实需要用union(比如要节省内存,同一时间只使用其中一个指针),那需要额外添加一个标记位,记录当前union中哪个成员是有效的,释放时只释放对应的成员:
// 修改结构体,添加标记位 typedef enum { TYPE_STRING, TYPE_FILENAME, TYPE_NONE } my_type_t; typedef struct my_struct { my_type_t type; // 标记当前使用的是union的哪个成员 union { char *string; char *filename; } x; } my_struct_t;
然后修改创建、修改和释放函数:
my_struct_t *build_struct(){ my_struct_t *obj = malloc(sizeof(my_struct_t)); obj->type = TYPE_NONE; // 初始化union成员为NULL obj->x.string = NULL; return obj; } void modify_struct(my_struct_t *obj, const char *value){ // 如果之前有已分配的内存,先释放 if (obj->type == TYPE_FILENAME && obj->x.filename != NULL) { free(obj->x.filename); } else if (obj->type == TYPE_STRING && obj->x.string != NULL) { free(obj->x.string); } // 分配内存并赋值 char *temp = malloc(strlen(value) + 1); strcpy(temp, value); obj->x.filename = temp; obj->type = TYPE_FILENAME; // 更新标记位 } void free_all(my_struct_t *obj){ // 根据标记位释放对应的成员 if (obj->type == TYPE_FILENAME && obj->x.filename != NULL) { free(obj->x.filename); } else if (obj->type == TYPE_STRING && obj->x.string != NULL) { free(obj->x.string); } free(obj); }
这样就能确保每次只释放当前有效的那个指针,避免重复释放或释放未分配的内存。
内容的提问来源于stack exchange,提问作者Max

