函数返回的char*调用free()是否安全?Linux内核示例及split实现探讨
核心原则
大型项目的内存管理遵循**「谁分配谁释放」**的核心规则,同时必须严格遵守函数文档明确标注的内存所有权约定——只有当函数明确说明返回值的内存由调用者负责释放时,才能调用free()(内核对应kfree());反之则绝对禁止。
可安全调用free()的场景(有文档/惯例支持)
这类场景的核心是:函数通过动态分配(malloc/kmalloc等)创建内存,并将所有权完全转移给调用者,文档会明确标注这一点。
示例1:明确标注返回动态分配内存的自定义函数
比如你实现的split函数,文档明确说明用malloc(3)分配内存并返回NULL结尾的字符串数组,此时调用者必须负责释放内存,正确用法如下:
char **tokens = split("a:b:c", ':'); if (!tokens) { // 处理分配失败 return; } // 使用分割后的字符串 for (int i = 0; tokens[i]; i++) { puts(tokens[i]); } // 释放内存:先释放每个子字符串,再释放数组本身 for (int i = 0; tokens[i]; i++) { free(tokens[i]); } free(tokens);
示例2:内核中明确转移所有权的函数
比如内核的kstrdup函数,文档标注返回由kmalloc分配的字符串副本,调用者需用kfree()释放,调用者可安全执行释放操作:
const char *original = "test string"; char *dup = kstrdup(original, GFP_KERNEL); if (dup) { // 使用dup kfree(dup); // 安全释放 }
示例3:解析类函数返回的动态结果数组
比如用户态的glob函数,返回的匹配路径数组是动态分配的,文档要求调用者用globfree释放(本质是封装了free逻辑);类似的,若内核中有自定义的配置解析函数返回动态字符串数组,且文档说明调用者需释放,即可安全操作。
禁止调用free()的场景
这类场景的核心是:返回的内存不属于调用者所有,要么是静态/全局内存,要么属于其他对象的一部分,要么由内部内存池管理。
示例1:返回静态/全局内存的函数
比如内核的linux_banner变量,返回的是编译时定义的全局静态字符串;用户态旧版本的strerror返回静态缓冲区。调用者若尝试free会直接触发崩溃:
// 错误示例:禁止free静态内存 const char *banner = linux_banner; free(banner); // 非法操作,会导致内存访问错误
示例2:返回其他对象的内部成员
比如内核的netdev_get_name函数,返回的是网络设备结构体struct net_device中的name字段,内存由内核管理,调用者不能释放:
struct net_device *dev = dev_get_by_name(&init_net, "eth0"); if (dev) { const char *name = netdev_get_name(dev); // 使用name,但不能free(name) dev_put(dev); // 只需释放设备引用,无需处理name内存 }
示例3:返回内存池/共享缓存的内存
内核中用slab分配器从共享缓存(如kmem_cache)分配的内存,不能直接用kfree释放,必须归还给对应缓存;若函数返回这类内存且文档未说明调用者可释放,禁止操作。
示例4:文档明确标注无需调用者释放的函数
比如一些临时缓冲区函数,内存由函数内部自动回收(如用alloca分配的栈内存,或内部静态缓冲区复用),调用者若free会破坏内存结构。
你的split函数最佳实践补充
为了符合大型项目的惯例,需在函数文档中明确以下细节:
- 数组及内部字符串均由
malloc分配; - 调用者必须先遍历数组释放每个字符串,再释放数组本身;
- 返回NULL时无需任何释放操作;
- 若中途分配失败,函数内部需自动释放已分配的内存,避免泄漏。
完善后的函数注释示例:
/** * split - Split a string into an array of substrings * @s: Input string to split * @c: Delimiter character * * Allocates memory with malloc(3) for the array and each substring. * The returned array is NULL-terminated. * * Return: NULL-terminated array of substrings on success; NULL on allocation failure. * Caller must free each substring first, then the array itself. * If NULL is returned, no memory needs to be freed. */ char **split(const char *s, char c) { // 实现逻辑:分配数组,分割字符串,若中途失败则释放已分配内存 }
内容的提问来源于stack exchange,提问作者Andrei

