2017年7月21日 星期五

DD 新頭 - DDH-07



從 Karasu Tanba 購入 DDH -07 的娃頭, 20170427 收到, 這是已經完妝的頭, 含運費 1600nt。我個人覺得很漂亮, 很喜歡。

fig1 ddh-07 fig2 官娃 candy

fig 1 的脖子洞和 fig2 官娃的有所不同, 多了一個凹處, 不知道是幹麻用的, 而且比較緊, 插到 candy 素體時不好插入, 我很擔心把 ddh-07 或是素體弄壞 (畢竟我目前只有一個素體), 最後還是大膽插上 ddh-07 頭, 看來沒什麼大礙。


沒有眼睛有點嚇人, 我知道, 所以想辦法來裝上眼睛, 之前購入一個雪初音的 22mm 的眼睛, 正好派上用場, 這個眼睛本來是要用來替換 candy 的眼睛, 不過官娃的眼睛用熱溶膠黏住, 我不想破壞官娃的眼睛, 一直等到購入這顆娃頭才裝上。



但問題來了, 我知道有種叫「眼泥」的東西可以用來黏眼睛, 但我不知道去那買, 最後得知有一種隨意黏的黏土可以用, 不用到娃店買, 一般的書局就有了, 知道這情報後, 我立刻飛奔某書局, 也如預期找到了隨意黏的貼土, 選了 40 元的那個, 來試試吧!



這是第一次黏眼睛, 果然不是很容易, 貼土黏的很醜, 眼睛對齊了很久, 總算順利黏上, 沒有像鬥雞眼, 過程還蠻有趣的。呵呵, 好可愛阿! 裝個頭髮一定更好看。



看起來很成功呢! 感覺這個 saber 的馬尾頭髮很搭, 再來看看完成體吧!

該搭配那個服裝造型呢? 選了冬天的水手服, 有馬尾、黑絲襪, 應該是滿足每個男人心目中完美女孩的點, 就這樣決定了, 翻出黑色版本的水手服, 換裝時間到了。

水手服算是蠻好穿的, 但是拔掉頭會比較容易穿上, 我第一次拔掉 DD 頭就是為了穿上手水服, 嚇死我了。目前的服飾是初音未來的官服, 脫掉也還蠻容易的, 長靴也不難脫 (穿上它比較難點), 在搭配最近獲得的小書包, 更有學生妹的樣子了。



果然有了眼睛之後就不再那麼恐怖了呢! 不過還是和我想像中的水手服學生有點差距, 到底是哪裡不對了呢?

最後換上雪初音的服飾, 果然有了藍眼睛之後, 和官娃相比, 有了七分像, 這是自製娃頭的好處, 官娃娃頭的眼睛用熱融膠黏上, 不好卸掉, 我也不打算動官娃娃頭的眼睛, 以後在補足所有娃頭, 就有更多不同造型。



雪初音果然還是要搭藍色的眼睛, 看起來又漂亮多了, 這次的造型先穿上阿麼衣 (防染衣), 其實有點來不及了, 整個娃體嚴重的被染成黑色, 哪有被染色才穿防染衣的, 不過遮住圓形關節球讓 DD 娃看來又有不同的風格, 也很不錯, 可以搭配這麼多的配件是 bjd 娃的魅力所在。

2017年7月14日 星期五

[DD] Hatsune (初音) Candy

裝扮後第一眼看上去, 哇! 還蠻可愛的嘛! 「初音未來」的造型真是有一套, 連不是初音的 DD 官娃, 整起造型也很好看。

既上次 cos 雪初音後, 這次要來 cos 初音未來, 就是黑黑的那隻。這隻也是在 DD 界很受歡迎的角色, 我想就算沒有買初音的娃友, 可能也都有雙馬尾的這頂頭髮吧!

講到這頂頭髮, 很長, 很難整理, 還蠻麻煩的, 上次那雪初音那頂, 我幾乎都要天天梳, 真的累人。

如果你很熟悉初音, 應該會覺得 fig 1 的圖哪裡怪怪的, 我太大意了, 沒看清楚就購入, 不過也還好, 整體看來效果並不差。

fig 1.
  1. 上衣: 99 rmb, 【SEN】BJD/SD/DD娃娃/娃衣/女裝/洋裝/淑女裝/裙子/初音COS裝
  2. 除了上衣的初音服飾: 2500, 從娃圈社團購入。
  3. 頭髮 (非官方頭髮): 800, 從娃圈社團購入。
我沒那麼喜歡初音, 不想為了這套衣服就購入 "初音 DD"。「只要有官服就好了。」我是這麼想的, 但並不總是會有那麼好的運氣, 有娃友願意釋出, 而且價格可能不便宜, 以及會有很多人一起搶標, 這些都充滿了不確定性, 要買到官服, 難如登天, 我抱著隨緣的心態, 等待機會的到來。

