You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

get_next_line函数实现存在内存泄漏,寻求修复方案

无法修复get_next_line的内存泄漏问题

我实现的get_next_line是一个学生项目,用于学习静态变量,功能是通过文件描述符逐行读取文件。函数逻辑在空文件、单字符文件、单行文件等场景下均正常,但内存泄漏问题始终存在。使用Valgrind检测(执行./a.out 5读取含4行文本的文件),结果显示程序退出时存在76字节的确定丢失内存,涉及malloc、strdup、ft_strjoin、ft_strcut等操作的内存分配。

我怀疑泄漏来自静态变量stash,但提前释放会导致无法读取剩余行;仅在返回最后一行时释放stash,且已确保释放了read相关的buffer,但读完最后一行后仍存在泄漏,对此感到困惑。


项目代码

gnl_utils.c

#include "get_next_line.h"

size_t  ft_strlen(const char *str)
{
    size_t  i;

    i = 0;
    while (str[i])
        i++;
    return (i);
}

char    *ft_strjoin(const char *s1, const char *s2)
{
    char    *str;
    size_t  size;
    int     i;
    int     j;

    size = ft_strlen(s1) + ft_strlen(s2);
    str = malloc(size * sizeof(char) + 1);
    if (!str)
        return (NULL);
    i = 0;
    j = 0;
    while (s1[i])
        str[j++] = s1[i++];
    i = 0;
    while (s2[i])
        str[j++] = s2[i++];
    str[j] = '\0';
    return (str);
}

char    *ft_strchr(const char *s, int c)
{
    while (*s != '\0')
    {
        if ((unsigned char)*s == (unsigned char)c)
            return ((char *)s);
        s++;
    }
    if (c == 0)
        return ((char *)s);
    return (0);
}

char    *ft_substr(const char *s, unsigned int start, size_t len)
{
    char    *str;
    int     i;
    int     y;

    if (start >= ft_strlen(s))
        start = ft_strlen(s);
    y = 0;
    while (start + y < start + len && s[start + y] != '\0')
        y++;
    i = 0;
    str = malloc((y) + 1);
    if (!str || start > ft_strlen(s))
        return (NULL);
    while (i < y)
    {
        str[i] = s[start];
        i++;
        start++;
    }
    str[i] = '\0';
    return (str);
}

char    *ft_strdup(const char *s)
{
    char    *dup;
    int     i;

    i = 0;
    dup = malloc(ft_strlen(s) + 1);
    if (!dup)
        return (NULL);
    while (s[i])
    {
        dup[i] = s[i];
        i++;
    }
    dup[i] = '\0';
    return (dup);
}

gnl.c

#include "get_next_line.h"

static int  check_line(char *str)
{
    int i;

    i = 0;
    if (!str)
        return (-1);
    while (str[i])
    {
        if (str[i] == '\n')
            return (1);
        i++;
    }
    return (-1);
}

static char *ft_strcut(char *str)
{
    char    *cutted_str;
    int     i;
    int     j;
    
    i = 0;
    j = 0;
    while (str[i] != '\n')
        i++;
    cutted_str = malloc(sizeof(char) * (ft_strlen(str) - i + 1));
    if (!cutted_str)
        return (NULL);
    i++;
    while (str[i])
        cutted_str[j++] = str[i++];
    cutted_str[j] = '\0';
    free(str);
    return (cutted_str);
}

