• c语言15行实现简易cat命令


    刚刚和舍友打赌。舍友说PY20行能做xlsx文件分析整理,C20行屁都干不了。我说简单的cat还是能做的嘛。他说不信。我说不处理非文件的参数的话10行能做啊。
    下面直接贴代码吧:

    #include <stdio.h>
    #include <stdlib.h>
    
    #define assert(x) ((void)((x)||(perror(argv[0]), exit(-1), 0))) //<update171106
    
    int main(int argc, char *argv[]) {
    	int ai = 1;
    	do {
    		if (argc > 1) assert(freopen(argv[ai], "rb", stdin)); //<debug
    		for (int c; (c = getchar()) != EOF; putchar(c)) //<update180305
    			;
    		assert(!ferror(stdin));
    	} while (++ai < argc); //<debug
    }
    
    

    Feature

    • 允许多个文件合并
    • 允许从标准输入输入
    • 有错误反馈

    运行截图:

    正常运行
    报错信息

    debug

    mdzz 之前些地方写错了:
    13行 } while (ai++ < argc);
    应该是++ai。这里参数argc指的是argv数组成员个数(标准要求argc >= 0 && argv[argc][0] == ''

    9行 if (argc != 1) assert(freopen(argv[ai], "rb", stdin));
    应该是argc > 1

    这两个错误会导致:

    1. 如果目标环境不提供命令行参数,即argc == 0的时候,可能会导致freopen的第一个参数解析时越界,出现不可预知的状况。
    2. 如果调用时带参数,会导致最后一次循环ai == argc,间接导致freopen的第一个参数为空串。

    2运行结果错误的演示:
    BUG-2
    这里最后一次freopen接收到空串,这个freopen的表现和传递NULL一样(不合理,我看POSIX标准没找到这点,反而找到了ENOENT: pathname is an empty string <- 感觉原文有二义性)
    为什么我记得空串代表原本的stdin呢?

    update171106:

    今天有瞄了下代码,感觉那个宏不太对劲,好像加上个抛弃返回值的强制转换比较合适。
    否则可能会被用来做奇怪的事情(因为返回 ((_bool)1)):

    1. 后跟数组下标
      assert(true)["string233"]; => "string233"[1] => 't'
    2. 前跟函数指针(函数名)
      pFun assert(true); => pFun(1)
    3. 前跟 if while
      if/while assert(true) => if/while (1)

    加上 (void) 就能防止被上面那么说的乱用啦 233,然而没什么意义 hh

    assert 函数宏

    解释一下这个函数宏吧:
    #define assert(x) ((void)((x)||(perror(argv[0]), exit(-1), 0)))
    首先这个宏没有副作用(因为所有参数有且只有出现一次)。
    其次这个东西还是用了不少 C 技巧的:

    1. || 短路特性,只有参数 (_bool)x == false 才执行后面的逗号运行符; 见 std-keyword: sequence point & short-circuit evaluation
    2. , 逗号运算符顺序执行的特性,所以先执行 perror 再执行 exit。因为 || 运算符要求运算符两边都是标量,所以在最后让逗号表达式返回一个 int。
    3. perror(argv[0]) argv[0] 是程序的调用名字,这个输出是很标准的 linux 程序做法。

    update180305:

    原本那个 getchar 的循环原本是这样写的:for (char c; (c = getchar()) != EOF; putchar(c)),这段代码有明显的错误。
    错在哪?当然是char这里了:如果用char的话,在“实际应用”中会导致0xFFEOF的重合。
    实际上很多 C 环境的 char 都是 int8_t,而 (int8_t)0xff 很明显是 -1,不相信?那就试试下面的 C11 代码能不能通过编译。

    // test.c
    // clang -Wall -Wextra test.c -c -o cat
    #include <stdint.h>
    _Static_assert((int8_t)0xff == -1 && sizeof(char) == sizeof(int8_t), "it's impossible meet error!");
    _Static_assert((char)0xff != -1, "in this env., char is signed!");
    

    结果如下:

    我们来分析一下
    编译不通过 -> 静态断言失败 -> (char)0xff == -1 -> (char)0xff 算数提升到 int,且值等于负一 -> (char)0xff 符号位扩展了 -> (char)0xff 等价于 int8_t

    所以如果一个二进制文件内有 0xff 这个字节的话,原本代码会导致误判,以为到了 EOF 而提前结束。
    改成 int 就能在实际应用中避免这个问题,这也是标准函数原型这样设计的本意。

    注意:二进制文件才会有这样的问题, ascii 编码文本文件没有 0xff

    然而这里还是有一个问题,标准只要求了 sizeof(char) <= sizeof(int),会不会有一些很 gay 的环境,他们两者相等,那就无法预留一个 EOF 的值出来了,所以这个标准的原型严格的说还是存在问题的。
    不过如果真的有这种环境,那在这之上很难实现通用的文件就是了。

    欢迎转载©️tjua link: http://www.cnblogs.com/tjua/p/7758047.html (请保留本行)

    next target: 基于 make 的简易自动构建系统

    最近看到 gitlab-runner 项目后想到的脑洞。估计也是几行的事。

  • 相关阅读:
    奶酪真香
    规格说明书
    33
    111
    出题
    随笔 01
    我爱奶酪
    用户规格说明书
    第二次结对作业
    结对作业1
  • 原文地址:https://www.cnblogs.com/tjua/p/7758047.html
Copyright © 2020-2023  润新知