PAYATHON 2026

K&R 的 readlines 删除无换行符行的末尾字符,这是 bug 吗?

数据小陈

是的,如果要求保留未以 \n 结尾的最后一行,题目中的实现就存在丢失数据的 bug。readlines 无条件覆盖读取结果的最后一个字符,依赖每次读取都以换行符结尾,而 mygetline 并不保证这一点。

仅凭代码,无法判断作者是否有意要求输入满足这一前提。

为什么会丢失最后一个字符?

题目中的 mygetline 返回实际存入缓冲区的字符数,不包含末尾的 \0。如果读到了 \n,换行符也计入长度。

输入为 abc\n 时:

line[0] = 'a'
line[1] = 'b'
line[2] = 'c'
line[3] = '\n'
line[4] = '\0'
len = 4

此时执行:

line[len - 1] = '\0';

会把换行符替换为字符串终止符,得到 abc,符合预期。

如果输入只有 abc,随后遇到 EOF,结果为:

line[0] = 'a'
line[1] = 'b'
line[2] = 'c'
line[3] = '\0'
len = 3

同一条语句会覆盖 line[2] 中的 'c',最终只保存 ab。EOF 是读取函数返回的状态,不会作为字符存入字符串,也不会自动补上换行符。

如何修复?

先检查最后一个字符是否为 \n,确认存在换行符后再删除。同时,还要调整内存分配大小,为最终字符串的 \0 留出空间。

沿用题目中的 alloc 和 mystrcpy,可以这样修改:

#include <stddef.h>
#include "mylib.h"
#define MAXLEN 1000

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

    nlines = 0;
    while ((len = mygetline(line, MAXLEN)) > 0) {
        if (line[len - 1] == '\n') {
            line[--len] = '\0';
        }

        if (nlines >= maxlines ||
            (p = alloc(len + 1)) == NULL) {
            return -1;
        }

        mystrcpy(p, line);
        lineptr[nlines++] = p;
    }
    return nlines;
}

这里假定 alloc(n) 分配可容纳 n 个字符的空间,mystrcpy 会复制末尾的 \0。

修改后,len 表示删除换行符(如果有)后的字符串长度,因此统一使用 alloc(len + 1):

  • 读取到 abc\n:原长度为 4,删除换行符后长度为 3,分配 4 个字符的空间。
  • 读取到 abc 后遇到 EOF:长度仍为 3,同样分配 4 个字符的空间。
  • 读取到空行 \n:删除换行符后长度为 0,分配 1 个字符的空间,用于保存 \0。

如果只给删除语句加条件,却保留原来的 alloc(len),无换行符时就会少分配一个字符的空间,导致复制字符串终止符时越界。

缓冲区读满时也会遇到同样的问题

没有换行符不一定意味着遇到了 EOF。题目中的 mygetline 在缓冲区空间不足时也会停止读取。

当 MAXLEN 为 1000 时,它最多保存 999 个输入字符,再补上 \0。如果在此之前尚未读到换行符,原来的 readlines 也会错误地删除本次读取的最后一个字符。

上面的修改可以避免字符丢失,但仍会把过长的一行拆成多条记录。如果需要让每条记录对应完整的一行,还需要拼接后续读取的片段,或者改用能够动态扩容的读取实现。

要求输入的最后一行以换行符结束,只能避开部分触发条件。修复时应检查实际读取结果,并按最终字符串长度分配空间。

备注:内容仅供参考。