部分答案参考了官方题解和网上的答案,仅供参考,可能也有部分bug未发现或解决。

exercise8-1

用 read、write、open 和 close 系统调用代替标准库中功能等价的函数,重写第 7 章的 cat 程序,并通过实验比较两个版本的相对执行速度。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void filecopy(int ifp, int ofp)
{
    char buf[BUFSIZE];
    int n;
    while ((n = read(ifp, buf, BUFSIZE)) > 0)
        if (write(ofp, buf, n) != n)
            error("cp: write error on file2");
}

int main(int argc, char *argv[])
{
    int fd;
    char *prog = argv[0]; /* program name for errors */
    if (argc == 1)        /* no args; copy standard input */
        filecopy(STDIN_FILENO, STDOUT_FILENO);
    else
        while (--argc > 0)
            if ((fd = open(*++argv, O_RDONLY, 0)) == -1)
            {
                fprintf(stderr, "%s: can't open %s\n",
                        prog, *argv);
                exit(1);
            }
            else
            {
                filecopy(fd, STDOUT_FILENO);
                close(fd);
            }

    if (ferror(stdout))
    {
        fprintf(stderr, "%s: error writing stdout\n", prog);
        exit(2);
    }
    exit(0);
}

运行:

1
2
3
4
➜  ch08 git:(main) ✗ ./Exercise8-1 test01.txt test02.txt
This is test01.txt
Good idea.This is test02.txt
Bad idea.

exercise8-2

用字段代替显式的按位操作,重写 fopen 和_fillbuf 函数。比较相应代码的长度和执行速度。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
struct flags
{
    unsigned int _READ : 1;  /* file open for reading */
    unsigned int _WRITE : 1; /* file open for writing */
    unsigned int _UNBUF : 1; /* file is unbuffered */
    unsigned int _EOF : 1;   /* EOF has occurred on this file */
    unsigned int _ERR : 1;   /* error occurred on this file */
};

FILE *fopen(char *name, char *mode)
{
    int fd;
    FILE *fp;
    if (*mode != 'r' && *mode != 'w' && *mode != 'a')
        return NULL;
    for (fp = _iob; fp < _iob + OPEN_MAX; fp++)
        if ((flagsempty(fp->flag) & (_READ | _WRITE)) == 0)
            break;             /* found free slot */
    if (fp >= _iob + OPEN_MAX) /* no free slots */
        return NULL;
    if (*mode == 'w')
        fd = creat(name, PERMS);
    else if (*mode == 'a')
    {
        if ((fd = open(name, O_WRONLY, 0)) == -1)
            fd = creat(name, PERMS);
        lseek(fd, 0L, 2);
    }
    else
        fd = open(name, O_RDONLY, 0);
    if (fd == -1) /* couldn't access name */
        return NULL;
    fp->fd = fd;
    fp->cnt = 0;
    fp->base = NULL;
    fp->flag._READ = (*mode == 'r') ? 1 : 0;
    fp->flag._WRITE = (*mode == 'w') ? 1 : 0;
    return fp;
}

/* _fillbuf: allocate and fill input buffer */
int _fillbuf(FILE *fp)
{
    int bufsize;
    if ((flagsempty(fp->flag) & (_READ | _EOF | _ERR)) != _READ)
        return EOF;
    bufsize = (flagsempty(fp->flag) & _UNBUF) ? 1 : BUFSIZ;
    if (fp->base == NULL) /* no buffer yet */
        if ((fp->base = (char *)malloc(bufsize)) == NULL)
            return EOF; /* can't get buffer */
    fp->ptr = fp->base;
    fp->cnt = read(fp->fd, fp->ptr, bufsize);
    if (--fp->cnt < 0)
    {
        if (fp->cnt == -1)
            fp->flag._EOF = 1;
        else
            fp->flag._ERR = 1;
        fp->cnt = 0;
        return EOF;
    }
    return (unsigned char)*fp->ptr++;
}

exercise8-3