item 2 不知道為什麼沒有我想像中的搶手, 可能是沒有附上耳機和頭髮, 或是很多人都有了, 所以我目前還缺耳機, 這個我猜想應該是很難單獨收到了, 隨緣隨緣。

先把衣服穿上, 初音的服飾算是單純, 很輕鬆就換上, 麻煩的是那個頭髮。

花了不少時間整理頭髮, 總算看起來沒有那麼糾結了, 如何把頭髮戴好對我來說一直是個問題, 在我笨拙的雙手下, 總算把這頂頭髮戴好了。

這種雙馬尾的還有一種是用夾的, 也就是得用雙馬尾的部份是和頭髮分開的, 雙馬尾上會有夾子, 夾上後就變成雙馬尾的樣子, 我有一頂雪初音的頭髮就是這樣, 但我一直沒辦法好好的夾住, 直到我看到娃友提供的方法。

官方的服飾據說會有染色的情形, 甚至還順便賣起防染色的衣服, 對於染色問題我早已看開, 染就染了吧, 不過套上黑色絲襪還蠻搭的, 也間接提供防染色的功能。

對於 DD 要怎麼擺出好看的姿勢一直沒有頭緒, 通常我就是找找官方的圖, 然後試著擺擺看, 其實還蠻難的, 擺出來的姿勢通常沒有官方的好看, 也不知道其中訣竅在那, 只得慢慢嘗試。

而我的拍攝技巧也不算高明, 通常只要看起來還可以我就接受了, 沒像有些娃友們有那麼高品質的傑作, 我也不會修圖把腳架去掉, 對我而言那太麻煩, 我還沒要求到那樣的地步。

搭配可動手, 總算可以有比較豐富的姿勢, 不再是呆板的基本手勢, 雖然沒辦法很完美的比出任何手勢, 但也豐富了整體造型。



放上一張雪初音的服飾做對照, 有不同的風格, 突然覺得, 應該來買第二顆頭了, 而在這之後過了不久, 我也如願買到上妝的 ddh-07 的頭, 下一篇再來分享這顆新頭。

2017年7月7日 星期五

c++ RAII VS unix signal

RAII 很美好, 威力也很強大, 但一遇到 unix signal, 就沒轍了。

b.cpp
 1 #include <typeinfo>
 2 #include <iostream>
 3 #include <cstdlib>
 4 using namespace std;
 5 
 6 #include <signal.h>
 7 #include <unistd.h>
 8 
 9 int i=5;
10 void sig_handle(int sig)
11 {
12   cout << "sig: " << sig << endl;
13   i=0;
14   exit(0);
15   return;
16 }
17 
18 
19 class A
20 {
21   public:
22     A()
23     {
24       cout << "A ctor" << endl;
25     }
26     ~A()
27     {
28       cout << "~A dtor" << endl;
29     }
30 };
31 
32 
33 
34 int main(int argc, char *argv[])
35 {
36   A a;
37   signal(SIGINT, sig_handle);
38   while(1);
39   // c++ compiler 在這裡插入 A::~A()
50   return 0;
51 }

按下 Ctrl-C 之後, 解構函式不會發動, 這是很自然的, 因為 c++ 編譯器將解構函式安插在 39 那行, 而 Ctrl-C 按下後, 會執行 sig_handle, 不會在回到 39 那行, 自然無法發動解構函式。

RAII 破功? 雖然很不想承認, 但這是真的。c++ 神話破功了。

不過只要 a 是 global object, 那就沒問題, 解構函式還是會正常發動, 算是不幸中的大幸。

回到 local object 的版本, 那該怎麼辦? 如何讓 sig_handle 回到 39 那行呢?

我先是用了 exception handle, 但是失敗。

c.cpp
 1 #include <typeinfo>
 2 #include <iostream>
 3 #include <cstdlib>
 4 using namespace std;
 5 
 6 #include <signal.h>
 7 #include <unistd.h>
 8 #include <setjmp.h>
 9 
10 int i=5;
11 jmp_buf env;
12 
13 void sig_handle(int sig)
14 {
15   cout << "sig: " << sig << endl;
16   i=0;
17   throw 5;
18   return;
19 }
20 
21 
22 class A
23 {
24   public:
25     A()
26     {
27       cout << "A ctor" << endl;
28     }
29     ~A()
30     {
31       cout << "~A dtor" << endl;
32     }
33 };
34 
35 int main(int argc, char *argv[])
36 {
37   A a;
38   try
39   {
40     signal(SIGABRT, sig_handle);
41     while(1);
42   }
43   catch (...)
44   {
45     cout << "ex" << endl;
46   }
47   return 0;
48 }

ref:
Throwing an exception from within a signal handler
24.9 Using a Separate Signal Stack

我推測可能是 sig_hanlde 發動的方式和一般函式呼叫不同, sig_handle 無法回到 main, 或是回到 main 但無法正確配對到 catch 的程式碼, signal handle 連 stack 都不同於這個程式。

使用了 gdb 來驗證看看, gdb 會攔截 sigint, 我改用 sigabort, sigabort 會傳給要除錯的那支程式, 得到

