使用getline解析配置文件时随机出现内存泄漏问题求助
问题描述
使用getline解析配置文件时,随机出现内存泄漏,已确认在所有执行分支中释放了fileconfig_parser中初始分配的line内存,但泄漏仍随机发生。泄漏来自getline分配的内存,AddressSanitizer检测日志如下:
================================================================= ==1561==ERROR: LeakSanitizer: detected memory leaks Direct leak of 120 byte(s) in 1 object(s) allocated from: #0 0x7fad2918a808 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cc:144 #1 0x7fad28f0e543 in _IO_getdelim /build/glibc-SzIz7B/glibc-2.31/libio/iogetdelim.c:62 #2 0x7ffde0365e1f ([stack]+0x1ee1f) SUMMARY: AddressSanitizer: 120 byte(s) leaked in 1 allocation(s).
相关代码如下:
#define _GNU_SOURCE #include "parser.h" #include <stdlib.h> #include <string.h> struct global parse_global(char *line, FILE *file) { char *log_file = NULL; bool log = false; char *pid_file = NULL; size_t len; ssize_t nread; char *saveptr = NULL; while ((nread = getline(&line, &len, file)) != -1 && strcmp(line, "[[vhosts]]\n") != 0) { char *arg_name = strtok_r(line, " =", &saveptr); char *arg_value = strtok_r(NULL, " =\n", &saveptr); if (strcmp(arg_name, "log_file") == 0) { log_file = strdup(arg_value); } else if (strcmp(arg_name, "log") == 0) { if (strcmp(arg_value, "true") == 0) { log = true; } else { log = false; } } else if (strcmp(arg_name, "pid_file") == 0) { pid_file = strdup(arg_value); } } return global_init(log_file, log, pid_file); } struct vhost parse_vhost(char *line, FILE *file) { char *server_name = "my_server"; char *port = NULL; char *ip = NULL; char *root_dir = NULL; char *default_file = NULL; size_t len; ssize_t nread; char *saveptr = NULL; while ((nread = getline(&line, &len, file)) != -1 && strcmp(line, "\n") != 0) { char *arg_name = strtok_r(line, " =", &saveptr); char *arg_value = strtok_r(NULL, " =\n", &saveptr); if (strcmp(arg_name, "server_name") == 0) { server_name = strdup(arg_value); } else if (strcmp(arg_name, "port") == 0) { port = strdup(arg_value); } else if (strcmp(arg_name, "ip") == 0) { ip = strdup(arg_value); } else if (strcmp(arg_name, "root_dir") == 0) { root_dir = strdup(arg_value); } else if (strcmp(arg_name, "default_file") == 0) { default_file = strdup(arg_value); } } return vhost_init(server_name, port, ip, root_dir, default_file); } void free_global(struct global g) { free(g.pid_file); free(g.log_file); } void free_vhost(struct vhost v) { free(v.server_name); free(v.port); free(v.ip); free(v.root_dir); free(v.default_file); } struct server *fileconfig_parser(char *file) { FILE *f = fopen(file, "r"); if (!f) { perror("cannot open file"); return NULL; } char *line = NULL; size_t len = 0; ssize_t nread = getline(&line, &len, f); if (nread == -1 || strcmp(line, "[global]\n") != 0) { free(line); perror("invalid file"); fclose(f); return NULL; } struct global global = parse_global(line, f); if (global.pid_file == NULL) { free_global(global); fclose(f); free(line); return NULL; } struct vhost vhost = parse_vhost(line, f); if (vhost.server_name == NULL || vhost.port == NULL || vhost.ip == NULL || vhost.root_dir == NULL) { free_global(global); free_vhost(vhost); fclose(f); free(line); return NULL; } struct server *server = server_init(global, vhost); free(line); fclose(f); return server; }
问题原因
核心问题在于parse_global和parse_vhost的参数传递方式:
fileconfig_parser中创建的line指针是以值传递的方式传入两个解析函数的,这意味着函数内部的line是局部变量,仅初始指向外部line的内存,但二者并非同一个指针。- 在解析函数的
getline(&line, &len, file)调用中,当当前内存不足以容纳新行时,getline会分配新的堆内存,并修改局部的line指针指向新内存,但这个新指针不会同步回fileconfig_parser中的外部line。 - 函数返回后,这些在解析函数内部由
getline新分配的内存地址丢失,无法被释放,从而导致泄漏。 - 泄漏的随机性来自
getline的内存分配策略:只有当现有内存不足以容纳新行时才会分配新内存;若所有行都能被现有内存容纳,就不会触发泄漏。
修复方案
需要将line和len以指针的指针(char/size_t*)**传递给解析函数,确保getline修改的是fileconfig_parser中的原始指针,这样后续free(line)就能释放所有由getline分配的内存。同时修复server_name字符串字面量被free的潜在未定义行为。
1. 修改parse_global函数
struct global parse_global(char **line, size_t *len, FILE *file) { char *log_file = NULL; bool log = false; char *pid_file = NULL; ssize_t nread; char *saveptr = NULL; while ((nread = getline(line, len, file)) != -1 && strcmp(*line, "[[vhosts]]\n") != 0) { char *arg_name = strtok_r(*line, " =", &saveptr); char *arg_value = strtok_r(NULL, " =\n", &saveptr); if (strcmp(arg_name, "log_file") == 0) { log_file = strdup(arg_value); } else if (strcmp(arg_name, "log") == 0) { log = strcmp(arg_value, "true") == 0; } else if (strcmp(arg_name, "pid_file") == 0) { pid_file = strdup(arg_value); } } return global_init(log_file, log, pid_file); }
2. 修改parse_vhost函数
struct vhost parse_vhost(char **line, size_t *len, FILE *file) { char *server_name = NULL; // 初始化为NULL,避免释放字符串字面量 char *port = NULL; char *ip = NULL; char *root_dir = NULL; char *default_file = NULL; ssize_t nread; char *saveptr = NULL; while ((nread = getline(line, len, file)) != -1 && strcmp(*line, "\n") != 0) { char *arg_name = strtok_r(*line, " =", &saveptr); char *arg_value = strtok_r(NULL, " =\n", &saveptr); if (strcmp(arg_name, "server_name") == 0) { server_name = strdup(arg_value); } else if (strcmp(arg_name, "port") == 0) { port = strdup(arg_value); } else if (strcmp(arg_name, "ip") == 0) { ip = strdup(arg_value); } else if (strcmp(arg_name, "root_dir") == 0) { root_dir = strdup(arg_value); } else if (strcmp(arg_name, "default_file") == 0) { default_file = strdup(arg_value); } } return vhost_init(server_name, port, ip, root_dir, default_file); }
3. 调整vhost_init处理默认值
struct vhost vhost_init(char *server_name, char *port, char *ip, char *root_dir, char *default_file) { struct vhost v; // 若server_name为NULL,分配默认值的堆内存,确保可以安全释放 v.server_name = server_name ? server_name : strdup("my_server"); v.port = port; v.ip = ip; v.root_dir = root_dir; v.default_file = default_file; return v; }
4. 修改fileconfig_parser中的调用
struct server *fileconfig_parser(char *file) { FILE *f = fopen(file, "r"); if (!f) { perror("cannot open file"); return NULL; } char *line = NULL; size_t len = 0; ssize_t nread = getline(&line, &len, f); if (nread == -1 || strcmp(line, "[global]\n") != 0) { free(line); perror("invalid file"); fclose(f); return NULL; } struct global global = parse_global(&line, &len, f); if (global.pid_file == NULL) { free_global(global); fclose(f); free(line); return NULL; } struct vhost vhost = parse_vhost(&line, &len, f); if (vhost.server_name == NULL || vhost.port == NULL || vhost.ip == NULL || vhost.root_dir == NULL) { free_global(global); free_vhost(vhost); fclose(f); free(line); return NULL; } struct server *server = server_init(global, vhost); free(line); fclose(f); return server; }
总结
通过指针传递line和len,确保getline修改的是外部原始指针,后续free(line)就能释放所有getline分配的内存,解决随机泄漏问题。同时调整server_name的初始化逻辑,避免释放字符串字面量导致的未定义行为。
内容的提问来源于stack exchange,提问作者Nicolas
相关产品推荐
相关产品推荐

