coreutils4.5.1 expr.c 原始碼分析2
今天又開始讀程式碼。前段時間看演算法分析相關的書,蒐集了不少演算法相關書籍,感覺自己功力太淺,還是讀讀原始碼吧。好在,讀小說,養成了快速讀書的好習慣,再加不求甚解,把快速+不求甚解利用到讀程式碼上,感覺也很有意思。
今天重點翻了翻expr.c,這個原始碼,很有特色,首先啟用debug功能。
文件中有註釋,Define EVAL_TRACE to print an evaluation trace.
我是通過讀程式碼後,發現
#define EVAL_TRACE
可以啟用註釋功能,重新編譯後,本地執行
sudo make
./expr 3 + 2
發現能打印出一堆東西,如圖:
eval: 3 + 2
eval1: 3 + 2
eval2: 3 + 2
eval3: 3 + 2
eval4: 3 + 2
eval5: 3 + 2
eval6: 3 + 2
eval7: 3 + 2
eval4: 2
eval5: 2
eval6: 2
eval7: 2
5
------------------
這個程式很有意思,定義了一個結構體
/* The kinds of value we can have. */
enum valtype
{
integer,
string
};
typedef enum valtype TYPE;
/* A value is.... */
struct valinfo
{
TYPE type; /* Which kind. */
union
{ /* The value itself. */
intmax_t i;
char *s;
} u;
};
typedef struct valinfo VALUE;
你不知道,我上次讀awk的原始碼,被那個結構體徹底弄暈了,所以這個結構體還是很清爽的。這個結構體很有意思。計算的返回值就是它。
main中調eval(),再printv,程式如下:
args = argv + 1;
v = eval ();
if (!nomoreargs ())
error (2, 0, _("syntax error"));
printv (v);
因此,文章的重點就在eval函式上,而printv較簡單,就是把結構打印出來。
我們來重點讀eval
/* Handle |. */
static VALUE *
eval (void)
{
VALUE *l;
VALUE *r;
#ifdef EVAL_TRACE
trace ("eval");
#endif
l = eval1 ();
while (1)
{
if (nextarg ("|"))
{
r = eval1 ();
if (null (l))
{
freev (l);
l = r;
}
else
freev (r);
}
else
return l;
}
}
開始我真不知道這個"|"是什麼用處,因為expr命令也不熟悉。後來執行
./expr --help
打印出來一段註釋,也就是usage函式打印出來的,我一般忽略掉usage了,讀了help後,才知道是
expr a \| b
如果a=0就列印b,大概是這意思,於是,再讀原始碼就理解了。
先呼叫eval1計算出左值,判斷如果下一運算子是"|",再計算出右值,然後判斷,如果左值是空,就取右值。最後返回值。
while (1)
{
if (nextarg ("|"))
{
r = eval1 ();
if (null (l))
{
freev (l);
l = r;
}
else
freev (r);
}
else
return l;
}
這段程式碼很有代表性,其中eval1,eval2,eval3,eval4....基本上都是這個套路。
不過,eval1-->eval2-->eval3--->eval4--->eval5--->eval6-->eval7--->eval1
其中--->表示函式呼叫的意思,出現了遞迴,看到沒?因為eval7是處理外圍的括號,於是用到了遞迴。如果出現
./expr \( 30 + 2 \)
結果是:
eval: (30 + 2 )
eval1: (30 + 2 )
eval2: (30 + 2 )
eval3: (30 + 2 )
eval4: (30 + 2 )
eval5: (30 + 2 )
eval6: (30 + 2 )
eval7: (30 + 2 )
eval4: 2 )
eval5: 2 )
eval6: 2 )
eval7: 2 )
./expr: non-numeric argument
開始沒加轉義符號,搞了一會沒弄出來。
基本上把expr.c的程式碼過了一次。感覺很有收穫。越看,越感覺這些人的寫法很奇怪,
while (1)
{
if (nextarg ("|"))
{
r = eval1 ();
if (null (l))
{
freev (l);
l = r;
}
else
freev (r);
}
else
return l;
}
當時我在想,如何跳出迴圈呢?該死的是遞迴,另外,nextarg也很奇怪,上面說已經對args+1了,我想,豈不每次都往後面跑了嗎?程式碼如下:
static int
nextarg (char *str)
{
if (*args == NULL)
return 0;
else
{
int r = strcoll (*args, str) == 0;
args += r;
return r;
}
}
你想args是字串指標陣列,r為1,此時,args指下向一字串,也就是每比較一次,就向後跑一個,比如,本來應該是問同一個人吧,變成每問一次,就跳到下一位,有些燒腦子。不過, 我也不要求一次性全搞懂,知道大概,下次再看吧。