对于K&R exercise 5-13实现程序中一个语句i = (i + 1) % LINES的质疑

编写程序 tail,打印输入的最后 n 行。
大家好,我关于其实现程序不清楚的部分是如下程序段,书中对i = (i + 1) % LINES的描述是这样的。

    for (i = first; n-- > 0; i = (i + 1) % LINES)
        printf("%s", lineptr[i]);

书中对于该段语句的解读:
因为i从初值first开始递增n次,那么,当i大于LINES时,就会出现某些输人行被反复打印多次的情况。为了避免出现这一局面,我们使用了取模运算符(%)来确保i的取值范围能够落在0到LINES- 1之间。

我知道i = (i + 1) % LINES所起到地作用就是限制变量i的范围。但在思考之后发现这并没什么意义?原因如下:
i的初始值为first,如果最终i要大于LINES,那么first+n就要大于LINES。
又因为程序段中

  first = last - n;//输出行的起始索引,要从输入最后一行的索引开始,退回n个值

那么就是说要满足上面所述的这种情况,last就要大于LINES,但是last不可能大于LINES呀!
原因如下:

        if (++last >= LINES)
            last = 0;//如果输入行总数(last的值)每超过LINES一次,last值就重置一次(设置为0),类似环状结构

所以我就很好奇i = (i + 1) % LINES的重要性
产生的问题:
我无法理解刚才书中这段话的意思。
1.什么时候才会出现它所说的某些输入行被反复打印
2.它说的会出现某些输人行被反复打印多次的情况,某些行是指这些行是随机的吗?
3.我直接把这个程序段

    for (i = first; n-- > 0; i = (i + 1) % LINES)
        printf("%s", lineptr[i]);

改成这个可以吗,因为根据我上面的分析变量i是不可能大于LINES的,所以我觉得没必要限制范围。

    for (i = first; n-- > 0; i ++)
        printf("%s", lineptr[i]);

实现程序

/* Write a Program tail, which prints the last n lines of its input. By default n is 10. let us say; but it can be changed
by an optional argument so that tail -n */
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#define DEFLINES 10 /* default # of lines to print */
#define LINES 3 /* maximum # of lines to print */
#define MAXLEN 100 /* maximum length of an input line */

void error(char *);
int mgetline(char *, int);

/* print the last n lines of the input */

int main(int argc, char *argv[])
{
    char *p;
    char *buf; /* pointer to the large buffer */
    char *bufend; /* end of the large buffer */

    char line[MAXLEN];
    char *lineptr[LINES]; /* pointer to lines read */

    int first, i, last, len, n, nlines;

    scanf("%d", &argc);
    for (int i = 1; i < argc; i++)
    {
        argv[i] = (char *)malloc(MAXLEN * sizeof(char));
        scanf("%s", argv[i]);
    }
    getchar();
    if (argc == 1)
        n = DEFLINES;

    else if (argc == 2 && (*++argv)[0] == '-')
        n = atoi(argv[0] + 1);
    else
        error("Usage: tail [-n]");

    if (n < 1 || n > LINES)//将输入的最后n行打印出来,所以n的值有意义的同时,不能够大于最多允许打印的行数
        n = LINES;

    for (i = 0; i < LINES; i++)
        lineptr[i] = NULL;

    if ((p = buf = malloc(LINES * MAXLEN)) == NULL)
        error("tail: cannot allocate buf");
    bufend = buf + LINES + MAXLEN;

    last = 0;    //上一个输入行的索引
    nlines = 0;    //输入行的个数

    while ((len = mgetline(line, MAXLEN)) > 0)
    {
        if (p + len + 1 >= bufend)
            p = buf;
        lineptr[last] = p;

        strcpy(lineptr[last], line);
        if (++last >= LINES)
            last = 0;

        p += len + 1;    //mgetline返回的字符串长度len没有将终止符'\0'算在其中
        nlines++;
    }

    if (n > nlines)        //lines of request more than recording ?
        n = nlines;

    first = last - n;//输出行的起始索引,要从输入最后一行的索引开始,退回n个值

    if (first < 0)
        first += LINES;

    for (i = first; n-- > 0; i = (i + 1) % LINES)
        printf("%s", lineptr[i]);
    return 0;
}

/* error: print error messages and exit */

void error(char *s)
{
    printf("%s\n", s);
    exit(1);
}

/* mgetline: read a line into s and return length */

int mgetline(char *s, int lim)
{
    int c, i;

    for (i = 0; i < lim - 1 && (c = getchar()) != EOF && c != '\n'; ++i)
        s[i] = c;
    if (c == '\n')
    {
        s[i] = c;
        ++i;
    }

    s[i] = '\0';
    return i;
}

我觉得:

    for (i = first; n-- > 0; i = (i + 1) % LINES)
        printf("%s", lineptr[i]);

这句的作用是这样的:
如果没有使用%:假如写入8行,last=8,输出最后3行,即n=3。那么first=last-n=8-3=5。也就是lineptr[i]的下标i从5开始,如果不对其取模的话,这就超过了定义时的:lineptr[LINES] 的范围,因为LINES=3。所以使用取余能够限定i的值小于LINES。
这是我的一点看法。

我没太看懂你想要问什么,如果是想问(i+1)%LINES,我给你解释下吧,这种就是没了防止越界,因为循环中你并没有表明i的取值范围,那么就会出现越界的情况,所以一般会用这种方式,这也是你以后学循环队列的下标表示法,如果是有其他的问题,可以直接问我的