设计并编写函数_flushbuf、fflush 和 fclose。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
int _flushbuf(int c, FILE *fp)
{
    int num_written, bufsize;
    unsigned char uc = c;
    if ((fp->flag & (_WRITE | _EOF_ERR)) != _WRITE)
        return EOF;
    /*
        如果尚未分配缓冲区并且不是无缓冲模式,尝试分配一个大小为 BUFSIZ 的缓冲区。
        如果分配失败,设置为无缓冲模式;如果成功,初始化缓冲区指针和计数器。
    */
    if (fp->base == NULL && ((fp->flag & _UNBUF) == 0))
    {
        if ((fp->base = malloc(BUFSIZ)) == NULL)
            fp->flag |= _UNBUF;
        else
        {
            fp->ptr = fp->base;
            fp->cnt = BUFSIZ - 1;
        }
    }
    /*
        如果是无缓冲模式,直接将字符写入文件。
        此时将指针和计数器设置为 NULL 和 0。若要写入的是 EOF,则直接返回。
    */
    if (fp->flag & _UNBUF)
    {
        /* unbuffered write */
        fp->ptr = fp->base = NULL;
        fp->cnt = 0;
        if (c = EOF)
            return EOF;
        num_written = write(fp->fd, &uc, 1);
        bufsize = 1;
    }
    /*
        如果是缓冲模式且要写入的字符不是 EOF,将字符存入缓冲区,然后更新指针。
        接着计算当前缓冲区的大小,并将缓冲区内容写入文件。写入后,将指针和计数器重置。
        因为没有写入EOF,所以不会打印
    */
    else
    {
        /* buffered write */
        if (c != EOF)
        {
            *fp->ptr = uc;
            fp->ptr++;
        }
        bufsize = (int)(fp->ptr - fp->base);
        num_written = write(fp->fd, fp->base, bufsize);
        fp->ptr = fp->base;
        fp->cnt = bufsize - 1;
    }
    if (num_written = bufsize)
        return c;
    else
    {
        fp->flag |= _ERR;
        return EOF;
    }
}

/* 刷新输出缓冲区 */
int fflush(FILE *f)
{
    int retval; // 返回值
    int i;
    retval = 0;
    if ((f->flag & _WRITE) == 0)
        return -1;
    // 将EOF写入f中,代表文件结束
    _flushbuf(EOF, f);
    if (f->flag & _ERR)
        retval = -1;
    return retval;
}

/* 关闭文件 */
int fclose(FILE *f)
{
    int fd;
    if (f == NULL)
        return -1;
    fd = f->fd;
    // 将文件输出缓冲区刷新
    fflush(f);
    // 将cnt、ptr、base、flag、fd重置,释放base指向的内存
    f->cnt = 0;
    f->ptr = NULL;
    if (f->base != NULL)
        free(f->base);
    f->base = NULL;
    f->flag = 0;
    f->fd = -1;
    // 关闭文件描述符
    return close(fd);
}

int main(int argc, char *argv[])
{
    FILE *fp;
    fp = fopen("test01.txt", "r");
    char c;
    while ((c = getc(fp)) != EOF)
        putc(c, stdout);
    fflush(stdout);
    fclose(fp);
}

运行:

1
2
3
➜  ch08 git:(main) ✗ ./Exercise8-3 test01.txt 
This is test01.txt
Good idea.

exercise8-4

标准库函数int fseek(FILE *fp, long offset, int origin)类似于函数 lseek,所不同的是,该函数中的 fp 是一个文件指针而不是文件描述符,且返回值是一个 int 类型的状态而非位置值。编写函数 fseek,并确保该函数与库中其它函数使用的缓冲能够协同工作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/* 将读写指针置于一定位置 */
int fseek(FILE *fp, long offset, int origin)
{
    if ((fp->flag & _UNBUF) == 0 && fp->base != NULL)
    {
        // 如果是写入模式,刷新缓冲区,将缓冲区内容输出,防止写入一个不为空的缓冲区
        if (fp->flag & _WRITE)
            fflush(fp);
        // 如果是读取模式,将缓冲区内容清空,防止读取一个不为空的缓冲区
        else if (fp->flag & _READ)
        {
            fp->cnt = 0;
            fp->ptr = fp->base;
        }
    }
    return (lseek(fp->fd, offset, origin) < 0);
}

