どうせなら、もう少し潰しがきくように書いてみた。

はこべにっき# - C言語でtailっぽいものを書く
また,明日学科のC言語のテストがある.C言語なぞ普段まったく使わないもんだから,思い出さねば.てことで,10行固定版tailを書いてみた.以下のソース.

まずは、main()の方から。再発明だけでは芸が無いので、行数もオプションとして指定できるようにしてみた。

tail.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_CHARS_IN_LINE 1024
#include "queue.h"

char *new_string(const char *str){
    char *new = (char *)malloc(strlen(str)+1);
    if (new == NULL) DIE_OUT_OF_MEMORY;
    strcpy(new, str); /* it's okay not to use strncpy */
    return new;
}

void usage(const char *str){
    fprintf(stderr, "usage: %s [-n] inputfile\n", str);
    exit(2);
}

int print_atom(void *str){
    return printf("%s", (char *)str);
}

int main(int argc, char **argv){
    char buf[MAX_CHARS_IN_LINE];
    int nline = 10;
    queue *q;
    FILE *fp;

    if (argc < 2) usage(argv[0]);
    if (argv[1][0] == '-'){
        nline = atoi(argv[1]+1);
        if (!nline) usage(argv[0]);
    }
    if ((fp = fopen(argv[argc-1],"r")) == NULL){
        fprintf(stderr, "%s : %s\n", argv[argc-1], strerror(2));
        exit(2);
    }
    q = new_queue(nline);
    while (fgets(buf, MAX_CHARS_IN_LINE, fp)) {
        enqueue(q, new_string(buf));
    }
    traverse_queue(q, print_atom);
    del_queue(q);
    exit(0);
}

見ての通り、

  • 今時の端末なら、一画面に収まる大きさに。
  • -Wallをつけてcompileしてもちゃんとwarningが出ないよう、きっちりcast。
  • queueの関わる関数定義は、queue.hに全部追い出してある。
  • traverse_queue()に注目。Cでも一応関数型っぽく出来なくもないという例として。もっともCにはlambdaはないのだけど。

それで、queue.hは以下のとおり。

queue.h
#define DIE_OUT_OF_MEMORY { fprintf(stderr, "Out of memory!\n"); exit(-1); }

struct list {
    void *car, *cdr;
};
typedef struct list list;

struct queue{
    int size, max;
    list *head, *tail;
};
typedef struct queue queue;

list *new_list(void *atom){
    list *new = (list *)malloc(sizeof(list));
    if (new == NULL) DIE_OUT_OF_MEMORY;
    new->car = atom;
    new->cdr = NULL;
    new->head = new->tail = NULL; /* shiro-san, thanx */
    return new;
}

void del_list(list *l){
    if (l == NULL) return;
    if (l->car) free(l->car);
    free(l);
}

queue *new_queue(int max){
    queue *new = (queue *)malloc(sizeof(queue));
    if (new == NULL) DIE_OUT_OF_MEMORY;
    new->max  = max;
    new->size = 0;
    return new;
}

list *dequeue(queue *q){
    list *head = q->head;
    if (head) {
        q->head = q->head->cdr;
        q->size--;
    }
    return head;
}

int enqueue(queue *q, void *atom){
    list *l = new_list(atom);
    if (q->size == q->max) del_list(dequeue(q));
    if (q->size == 0) {
        q->head = q->tail = l;
        q->head->cdr = q->tail;
    }else{
        q->tail->cdr = l;
        q->tail = l;
    }
    q->size++;
    return q->size;
}

void del_queue(queue *q){
    list *l;
    if (q == NULL) return;
    while((l = dequeue(q)) != NULL){ 
        del_list(l); 
    }
    free(q);
}

void traverse_queue(queue *q, int (*callback)(void *)){
    list *l;
    for (l = q->head; l; l = l->cdr) callback(l->car);
}
  • car,cdrというのは趣味:)
  • void *なので、潰しが利く。これならtail以外にも使えるだろう。
  • ただし、carにlistをぶら下げることは考慮していない。が、ここではqueueを管理できればよいのでこの程度でいいだろう。
  • queueにまつわる操作は一通り出来るので、使う方は構造を気にする必要があまりない。強いて言えば、enqueueされるelementが必ずmalloc()されたものであるということか。
  • traverse_queue()callback()voidでもよかったかな。

ちなみに、「ふつうのHaskell本」のtailは、メモリー浪費型。この程度だとまだHaskellのご利益はあまりなくて、Perlでは

perl -e 'print reverse((reverse <>)[0..9])'

で出来てしまう。

Dan the Occasional C Programmer