char    *get_next_line(int fd)
{
    static char *stash;
    char        *buff;
    char        *line;
    int     readed;

    if (fd < 0 || BUFFER_SIZE <= 0)
        return (NULL);

    buff = malloc(sizeof(char) * (BUFFER_SIZE + 1));
    if (!buff)
        return (NULL);

    readed = read(fd, buff, BUFFER_SIZE);
    
    if (readed <= 0 && !stash)
        return (NULL);

    buff[readed] = '\0';
    if (!stash)
        stash = strdup(buff);
    else
        stash = ft_strjoin(stash, buff);
    free(buff);

    while (stash)
    {
        buff = malloc(sizeof(char) * (BUFFER_SIZE + 1));
        readed = read(fd, buff, BUFFER_SIZE);
        buff[readed] = '\0';
        stash = ft_strjoin(stash, buff);
        free(buff);

        if (readed < BUFFER_SIZE && check_line(stash) == -1)
        {
            line = ft_strdup(stash);
            stash = NULL;
            free(stash);
            return (line);
        }
        else if (check_line(stash) != -1)
        {
            line = ft_substr(stash, 0, ft_strchr(stash, '\n') - stash + 1);
            stash = ft_strcut(stash);
            return (line);
        }
    }
    return (NULL);
}

int main(int argc, char **argv)
{
    char    *line;

    int i = 0;
    int fd = open("text.txt", O_RDONLY);
    if (argc == 2)
    {
        while (i < atoi(argv[1]))
        {
            line = get_next_line(fd);
            printf("Ligne %d : %s\n", i + 1, line);
            free(line);
            i++;
        }
    }
    return (0);
}

核心泄漏点与修复方案

1. 关键泄漏原因

  • stash释放逻辑错误:最后一行处理时stash = NULL; free(stash);完全无效,先将stash置为NULL再释放等于没操作,原stash内存泄漏。
  • ft_strjoin内存泄漏:每次拼接stash = ft_strjoin(stash, buff);时,旧stash指针被覆盖,未释放原内存。
  • 未处理残留stash:若程序未读完所有行(如测试用例读5次但文件仅4行),stash中剩余数据未释放。
  • 初始read后的buff泄漏:当readed <=0 && !stash时,直接返回NULL未释放buff。

2. 修复后的get_next_line函数

char    *get_next_line(int fd)
{
    static char *stash;
    char        *buff;
    char        *line;
    char        *temp; // 保存旧stash地址,避免泄漏
    int         readed;

    if (fd < 0 || BUFFER_SIZE <= 0)
        return (NULL);

    buff = malloc(sizeof(char) * (BUFFER_SIZE + 1));
    if (!buff)
        return (NULL);

    readed = read(fd, buff, BUFFER_SIZE);
    if (readed <= 0 && !stash)
    {
        free(buff); // 先释放buff再返回
        return (NULL);
    }

    buff[readed] = '\0';
    if (!stash)
        stash = ft_strdup(buff);
    else
    {
        temp = stash;
        stash = ft_strjoin(stash, buff);
        free(temp); // 释放旧stash
    }
    free(buff);

    while (stash)
    {
        buff = malloc(sizeof(char) * (BUFFER_SIZE + 1));
        if (!buff)
        {
            free(stash); // 分配失败时清理stash
            stash = NULL;
            return (NULL);
        }
        readed = read(fd, buff, BUFFER_SIZE);
        buff[readed] = '\0';

        temp = stash;
        stash = ft_strjoin(stash, buff);
        free(temp);
        free(buff);

        // 处理最后一行
        if (readed < BUFFER_SIZE && check_line(stash) == -1)
        {
            line = ft_strdup(stash);
            free(stash); // 直接释放原stash
            stash = NULL;
            return (line);
        }
        // 处理带换行的行
        else if (check_line(stash) != -1)
        {
            line = ft_substr(stash, 0, ft_strchr(stash, '\n') - stash + 1);
            stash = ft_strcut(stash); // ft_strcut已内部释放旧stash
            return (line);
        }
    }
    return (NULL);
}

3. 补充:手动清理stash的接口

由于静态变量stash生命周期为整个程序,若需在读完文件后彻底清理,可新增辅助函数:

void gnl_clean(void)
{
    free(stash);
    stash = NULL;
}

在main函数循环结束后调用:

// main函数末尾
gnl_clean();
close(fd);

内容的提问来源于stack exchange,提问作者nokosse

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 17:50:34