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

如何解决C语言get_next_line函数的超时及malloc检测问题?

Get_next_line 大文件超时与Malloc保护错误排查

问题描述

我实现了get_next_line函数,通过文件描述符读取文件,BUFFER_SIZE由用户指定。函数每次读取一行并返回,剩余内容存储在static变量中供下次调用使用。基础功能正常,但使用Paco测试工具测试大文件(giant_line.txt、giant_line_nl.txt)时出现超时问题,且触发malloc保护检查错误,无法定位问题原因,寻求解决方案。

实现代码

#include "get_next_line.h"

static size_t   ft_gnl_strlen(char *ptr, char sep)
{
    size_t  i;

    i = 0;
    while (ptr && ptr[i] && ptr[i] != sep)
        i++;
    if (ptr && ptr[i] == '\n')
        i++;
    return (i);
}

char    *ft_gnl_strchr(const char *p, char ch)
{
    while (*p != '\0')
    {
        if (*p == (char)ch)
            return ((char *)p);
        p++;
    }
    return (NULL);
}

static void ft_gnl_change_reminder(char *helper)
{
    char    ptr[BUFFER_SIZE + 1];
    size_t  i;
    size_t  j;

    i = 0;
    j = 0;
    while (helper[i] != '\n' && helper[i])
        i++;
    if (helper[i] == '\n' && helper[i])
        i++;
    while (helper[i])
        ptr[j++] = helper[i++];
    while (j <= BUFFER_SIZE)
        ptr[j++] = '\0';
    i = -1;
    while (ptr[++i])
        helper[i] = ptr[i];
    while (i <= BUFFER_SIZE)
        helper[i++] = '\0';
}

static char *ft_gnl_join(char *ptr, char *helper)
{
    char    *newptr;
    size_t  i;
    size_t  ptrlen;

    if (!ptr && !helper)
        return (free(ptr), NULL);
    i = -1;
    ptrlen = ft_gnl_strlen(ptr, '\0');
    newptr = (char *)malloc(ptrlen + ft_gnl_strlen(helper, '\n') + 1);
    if (!newptr)
        return (free(ptr), NULL);
    while (ptr && ptr[++i])
        newptr[i] = ptr[i];
    i = 0;
    while (helper && helper[i] != '\n' && helper[i])
        newptr[ptrlen++] = helper[i++];
    if (helper && helper[i] && helper[i] == '\n')
        newptr[ptrlen++] = '\n';
    newptr[ptrlen] = '\0';
    return (free(ptr), ft_gnl_change_reminder(helper), newptr);
}

char    *get_next_line(int fd)
{
    static char helper[BUFFER_SIZE + 1];
    char        *ptr;

    if (fd < 0 || read(fd, helper, 0) < 0 || BUFFER_SIZE < 1)
        return (helper[0] = '\0', NULL);
    ptr = (char *)malloc(1);
    if (!ptr)
        return (NULL);
    ptr[0] = '\0';
    if (ft_gnl_strlen(helper, '\0'))
    {
        ptr = ft_gnl_join(ptr, helper);
        if (!ptr)
            return (NULL);
    }
    while (!ft_gnl_strchr(ptr, '\n') && read(fd, helper, BUFFER_SIZE))
    {
        helper[BUFFER_SIZE] = '\0';
        ptr = ft_gnl_join(ptr, helper);
        if (!ptr)
            return (NULL);
    }
    if (ptr[0] == '\0')
        return (free(ptr), NULL);
    return (ptr);
}

测试结果

BUFFER_SIZE: 10

giant_line.txt      : 1.OK 2.OK 3_LEAKS.OK 4.KO Timeout

giant_line_nl.txt   : 1.OK 2.OK 3.OK 4_LEAKS.OK 5.KO Timeout

报错信息

Errors found:
Error in test 4: get_next_line(5: "giant_line.txt"):
malloc protection check for 594th malloc:
     in _add_malloc malloc_mock.c:29:8
     in malloc malloc_mock.c:75:10
  -> in ft_gnl_join get_next_line.c:71:9
     in get_next_line get_next_line.c:105:7
     in null_check_gnl file_utils.c:116:7
     in main tester.c:173:3

Error in test 5: get_next_line(5: "giant_line_nl.txt"):
malloc protection check for 597th malloc:
     in _add_malloc malloc_mock.c:29:8
     in malloc malloc_mock.c:75:10
  -> in ft_gnl_join get_next_line.c:71:9
     in get_next_line get_next_line.c:105:7
     in null_check_gnl file_utils.c:116:7
     in main tester.c:181:3

问题分析

1. 超时问题原因

代码每次拼接字符串时都会重新分配内存并复制全部已有内容,对于超大行(如几MB级别的单行),这种方式的时间复杂度为O(n²)。随着读取内容增多,内存分配和全量复制的开销急剧增加,最终导致测试超时。

2. Malloc保护检查错误原因

ft_gnl_change_reminder函数中使用了栈上固定大小数组ptr[BUFFER_SIZE + 1],当BUFFER_SIZE较大时会触发栈溢出;即使BUFFER_SIZE较小,也可能因内存操作的边界问题被测试工具的malloc mock检测到越界,触发保护错误。


