入力の最後のn行を表示するtailの実装

K&R本 演習5-13

入力の最後のn行を印字するプログラムtailを書け。nの省略時仮定値は10であるが、これは省略可能な引数によって変更できるようにし、

tail -n

で最後のn行を印字するようにせよ。

難しそうですね。

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

#define LINEMAX 1000
#define DEFAULTTAIL 10

int main (int argc,char *argv[]) {
    char **lines;
    char *line;
    char str[LINEMAX];
    int i=0,j,k;
    int total = 0;
    int num = DEFAULTTAIL;
    
    if ( argc > 1 ) {
        ++argv;
        if ( *(*argv)++ == '-' ) {
            if ( isdigit(**argv) ) {
                num = atoi(*argv);
                if ( num <= 0 ) num = DEFAULTTAIL;
            }
        }
    }
    
    // アドレスを保存するために各要素はintで取る
    lines = (char **)calloc(num,(sizeof(int)));
    
    while ( fgets(str,sizeof(str),stdin) != NULL ) {
        line = (char *)malloc(strlen(str)+1);
        if ( line == NULL ) {
            puts("error");
            return 1;
        }
        strcpy_s(line,strlen(str)+1,str);
        free(lines[i]); // 上書きされる行はもう使わないのでfreeで解放しとく
        lines[i] = line;
        i = (i+1) % num;
        total++;
    }
    
    if ( total > num ) {
        i = total - (total / num) * num;
        j = num;
    }
    else {
        i = 0;
        j = total;
    }
    
    for(k=0;k<j;k++,i=(i+1)%num){
        printf("%s",lines[i]);
    }
    
    return 0;
}
$ cat main.c | main
        i = 0;
        j = total;
    }

    for(k=0;k<j;k++,i=(i+1)%num){
        printf("%s",lines[i]);
    }

    return 0;
}

$ cat main.c | main -20
        lines[i] = line;
        i = (i+1) % num;
        total++;
    }

    if ( total > num ) {
        i = total - (total / num) * num;
        j = num;
    }
    else {
        i = 0;
        j = total;
    }

    for(k=0;k<j;k++,i=(i+1)%num){
        printf("%s",lines[i]);
    }

    return 0;
}

できました。

まず表示させる行数分のメモリを確保します。

この時気をつけないといけないのが

// アドレスを保存するために各要素はintで取る
lines = (char **)calloc(num,(sizeof(int)));

ここですね。最初ここのsizeofをcharでしていたんですがどうも時々表示がおかしくなるので何故だろうと悩んでおりました。

考え抜いた結果、linesに代入するデータは実際にはポインタなのでアドレスを保持できる分のメモリが確保されてないんじゃないのかという結論にいたり、intのsizeofにしたらうまく動くようになったというわけです。

もしかしたら認識間違ってるかもしれませんが・・・。

そのほかはまぁ割とうまく実装できたのかなぁという感じですね。

最後の出力の処理がちょっと冗長な気もしますが、他に良いのが思いつきませんでした。

再帰関数でも作ってそこで再帰的に表示させる方がもしかしたら綺麗なのかもしれません。