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

K&R练习5-13:tail程序处理大文件时异常问题排查

问题根源分析

你的alloc函数试图实现内存复用逻辑,但存在多处逻辑错误,导致大文件处理时内存管理混乱:

  • allocn的无意义调整:你试图通过allocn -= no避免溢出,但allocn是累计分配次数,这个操作会破坏对已分配行数的跟踪,导致后续leftalloc和rightalloc的计算完全错误。
  • 内存边界计算错误:通过while (*rightalloc++ != '\0')寻找旧行末尾不可靠——旧行内存被新数据覆盖后,字符串结束符'\0'可能已被破坏,无法正确定位内存边界。
  • 循环缓冲区逻辑混乱:leftalloc和rightalloc的初始化与更新逻辑不清晰,无法正确标记可复用内存区域,导致小n值(如-1到-4)时快速误判内存不足,n=5、6时出现内存覆盖导致行内容丢失。
  • 换行符处理错误:readlines中line[len-1] = '\0'会强制去掉换行符,若输入最后一行无换行符,会把该行最后一个字符替换为'\0',导致内容损坏。

修复方案

我们简化内存管理逻辑:用环形队列存储最后n行的指针和内存长度,新行到来时,若队列已满则复用最旧行的内存(空间足够的话),否则从缓冲区末尾分配,直到静态缓冲区耗尽。

修复后的完整代码:

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

int isint(char s[]); 
int toint(char s[]);
int readlines(char *lineptr[], int no);
void writelines(char *lineptr[], int no);

#define MAXLINE 100  /* 最大允许打印的行数 */

int main(int argc, char *argv[])
{
    int n = 10;
    char *lineptr[MAXLINE];

    while (--argc > 0) {
        if ((*++argv)[0] == '-' && isint(*argv)) {
            int val = toint(*argv);
            if (val > 0 && val <= MAXLINE) {
                n = val;
                break;
            }
        }
        printf("invalid argument %s\n", *argv);
    }

    printf("Printing %d line%s\n", n, n > 1 ? "s" : "");
    if (readlines(lineptr, n) >= 0) {
        writelines(lineptr, n);
        return 0;
    } else {
        printf("input too big to handle\n");
        return 1;
    }
}

int isint(char s[])
{
    if (*s == '-') s++;
    if (!isdigit(*s)) return 0;
    while (*s != '\0') {
        if (!isdigit(*s++)) return 0;
    }
    return 1;
}

int toint(char s[])
{
    return atoi(s + (*s == '-' ? 1 : 0));
}

#define MAXLEN 1000  /* 单行长上限 */
#define ALLOCSIZE MAXLINE * MAXLEN  /* 总内存缓冲区大小 */

static char allocbuf[ALLOCSIZE];
static char *allocp = allocbuf;  /* 下一个可用内存的起始位置 */
/* 记录环形队列中每行的内存起始地址和长度 */
typedef struct {
    char *ptr;
    int len;
} LineInfo;
static LineInfo line_info[MAXLINE];
static int queue_head = 0;  /* 环形队列头(最旧行的索引) */
static int queue_count = 0; /* 当前队列中的行数 */

char *alloc(int n)
{
    /* 先尝试复用环形队列中最旧的行的内存 */
    if (queue_count >= MAXLINE) {
        LineInfo oldest = line_info[queue_head];
        if (oldest.len >= n) {
            /* 旧行内存足够,直接复用 */
            return oldest.ptr;
        }
    }
    /* 没有可复用的内存,从缓冲区末尾分配 */
    if (allocbuf + ALLOCSIZE - allocp >= n) {
        char *p = allocp;
        allocp += n;
        return p;
    }
    return NULL;
}

void update_queue(char *p, int len)
{
    if (queue_count < MAXLINE) {
        /* 队列未满,直接添加新行信息 */
        line_info[queue_count].ptr = p;
        line_info[queue_count].len = len;
        queue_count++;
    } else {
        /* 队列已满,更新最旧行的信息 */
        line_info[queue_head].ptr = p;
        line_info[queue_head].len = len;
        queue_head = (queue_head + 1) % MAXLINE;
    }
}

int get_line(char *line, int lim)
{
    int i, c;

    i = 0;
    while (--lim > 0 && (c = getchar()) != EOF && c != '\n')
        line[i++] = c;
    if (c == '\n') {
        line[i++] = c;
    }
    line[i] = '\0';
    return i;
}

void push(char *lineptr[], char *p, int no)
{
    int i;
    for (i = 0; i < no - 1; i++) {
        lineptr[i] = lineptr[i + 1];
    }
    lineptr[no - 1] = p;
}

int readlines(char *lineptr[], int no)
{
    int len, i;
    char *p, line[MAXLEN];

    for (i = 0; i < no; i++) {
        lineptr[i] = NULL;
    }

    while ((len = get_line(line, MAXLEN)) > 0) {
        /* 分配内存时要包含'\0'的空间 */
        int alloc_len = len + 1;
        if ((p = alloc(alloc_len)) == NULL) {
            return -1;
        } else {
            /* 保留换行符,不需要修改line[len-1] */
            strcpy(p, line);
            push(lineptr, p, no);
            update_queue(p, alloc_len);
        }
    }
    return 1;
}

void writelines(char *lineptr[], int no)
{
    int i;
    for (i = 0; i < no; i++) {
        if (lineptr[i] != NULL) {
            printf("%s", lineptr[i]);
        }
    }
}

修复要点说明

  1. 简化内存分配逻辑:去掉原alloc函数中复杂的leftalloc/rightalloc和allocn调整,改用LineInfo结构体跟踪每行的内存地址和长度,优先复用最旧行的内存(空间足够时)。
  2. 修正换行符处理:不再强制修改line[len-1]为'\0',直接保留读取到的换行符,避免损坏无换行符的最后一行内容。
  3. 环形队列管理:用queue_head和queue_count维护环形队列,准确跟踪当前存储的n行,确保内存复用的正确性。
  4. 参数校验优化:原toint函数会跳过开头的'-'和'0',导致-011被解析为11,修复后直接用atoi处理完整字符串,同时校验参数范围。

修改后,无论小文件还是大文件,不同n值下都能正确输出最后n行,不会出现内存不足误判或行内容丢失的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 06:15:01