int main(int argc, char *argv[])
{
    FILE *fp;
    if (argc == 1)
        return 1;

    fp = fopen(argv[1], "r");
    off_t offset = 0;
    if (argc == 3)
        offset = atol(argv[2]);
    char c;
    fseek(fp, offset, SEEK_SET);
    while ((c = getc(fp)) != EOF)
        putc(c, stdout);
    fflush(stdout);
    fclose(fp);
}

运行:

1
2
3
➜  ch08 git:(main) ✗ ./Exercise8-4 test01.txt 12
01.txt
Good idea.

exercise8-5

修改 fsize 程序,打印 i 结点项中包含的其它信息。

书中提供的代码有一些问题:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
/* readdir: read directory entries in sequence */
Dirent *mreaddir(mDIR *dp)
{
    struct direct dirbuf; /* local directory structure */
    static Dirent d;      /* return: portable structure */
    int readsize;

    // 此版本的read无法读取一个目录文件,下面的语句执行之后 readsize 始终为-1
    // 在其他测试程序中单独测试了使用 read() 来读取一个目录文件,也是得到相同的结论
    // 所以就在本步骤,只好使用系统提供的 readdir() 来读取一个目录文件的信息
    while ((readsize = read(dp->fd, (char *)&dirbuf, sizeof(dirbuf))) == sizeof(dirbuf))
    {
        if (dirbuf.d_ino == 0) /* slot not in use */
            continue;
        d.ino = dirbuf.d_ino;
        strncpy(d.name, dirbuf.d_name, DIRSIZ);
        d.name[DIRSIZ] = '\0'; /* ensure termination */
        return &d;
    }
    return NULL;
}

经过修改,下面的代码可以实现递归打印目录中的文件及子目录内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include "./dirent.h"

#define MAX_PATH 1024

Dirent *mreaddir02(DIR *dp)
{
    struct direct *dirbuf;
    static Dirent d; // 静态变量

    if ((dirbuf = readdir(dp)) == NULL || dirbuf->d_ino == 0)
        return NULL;

    d.ino = dirbuf->d_ino;
    strncpy(d.name, dirbuf->d_name, DIRSIZ);
    d.name[DIRSIZ] = '\0';
    return &d;
}

/* dirwalk: apply fcn to all files in dir */
void dirwalk(char *dir, void (*fcn)(char *))
{
    char name[MAX_PATH];
    Dirent *dp = malloc(sizeof(Dirent));
    memset(dp->name, 0, sizeof(dp->name));
    DIR *dfd;
    // 直接使用系统的opendir()
    if ((dfd = opendir(dir)) == NULL)
    {
        fprintf(stderr, "dirwalk: can't open %s\n", dir);
        return;
    }
    while ((dp = mreaddir02(dfd)) != NULL)
    {
        if (strcmp(dp->name, ".") == 0 || strcmp(dp->name, "..") == 0)
            continue; /* skip self and parent */
        if (strlen(dir) + strlen(dp->name) + 2 > sizeof(name))
            fprintf(stderr, "dirwalk: name %s %s too long\n",
                    dir, dp->name);
        else
        {
            sprintf(name, "%s/%s", dir, dp->name);
            (*fcn)(name);
        }
    }
    // 直接使用系统提供的closedir()
    closedir(dfd);
}
void fsize(char *name)
{
    struct stat stbuf;
    if (stat(name, &stbuf) == -1)
    {
        fprintf(stderr, "fsize: can't access %s\n", name);
        return;
    }
    if (S_ISDIR(stbuf.st_mode)) /* directory */
        dirwalk(name, fsize);

    char timebuf[32];
    strftime(timebuf, sizeof(timebuf), "%b %d %R", localtime(&stbuf.st_mtime));
    // 展示文件大小,文件的inode号,文件的最后修改时间,文件名
    printf("%8ldB %8ld  %s %s\n", stbuf.st_size, stbuf.st_ino, timebuf, name);
}

