1. 程式人生 > >coreutils4.5.1 expr.c 原始碼分析2

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指下向一字串,也就是每比較一次,就向後跑一個,比如,本來應該是問同一個人吧,變成每問一次,就跳到下一位,有些燒腦子。不過,  我也不要求一次性全搞懂,知道大概,下次再看吧。