解决方案

方案1:优化内存分配策略(解决超时)

重构字符串拼接逻辑,使用动态扩容的缓冲区,减少内存分配和复制次数:

#include "get_next_line.h"

static size_t ft_gnl_strlen(const char *s, char sep)
{
    size_t i = 0;
    while (s && s[i] && s[i] != sep)
        i++;
    if (s && s[i] == '\n')
        i++;
    return i;
}

static char *ft_gnl_strchr(const char *s, int c)
{
    if (!s)
        return NULL;
    while (*s)
    {
        if (*s == (char)c)
            return (char *)s;
        s++;
    }
    return NULL;
}

static void ft_update_remainder(char *remainder, const char *src)
{
    size_t i = 0;
    while (src[i])
    {
        remainder[i] = src[i];
        i++;
    }
    while (i <= BUFFER_SIZE)
        remainder[i++] = '\0';
}

static char *ft_realloc(char *ptr, size_t old_size, size_t new_size)
{
    char *new_ptr = malloc(new_size);
    if (!new_ptr)
    {
        free(ptr);
        return NULL;
    }
    if (ptr)
    {
        size_t i = 0;
        while (i < old_size && i < new_size)
        {
            new_ptr[i] = ptr[i];
            i++;
        }
        free(ptr);
    }
    return new_ptr;
}

char *get_next_line(int fd)
{
    static char remainder[BUFFER_SIZE + 1];
    char *line = NULL;
    size_t line_len = 0;
    size_t line_cap = 0;
    ssize_t read_bytes;

    if (fd < 0 || BUFFER_SIZE < 1 || read(fd, NULL, 0) < 0)
    {
        ft_update_remainder(remainder, "");
        return NULL;
    }

    // 处理剩余内容
    if (remainder[0] != '\0')
    {
        size_t part_len = ft_gnl_strlen(remainder, '\n');
        // 扩容缓冲区
        if (line_len + part_len >= line_cap)
        {
            line_cap = line_len + part_len + BUFFER_SIZE;
            line = ft_realloc(line, line_len, line_cap);
            if (!line)
                return NULL;
        }
        // 复制剩余内容到行缓冲区
        size_t i = 0;
        while (i < part_len)
        {
            line[line_len++] = remainder[i];
            i++;
        }
        line[line_len] = '\0';

        // 如果剩余内容里有换行,更新remainder并返回
        if (ft_gnl_strchr(remainder, '\n'))
        {
            ft_update_remainder(remainder, remainder + part_len);
            return line;
        }
        // 没有换行,清空remainder
        ft_update_remainder(remainder, "");
    }

    // 循环读取文件直到找到换行或文件结束
    while (1)
    {
        read_bytes = read(fd, remainder, BUFFER_SIZE);
        if (read_bytes <= 0)
            break;
        remainder[read_bytes] = '\0';

        size_t part_len = ft_gnl_strlen(remainder, '\n');
        // 扩容缓冲区
        if (line_len + part_len >= line_cap)
        {
            line_cap = line_len + part_len + BUFFER_SIZE;
            line = ft_realloc(line, line_len, line_cap);
            if (!line)
                return NULL;
        }
        // 读取内容到行缓冲区
        size_t i = 0;
        while (i < part_len)
        {
            line[line_len++] = remainder[i];
            i++;
        }
        line[line_len] = '\0';

        // 找到换行,更新remainder并返回
        if (ft_gnl_strchr(remainder, '\n'))
        {
            ft_update_remainder(remainder, remainder + part_len);
            return line;
        }
        // 没有换行,清空remainder
        ft_update_remainder(remainder, "");
    }

    // 处理文件结束后的剩余内容
    if (line && line[0] == '\0')
    {
        free(line);
        return NULL;
    }
    return line;
}

方案2:修复栈溢出问题(解决Malloc保护错误)

若不想重构整个逻辑,可单独修改ft_gnl_change_reminder函数,将栈数组改为堆分配:

static void ft_gnl_change_reminder(char *helper)
{
    char    *ptr;
    size_t  i;
    size_t  j;

    ptr = malloc(BUFFER_SIZE + 1);
    if (!ptr)
        return;

    i = 0;
    j = 0;
    while (helper[i] != '\n' && helper[i])
        i++;
    if (helper[i] == '\n')
        i++;
    while (helper[i])
        ptr[j++] = helper[i++];
    ptr[j] = '\0';

    i = 0;
    while (ptr[i])
    {
        helper[i] = ptr[i];
        i++;
    }
    while (i <= BUFFER_SIZE)
        helper[i++] = '\0';

    free(ptr);
}

关键优化点

  1. 动态扩容缓冲区:使用ft_realloc动态调整行缓冲区大小,避免频繁内存分配和全量复制,将时间复杂度降至O(n)。
  2. 堆分配替代栈数组:彻底避免栈溢出问题,解决malloc保护检查错误。
  3. 减少内存复制次数:仅复制必要内容,而非每次拼接都复制全部已有内容。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 00:37:04