gdb debug sigabort
 1 Program received signal SIGABRT, Aborted.
 2 main (argc=1, argv=0xffd83224) at c.cpp:41
 3 41      while(1);
 4 (gdb) n
 5 sig_handle (sig=6) at c.cpp:14
 6 14  {
 7 (gdb) n
 8 15    cout « "sig: " « sig « endl;
 9 (gdb) bt
10 #0  sig_handle (sig=6) at c.cpp:15
11 #1  <signal handler called>
12 #2  main (argc=1, argv=0xffd83224) at c.cpp:41

看起來好像是 main call sig_handle, 但實際上可能沒有這麼單純, 不管他了, 反正就是不能用 exception handle 處理這問題。換 exception handle 破功。

再用另外一招, 改用 setjmp/longjmp, 這就沒問題了, 解構函式正常發動。

bsjlj.cpp
 1 #include <typeinfo>
 2 #include <iostream>
 3 #include <cstdlib>
 4 using namespace std;
 5 
 6 #include <signal.h>
 7 #include <unistd.h>
 8 #include <setjmp.h>
 9 
10 int i=5;
11 jmp_buf env;
12 
13 void sig_handle(int sig)
14 {
15   cout << "sig: " << sig << endl;
16   i=0;
17   longjmp(env, 5);
18   return;
19 }
20 
21 
22 class A
23 {
24   public:
25     A()
26     {
27       cout << "A ctor" << endl;
28     }
29     ~A()
30     {
31       cout << "~A dtor" << endl;
32     }
33 };
34 
35 
36 
37 int main(int argc, char *argv[])
38 {
39   A a;
40 
41   signal(SIGINT, sig_handle);
42   if (0 == setjmp(env))
43   {
44     while(1);
45   }
46   else
47   {
48     cout << "longjmp" << endl;
49   }
50   return 0;
51 }

結束了嗎? 還沒, 不要說 signal 這麼複雜的東西, exit 就可以讓 dtor 沒轍了。

r.cpp
 1 #include <typeinfo>
 2 #include <iostream>
 3 #include <cstdlib>
 4 #include <cstdio>
 5 using namespace std;
 6 
 7 #include <signal.h>
 8 #include <unistd.h>
 9 
10 class A
11 {
12   public:
13     A()
14     {
15       printf("A ctor\n");
16     }
17     ~A()
18     {
19       printf("~A dtor\n");
20     }
21 };
22 
23 int main(int argc, char *argv[])
24 {
25   A a;
26 
27   exit(1);
28   return 0;
29 }

A::~A() 不會發動呢! 那當然, 看看 r.dis, A::~A 根本沒被插到 main 裡頭, 能發動才有鬼。

r.dis (objdump -CSd r)
 1 int main(int argc, char *argv[])
 2 {
 3  8048e3c:       8d 4c 24 04             lea    0x4(%esp),%ecx
 4  8048e40:       83 e4 f0                and    $0xfffffff0,%esp
 5  8048e43:       ff 71 fc                pushl  -0x4(%ecx)
 6  8048e46:       55                      push   %ebp
 7  8048e47:       89 e5                   mov    %esp,%ebp
 8  8048e49:       51                      push   %ecx
 9  8048e4a:       83 ec 14                sub    $0x14,%esp
10   A a;
11  8048e4d:       83 ec 0c                sub    $0xc,%esp
12  8048e50:       8d 45 f7                lea    -0x9(%ebp),%eax
13  8048e53:       50                      push   %eax
14  8048e54:       e8 69 00 00 00          call   8048ec2 <A::A()>
15  8048e59:       83 c4 10                add    $0x10,%esp
16
17   exit(1);
18  8048e5c:       83 ec 0c                sub    $0xc,%esp
19  8048e5f:       6a 01                   push   $0x1
20  8048e61:       e8 fa 2a 0a 00          call   80eb960 <exit>
21
22 08048e66 <__static_initialization_and_destruction_0(int, int)>:
23   return 0;
24 }

按照慣例, 如果是 global object, 就算呼叫 exit, 依然可以正常發動 dtor。global object 是不是很好用呢?

至於為什麼 global object 可以正常發動 dtor, 沒什麼特別, 在離開 main 之後, c++ runtime library 補了一段程式碼在這邊, 會把所有的 global/static object 的解構函式執行起來。

_exit 就不行了。

2017年6月30日 星期五

俄羅斯方塊 (1) - 旋轉方塊

這回合來談談方塊的旋轉是怎麼做的?

其實也一樣是那 4 個點的座標變化而已。我的旋轉方式以 p2 為支點來轉動, 也就是轉動時, p2 是不改變座標的。

一樣是 z Tetromino。

  • Tetromino Z.svg Z: two stacked horizontal dominoes with the top one offset to the left.

以下畫出 z Tetromino 的四個旋轉圖, 應該就很清楚了。

旋轉一

0

1

2

3

4

5

6

1


*
p0

*
p1




2



*
p2

*
p3



3







4







5



































旋轉一 p0, p1, p2, p3 的座標是:
p0: (2,1)
p1: (3,1)
p2: (3,2)
p3: (4,2)

旋轉二

0

1

2

3

4

5

6

1





*
p0



2


*
p2
*
p1



3


*
p3




4







5



































旋轉二 p0, p1, p2, p3 的座標是:
p0: (4,1)
p1: (4,2)
p2: (3,2)
p3: (3,3)


旋轉三

0

1

2

3

4

5

6

1









2

*
p3

*
p2





3


*
p1
*
p0



4







5



































旋轉三 p0, p1, p2, p3 的座標是:
p0: (4,3)
p1: (3,3)
p2: (3,2)
p3: (2,2)

旋轉四

0

1

2

3

4

5

6

1




*
p3




2

*
p1

*
p2




3

*
p0





4







5



































旋轉四 p0, p1, p2, p3 的座標是:
p0: (2,3)
p1: (2,2)
p2: (3,2)
p3: (3,1)

把規則找出來, 轉成程式碼便是:

list 1.
int ZTetromino::rotate()
{
  state_ = ((state_+1) % 4);
  printf("state_: %d\n", state_);
  switch (state_)
  {
    case 0:
    {
      printf("mode 0 \n");
      p0_.y_-=2;
      ++p1_.x_;
      --p1_.y_;
      ++p3_.x_;
      ++p3_.y_;
      break;
    }
    case 1:
    {
      printf("mode 1 \n");
      p0_.x_+=2;
      ++p1_.x_;
      ++p1_.y_;
      --p3_.x_;
      ++p3_.y_;
      break;
    }
    case 2:
    {
      p0_.y_+=2;
      --p1_.x_;
      ++p1_.y_;
      --p3_.x_;
      --p3_.y_;
      break;
    }
    case 3:
    {
      p0_.x_-=2;
      --p1_.x_;
      --p1_.y_;
      ++p3_.x_;
      --p3_.y_;

      break;
    }
  }
}

list 1. case 1 便是 '旋轉一' 切換到 '旋轉二' 的座標變化:
p0 的 x 座標 +2 
p1 的 x 座標 +1
p1 的 y 座標 +1
p3 的 x 座標 -1
p3 的 y 座標 +1

旋轉規則就是這樣把圖一個個畫出來, 然後找到的, 所以我才會說我在筆記本畫了一堆圖形。

  • Tetromino S.svg S: two stacked horizontal dominoes with the top one offset to the right.

s Tetromino 也是用同樣的方法實作出來的。

相信其他 Tetromino 也難不倒你了。

這次我用了 c++ 的多型來處理不同的 Tetromino, 只要繼承 base class, 就可以很快的寫出一個新的 Tetromino, 當然也可以不要用多型, 不過這次選用多型帶來比較多的好處, 我就這麼用了, 雖然 virtual function 帶來了較差的執行效率, 但瑕不掩瑜, 程式的確比較容易撰寫。

fig 1. 旋轉與消掉方塊

fig 1 展示了方塊的旋轉, 以及最重要的消掉方塊, 下回合來談談怎麼消掉方塊。fig 1 的畫面也比《俄羅斯方塊 (0) - 移動方塊》那時候的好看多了, 有了顏色和邊界, 這沒用到什麼特別函式, 全都是用 printf 印出來的, 這是一步步進化的結果。

我目前只打算靠 printf 來印出畫面, 不靠 ncurses 有定位能力的函式, 還蠻有挑戰性的。不過因為我用 c++, 其實是用 cout 印出來的。

2017年6月24日 星期六

俄羅斯方塊 (0) - 移動方塊

寫組譯器實在太無聊了, 整天和 elf section, x86 machine code 搏鬥, 真苦! 我寫了太多練功型的程式, 這些程式大多是幫助我理解某個概念, 實用性不高, 成就感也不大, 我決定先暫停組譯器, 來寫個自己也會用的程式。

就是你了, 皮卡丘, 阿, 不是 ... 是俄羅斯方塊。

俄羅斯方塊大家都會玩, 但不是每個人都會寫俄羅斯方塊, 稍微思考後, 覺得還蠻難的, 沒有任何突破點, 絲毫無法下手寫程式。

fig 0. 決戰俄羅斯

這個程式我 200x 年就想寫了, 不過直到最近 (201706) 我才真的動手, 大概是練功型程式我真的有點膩了, fig 0 是決戰俄羅斯, 我想寫的是這個版本, 和一般的俄羅斯方塊有什麼不同呢? 他有一些特殊方塊, 會有特殊功能, 例如可以射出子彈消掉方塊的特殊方塊。我很喜歡那些特殊功能的方塊, 能增加遊戲性, 當然還有其雙人的對戰功能, 也很有趣, 在同一個螢幕上就可以 2 人對戰, 不用透過網路。

fig 1. 大型電玩 - 俄羅斯方塊

fig 2. 軟體世界 - 決戰俄羅斯

決戰俄羅斯
英文名Face to Face
類型智育遊戲
研發蔡祈岩王功華
代理軟體世界
發行年1990
平台PC
作業系統DOS

軟體世界金磁片獎的作品, 是移植大型電玩俄羅斯方塊而來。台灣交通大學大一學生蔡祈岩與王功華製作並於 1990 年由智冠科技 (當時叫軟體世界) 出版, 加入兩人對戰及許多道具並首創與電腦對戰功能, 可以兩人對戰也可以使用特殊道具。遊戲中可以自由選擇等級及遊戲規則, 遊戲以操作流暢與合理設計將這款遊戲推向全新高度迅速風靡中港台及亞洲各國是華人自製電腦遊戲的濫觴。

有五種特殊道具 (ref figure 3):

  1. 人造衛星 (S) 可以消去方塊
  2. 小鳥 (F) 可以填補方塊
  3. 炸彈 (B) 可以消去少許範圍的方塊
  4. 還有一個道具 (16T) 可以重壓消掉數列的方塊
  5. 道具 (4) ???

(20170707 我已經寫出這前四種道具)

這是移植大型電玩的版本。

它有個很討厭的磁片保護, 非常煩人。

fig 3.


這個遊戲真的好玩, 要是我能在 linux 玩該有多好, 當然可以用 dos 模擬器玩, 但如果是 linux native 程式, 那就更好了, 這個念頭大概只能靠自己開發了。

再回到怎麼寫俄羅斯方塊, 俄羅斯方塊要完成好多部份, 把目標簡化很重要, 這個步驟我已經非常熟悉, 第一個想到的就是, 先來實作移動方塊吧! 這樣感覺簡單了一些, 但其實還是沒方向 ...

俄羅斯方塊有很多不同的方塊, 英文術語是 - Tetromino, 我決定先實作 z Tetromino。

  • Tetromino Z.svg Z: two stacked horizontal dominoes with the top one offset to the left.

最後的成果就是 fig 1。

fig 1. 移動 z Tetromino

這個 console mode 程式應該嚇壞你了, 怎麼這麼醜, 但他包含了整個概念, 把繪製漂亮的 UI 功能抽離出來, 這樣會比較容易專住在主要核心上, 要畫出好看的圖形可不是件容易的事情, 先不把焦點放在圖形處理上。只要 c++ 標準程式庫就可以執行了, 其中用到了 sleep, 這個可能不是標準, 但用 for loop 也可以完成 delay 的動作。

z Tetromino 可以順利的移動, 也可以旋轉, 並且不會超過邊界。這一回合就只做這件事, 感覺簡單了一些, 但實際上我在筆記本上畫了不少方塊圖, 才得以想到方法, 完成這個功能。

fig 2. z Tetromino 設計方式
一個方塊是由 4 個點構成, 只要好好的描述好這 4 個點, 就可以完成移動、旋轉。

z Tetromino 由 fig 2 那 4 個點構成, p0, p1, p2, p3:
p0: (2,3)
p1: (3,3)
p2: (3,4)
p3: (4,4)

往右移動就是在改變 p0, p1, p2, p3 這些座標, 你一定也想到要怎麼做了吧! 把所有點的 x 加 1 就搞定了。

p0: (3,3)
p1: (4,3)
p2: (4,4)
p3: (5,4)

換成程式碼就是這樣:
int right()
    {
      ++p0_.x_;
      ++p1_.x_;
      ++p2_.x_;
      ++p3_.x_;
    }

簡單到不用說明, 上、下、左也是一樣的道理。

再來要怎麼畫出畫面呢? 宣告 2X2 array container_[10][20] 來處理, 把

p0: (2,3)
p1: (3,3)
p2: (3,4)
p3: (4,4)

的座標標成 1, 其他標 0, 程式碼類似這樣:

container_[2][3] = 1;
container_[3][3] = 1;
container_[3][4] = 1;
container_[4][4] = 1;

就會輸出以下的 array:

1111111111
1000000001
1000000001
1011000001
1001100001
1000000001
1000000001
1000000001
1000000001
1000000001
1000000001
1000000001
1111111111

程式只要去檢查這個 array 所有值, 是 1 的就畫 1, 0 的就畫 0, 這樣就可以畫出整個俄羅斯方塊的畫面。

另外一個難題是碰撞偵測, 我怎麼知道方塊到底了, 外圍的 1 是我的邊框, 在 down() 的函式會更新 4 個點的座標, 檢查新座標的位置是不是 1, 若是, 就表示遇到邊框或是其他方塊, 原理就是這樣。

下次再來談談旋轉, 其實也一樣, 把 4 個點的座標改動而已。

我本來打算參考 fig 3 的書來實做俄羅斯方塊, 不過我收到時, 已經做出 2 個方塊可以移動、旋轉, 也可以消掉方塊的版本了, 儘管如此, 此書還是有不少可以參考的地方, 厄 ... 這本書是使用 trubo pascal 完成俄羅斯方塊。

不過我也不打算看別人的想法, 我要自己想出來怎麼寫俄羅斯方塊。

fig 3. 2017/06/20 09:23:17 訂購於 https://tw.bid.yahoo.com/item/100125001955, 20170623 收到 200+80

ref:
【Arduino】自己的掌機自己作(3)-俄羅斯方塊遊戲

2017年6月18日 星期日

[敗家] 便攜式微型投影儀+72寸熒屏幕布

在 fb 看到 【便攜式微型投影儀+72寸熒屏幕布!NT$2999】 的廣告
訂單產品顏色:【便攜式微型投影儀+72寸熒屏幕布!NT$2999】 1 x 2999 |

全球最小專業投影儀!台灣首發!限時特價!】
活動價: NT$2299 /件 NT$5288
銷量:788件
限時折扣:4.3折
時間剩餘:9小時11分36秒
剩餘件數:25
派送方式:貨到付款

20161114 訂購, 你的訂單信息

訂單號:317
訂單名:
訂單產品名:【全球最小專業投影儀!台灣首發!限時特價!】
訂單手機號:
訂單產品顏色:【便攜式微型投影儀+72寸熒屏幕布!NT$2999】 1 x 2999 |
訂單產品尺碼:
訂單邮箱:
訂單地址:
訂單留言:
發貨: 未發貨
快遞信息:未查到相關信息

貨物到達時間為5-7個工作日,有問題請諮詢客服

一直想買台投影機, 就這台吧, 我來當白老鼠試試看。20161114 訂購, 20161123 收到, 為什麼會需要 9 天呢? 我心中有了些想法, 直到看到貨物的包裝時, 才驗正我的想法。



fig 1 2499 nt fig 2 199 rmb

這個就是中國貨運來的包裹阿! 馬上到淘寶找了一下, 果然就看到了, 199 rmb, 和 2299 nt 比起來有不少的差距。心裡頭不太舒服, 應該先在淘寶查查看的。而且不管什麼時候看, 都剩下 9 小時的搶購時間。

雖然可以退貨, 但我的確想要這台投影機, 不會因為這差價就退貨, 但之後在 fb 再看到類似的, 都會很注意, 先到淘寶查詢。

那陣子常看到四軸飛行器, 果然淘寶都有。facebook  上目前有越來越多類似的販賣手法, 以貨到付款方式讓人安心, 購買前請在三考慮, 不知道退貨時會不會遇到麻煩, 查看買賣討論區的討論, 看看是否可靠。



體積真的很小, 沒有自動對焦的功能, 需要自己將投影機前後移動, 找到最清晰的距離。有附上遙控器, 用遙控器比較容易操作機器。



影片播放畫面如下, 使用 sd card 的方式來讀取影片, 效果不算好, 不過聲音很大聲, 很像手機的山寨機, 我喜歡聲音大一點。



ref:
貨到付款的詐騙

2017年6月8日 星期四

compiler [6] - code generator - 四則運算

合抱之木,生於毫末;九層之臺,起於累土;千里之行,始於足下。
終於到這一步了, code generator 就是常聽到的程式碼產生器, 在編譯系統中, 就是用來產生組合語言。由於對 x86 32bit 模式比較熟悉, 我輸出的是 x86 32bit 的組合語言, 有點過時, 我知道; 現在都是 x64 耶! 我知道, 那你還用 x86 32bit, 就老人懶的學新東西嘛!

code generator 比直譯器難很多, 我想大家都知道, 但難上多少呢? 光四則運算的部份大概就是 1:20 這樣的難度, 只處理四則運算 (Elementary arithmetic) 就讓我花了不少時間, 也吃了不少苦頭, 遇到很多平常沒想到的細節。

如果還要加入變數的支援, 難度可能會來到 1:40, 相當難, 我已經簡化某些細節, 例如: 有號或是無號整數, signed, unsigned, 數字長度是 4 byte 還是 1 byte, 若把這些細節都考慮進來, 那得花很久的時間。對於開發編譯器的人, 我實在相當佩服。os kernel 的開發也很難, 他們屬於兩種不同的難, 並不會因為寫過 os kernel, 就讓學習編譯系統簡單些, 也是得從頭學習。

和 interpreter 一樣, 尋訪那個 AST, 在看到 1+2 時, 想辦法產生對應的組合語言, 說來簡單, 但光是加法就讓我絞盡腦汁才想出怎麼實作, 到了這步, 我刻意不參考任何書籍、網路資料, 我想測試一下自己的程度, 靠自己土砲出來, 很有成就感。原來我也寫的出來嘛!

以下是一些心得:

[暫時物件]
這不是指 c++ 的 "物件", 雖然在 c++ 書籍中很常聽到這個詞彙, 但直到我寫了 code generator 的四則運算, 我才真的體會到什麼是 "暫時物件"; 也不是指 "物件導向" 中的那個 "物件", 在寫 interpreter 就知道這個概念, 但那時還很模糊, 我現在有了新的體認。如果 "暫時物件" 迷惑了你, 那用 "中間結果" 這個詞彙也許更能貼近其意思。

1+2 在 interpreter 會產生一個暫時物件, 如果用 ASTNode 來存這個 3, 3 這個 ASTNode 就是運算 1+2 之後的暫時物件 (中間結果), 由於是 "暫時" 的而且也在記憶體佔據了空間, 所以要還回去, 重點來了, 該怎麼還, 什麼時候還呢?

哎呀, 真是難倒人, GC 就是在搞定這個, 我沒有提怎麼處理 interpreter 產生的暫時物件, 因為我根本沒處理。所以我那個玩具 c interpreter 會有 memory leak 的問題, 怎麼解, 交給 os gc 了。

但如果是在 code generator 上呢? 要如何產生這個暫時物件的程式, 又要產生歸還記憶體的程式碼呢? 沒 gc 那麼複雜, 比你想像中的還簡單。

mov $1, %eax
add $2, %eax

以 x86 32bit 組合語言為例子, 這樣就搞定 1+2, 疑! 暫時物件在哪裡? 這個例子不容易看出暫時物件 (因為根本就沒有暫時物件), 來看看下個例子:

1+2+5

mov $1, %eax
add $2, %eax
add $5, %eax

疑! 好像也沒看到暫時物件。

再看一個
(1+2)+(3+4)

1+2
mov $1, %eax
add $2, %eax

3+4
mov $3, %eax
add $4, %eax

eax 的值被覆蓋了, 這樣怎麼把他們加起來呢?

3+4 改用 ebx

mov $3, %ebx
add $4, %ebx

然後把 eax, ebx 加起來

add ebx, eax

有點政治程式敏感的人應該會開始覺得有點不對勁, 再來看下一個例子。

(1+2)+((3+4)+(5+6))

沒辦法很快寫出來吧
照前面的想法, 這個得用到 ecx 了, 如果有更長的運算式, 可能得用到 ezx 了, 但是 x86 沒有 ezx 可以用。暫存器也不是無限的, 回頭來看 1+2+5 的例子, 來看看引入暫時物件後的組合語言, 先算 1+2,

1+2+5

mov $1, %eax
add $2, %eax

然後產生暫時物件 3, 要把 3 存在哪裡呢? 都可以, 你想得到的地方都可以, 但其實選擇不多, 不是 stack 就是 heap。我選擇了 stack, 這樣比較容易釋放這個暫時物件。

push %eax # 把 3 這個暫時物件放到 stack, 再來做 +5 的動作。

pop %eax
add $5, %eax

這個組合語言很漂亮, 把暫時物件 3 所佔用的 stack 記憶體清掉 (因為 pop 出來了), 並做了剩下的 +5 計算。結束了嗎? 還沒, 產生的 8 (3+5) 也是暫時物件, 存到 stack 中吧!

push %eax

這時候的 stack 有一個 8 的暫時物件, 什麼時候要用到, 什麼時候要歸還這個 stack 佔用的記憶體, 就看程式碼怎麼寫, 以這個例子來說, 由於不用存到其他變數中, 就 pop 掉它吧。

由於產生的暫時物件我放在 stack, 所以在進入函式的開頭, 要產生留下這個 stack 空間的組合語言, 這可不容易, 要怎麼知道這個 stack 要留下多少 byte 呢? 當然要計算產生的暫時物件需要幾個 byte? 很難吧, 我放棄這部份的計算, 先 hard code 一個值。

光是搞定加法運算如: 1+2+5 就花了我很長的時間, 依樣畫葫蘆, 乘法沒那麼難了, 好不容易搞定 3*5*7, 來把他們組合起來, 1+2*3 馬上就出錯了, 讓我之前絞盡腦汁想的方法完全破功, 得從頭開始再想一個方法, 後來我又加入了 <, >, =, 複雜度又提高了。程式碼開始變得醜陋了。

比起 interpreter 的四則運算, code generator 的細節多很多, 很容易就產生錯誤的組合語言, 當然運算的結果也是錯的。

如果你用 gcc -S 看 1+2+5 產生後的組合語言程式碼, 肯定是看不到這樣的作法的, 因為 gcc 會很聰明的產生 8, 而不會如我說的產生那麼多組合語言, 若說 gcc 是聰明的 c compiler, 那我這個 simple c compiler 就是傻瓜編譯器, 他傻瓜我聰明。

另外有一個比較不傷腦筋的作法是完全使用 stack。

1+2+3
mov $1, %eax
mov $2, %ebx
push %eax
push %ebx
pop %ebx
pop %eax
add %eba, %eax

push %eax
mov 3, %ebx
push %ebx
pop %ebx
pop %eax
add %eba, %eax

把 1 和 2 push 到 stack, 然後做相加的動作, 再 push 到 stack, 再做 + 3 的動作, 這方法比較容易理解, 其實就是 postorder 或是 stack base machine 的作法, 我不想用這個作法, 看起來不厲害阿, 自己胡亂想了上述的方法。

看完暫時物件後, 再來看其他問題, 我把四則運算分為幾個部份:

  • immediate value
  • variable
  • type

[immediate value]
就是做 1+2 這種, 在暫時物件那段說完了。

[變數]
prog 1.
0 func1()
1 int c;
2 c=5;
3 c+2;

這種運算式就是用到了變數, 引入了變數會帶來很多問題, 這個變數在哪裡? 是 global 還是 local 變數。

所以要分配 c 的位址, 並且紀錄這個資訊, 這樣在 prog 1. L3 的 c+2 才知道 c 的位置是什麼, 才能去那個位址把值拿出來和 2 相加, 你肯定無法理解我的意思, 有興趣做一次看看吧!

一樣要用表格紀錄起來, 本科系的朋友應該知道這就是 symbol table, 陷阱在於, 在 function 開頭要保留 stack (eps = eps-4), 用來分配給 c 這個變數 (ebp-4 的位址保留給 c 變數)。

high address return address 上一個函式
new ebp
esp
old ebp  (上一個 fcuntion 的 ebp)  的 stack frame
esp (esp-4 的 esp) c (epb-4) func1 的


stack frame




func1:
push %ebp
mov %esp, %ebp
subl $4, %esp # 留給 c 變數的記憶體空間

push %ebp 的行為: 先把 esp-4, 再把 %ebp 放入 stack

[型別大小]
int i
char x;

x = 5678; // 超過 255, 會做 implicit conversion, 5678 的值會被截掉

i=300;
x=20;
i+x; // 將 x 提升到 int 大小

節錄 Linux C 编程一站式学习 表 15.2 來說明, 在 c 語言的 3+5; statement 中的 3, 是什麼型別? int 嗎? unsigned int? char ? 到底是那個呢? 有組合語言經驗的朋友馬上就聯想到這和暫存器有關系, 我應該產生

mov $3, %ah
mov $3, %ax
mov $3, %eax

中的那個呢?

顆顆, 我沒考慮這問題, 直接產生 mov $3, %eax 這個版本, 這是對的嗎? 3 對應到表 15.2 的 1A 欄位, 3 用 int 就可以存起來, 所以 3 的型別就是 int, 1234U 呢? 自己看連結的內容吧, 我要表達的是四則運算的型別部份沒想像中的簡單, 在我的簡單版本中, 是完全不考慮這些的, 現在你可以體會我說的難度來到 1:40 感覺吧, 事實上還有 conversion, integer promotion 要處理, 很複雜的。我的版本還沒考慮 float/double。
表 15.2. 整数常量的类型
后缀A 十进制常量B 八进制或十六进制常量
1 无
int
long int
long long int
int
unsigned int
long int
unsigned long int
long long int
unsigned long long int
2 u或U
unsigned int
unsigned long int
unsigned long long int
unsigned int
unsigned long int
unsigned long long int
2 l或L
long int
long long int
long int
unsigned long int
long long int
unsigned long long int
4 既有u或U,又有l或L
unsigned long int
unsigned long long int
unsigned long int
unsigned long long int
5 ll或LL
long long int
long long int
unsigned long long int
6 既有u或U,又有ll或LL
unsigned long long int
unsigned long long int

以上的幾個問題都被我部份簡化了, 但難度還是很高, code generator 真的不簡單。

最後, 終於來到最後了, 再忍耐一下, 這篇文章快結束了。

1 > 2 的 > 是輸出 cmp 這個指令, 雖然不是四則運算但是得提一下, 因為 if node 需要做 > 的運算, 所以要先支援 > 的 code gen, 才能處理 if node。

1 > 2 要產生什麼樣的組合語言呢? 難倒我也, gcc 編譯器依然不產生 1 > 2 的組合語言, 沒的抄, 我用了一個辦法得知這件事:

rel.c
1 int gr(int c)  
2 {      
3   return c<2;
4 }      

gcc -m32 -S rel.c = rel.s
 1       .file   "rel.c"
 2       .text
 3       .globl  gr
 4       .type   gr, @function
 5 gr:
 6       pushl   %ebp
 7       movl    %esp, %ebp
 8       cmpl    $1, 8(%ebp)
 9       setle   %al
10       movzbl  %al, %eax
11       popl    %ebp
12       ret
13       .size   gr, .-gr
14       .ident  "GCC: (GNU) 5.4.0"
15       .section        .note.GNU-stack,"",@progbits

終於得知要用 cmpl, setle 來得到 1 > 2 的運算結果:

1>2 得到 0
1<2 得到 1

這個 0, 1 是什麼 type 呢? C 標準規定要是 int, 所以要以 int 的大小回傳這個結果, 如果用 char type 來接 int 這個結果, 要做 implicit conversion, 別擔心, 我都沒做。