运行,打印文件的大小、inode节点号、最后修改日期、文件名

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
➜  ch08 git:(main) ✗ ./Exercise8-5 /home/jagger/Codes/ch08
    1300B   149492  Oct 19 10:11 /home/jagger/Codes/ch08/Exercise8-1.c
    6917B   149495  Oct 23 12:59 /home/jagger/Codes/ch08/Exercise8-4.c
      28B   149503  Oct 19 10:13 /home/jagger/Codes/ch08/test02.txt
      40B   149491  Oct 19 09:49 /home/jagger/Codes/ch08/.gitignore
    1461B   149502  Oct 19 11:29 /home/jagger/Codes/ch08/Exercise8-8.c
   13464B   149490  Oct 23 15:12 /home/jagger/Codes/ch08/README.md
    3134B   149498  Oct 20 14:04 /home/jagger/Codes/ch08/Exercise8-6.c
     662B   149504  Oct 20 13:12 /home/jagger/Codes/ch08/dirent.h
     399B   149505  Oct 23 13:32 /home/jagger/Codes/ch08/test.c
    3362B   149497  Oct 23 15:00 /home/jagger/Codes/ch08/Exercise8-5.c
      30B   149496  Oct 23 11:34 /home/jagger/Codes/ch08/test01.txt
     819B   149499  Oct 20 13:43 /home/jagger/Codes/ch08/Exercise8-7.c
    6202B   149494  Oct 23 11:47 /home/jagger/Codes/ch08/Exercise8-3.c
      75B   149501  Oct 19 09:50 /home/jagger/Codes/ch08/createfile.sh
   23016B   149506  Oct 23 15:13 /home/jagger/Codes/ch08/Exercise8-5
     209B   149500  Oct 19 09:51 /home/jagger/Codes/ch08/Makefile
    3511B   149493  Oct 19 11:22 /home/jagger/Codes/ch08/Exercise8-2.c
    4096B   149489  Oct 23 15:13 /home/jagger/Codes/ch08

exercise8-6

标准库函数 calloc(n, size)返回一个指针,它指向 n 个长度为 size的对象,且所有分配的存储空间都被初始化为0。通过调用或修改 malloc函数来实现 calloc函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void *mcalloc(size_t n, size_t size) {
    void *p;
    p = malloc(n * size);
    if (p != NULL)
        bzero(p, n * size);
    return p;
}

int main(void)
{
    int size;
    int *p = NULL;
    printf("Enter the size of memory to allocate: ");
    scanf("%d", &size);
    p = (int *)mcalloc(size, sizeof(*p));
    if (p != NULL) {
        printf("Memory allocated successfully\n");
        for (int i = 0; i < size; i++)
        {
            printf("%08X ", p[i]);
            if (i % 8 == 7)            
                printf("\n");
        }
    }
    return 0;
}

运行:

1
2
3
4
Enter the size of memory to allocate: 12
Memory allocated successfully
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 
00000000 00000000 00000000 00000000 

exercise8-7

malloc 接收对存储空间的请求时,并不检查请求长度的合理性;而 free则认为被释放的块包含一个有效的长度字段。改进这些函数,使它们具有错误检查的功能。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void *mmalloc(int size) {
    if (size <= 0 || size > MAXSIZE) {
        fprintf(stderr, "Invalid allocation size: %d Bytes\n", size);
        return NULL;
    } else {
        void *ptr = malloc(size);
        if (ptr == NULL) 
            fprintf(stderr, "Memory allocation failed\n");      
        printf("Memory allocated successfully\n");
        return ptr;
    }
}

void mfree(void *ptr) {
    if (ptr == NULL) {
        fprintf(stderr, "Attempt to free a null pointer\n");
        return;
    }
    printf("Memory freed successfully\n");
    free(ptr);
}

int main() {
    int size;
    printf("Enter the size of memory to allocate: ");
    scanf("%d", &size);
    void *ptr = mmalloc(size);
    if (ptr != NULL)
        mfree(ptr);
    return 0;
}

运行:

1
2
Enter the size of memory to allocate: -1
Invalid allocation size: -1 Bytes