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] == '\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 項目后想到的腦洞。估計也是幾行的事。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM