DIY 書套

 DSC05765_rescale DSC05764_rescale

不喜歡書本高高低低的,尤其還是同一家出版社的,這樣買書套很麻煩。不過離開小學後,好像只剩漫畫書比較容易塞進書套裡頭。

工具

先簡單知道一下什麼是:(參考這裡這裡

  1. 封面
  2. 封底
  3. 書背 

請先準備:prepare

  1. 書套:不知道有沒有比較正式的名稱,平時我都去重慶南路上的亞典書局買的,不過這分店已經結束營業了,唉!他們店內販售的外文書籍也是用這種書套包起來。  
  2. 切割尺:它有一面鑲有鐵片,不怕切割時刮到,上頭也有許多方格幫忙定位,價錢有點高。  
  3. 長尺:切割尺比較沉,有時候切割上不方便,準備一把短些、靈活的長尺比較方便,當然也要找上頭有方格的。
  4. 刀片:好的刀片很重要~
  5. 紙膠帶:很多膠帶會脫膠,會讓書本黑黑、黏黏的,紙膠帶是不錯的選擇。
  6. 電池:不一定要是電池,只要能用來壓線、方便靈活就可以。

動手吧~

 Step1Step2

Step1. 把書放在書套偏右,上下約莫留 8cm (視喜好而定),然後裁割。

Step3

Step2. 以書背為基準,把書本“橋”到書套中間

Step3-b Step3-a

Step3. 這邊,我們從封面做起,書背朝自己,大約捏出一條折線

Step4-a Step4-b

Step4-c Step4-d

Step4. 把封面翻過來朝桌面,再壓一次折線,這折線很重要,等會密不密實都靠它了。電池這邊派上用場了,用來壓實,這可是用手指無法比擬的。

 Step5-a Step5-b

Step5. 稍微把書本後退,然後再用電池壓實折線,最後也可以把書本退開,從頭用電池再壓一次。用這麼多次,知道這折線有多重要了吧?!

Step6

Step6. 封底也是同樣作法。這邊忘了拍一張照片作為參考,不過可以看上圖:把藍線以右假裝沒看到,就是封面、封底跑完 Step4 ~ 5 後的樣子。

Step7

Step7. 準備裁掉過長的部份,這邊用長尺書底做出一個直角,然後用刀片切出一條線。總共有四條線要切

Step8-a 

Step8-a. 四條線切完後,把書本拿開,但不要把兩旁折頁(紅色區域)打開,就會像上圖一樣,隱隱約約還可以看到切線(藍色表示)。

Step8-b 
Step8-b. 將書套壓平,基於兩點成一直線的原理,我們已經可以知道書的上下緣線。沿著藍線頂點構成的紅線下刀。但別急著把尺移開。

Step9-a 

Step9-a. 尺夠長的話,可以看到它橫跨過書套的折頁,也就是書的緣線,輕輕劃過,做個劃線記號就好,除非刀功一流,保證不會割到隔一層的書套去。

Step9-b

Step9-b. 四個角落都輕劃過完,可以看到 Step9-a 的劃線和 Step5 的折線形成一個四邊形,就裁掉它們吧~

Step9-c Step9-d

Step9-c. 看到這個像雙十字的形狀了嗎?這就是書套的形狀了。這時有個修正的機會,以紅線為準,去切掉不直的藍線。這誤差通常來自書套沒壓平,所以往往尾端會有些許多出來。

 Step10-a Step10-b

Step10-a. 先把書套折頁翻過去,再把上下兩邊的折頁蓋上去。

 Step10-cStep10-d

Step10-b. 一樣用電池密實。封面、封點都要做過一遍

Step11-a  Step11-bStep11-c

Step11. 把書本拿回來,部份放入書套,套過書本的幫忙,可以方便地用紙膠帶將折頁固定,之後再把封底完整放入書套。已經完成一邊了!

Step12-aStep12-bStep12-c   

Step12. 把書本翻到另一面,同時把書套折頁放入,利用書的重量一次整理一邊的上下折頁。過程中,手指記得把上下折頁的外緣用力拉往中間。

Step13

成果!不放心可以拿東西壓一壓,讓書套定型。

DSC05760

我都這樣用 Windows Internals 的~

Interrupt Handling in Windows —— part 1: Using WinDbg

透過 WinDbg 的 !idt 指令,我們可以看到 IDT 的內容:

0: kd> !idt

Dumping IDT:

37:    806e6864 hal!PicSpuriousService37
3d:    806e7e2c hal!HalpApcInterrupt
41:    806e7c88 hal!HalpDispatchInterrupt
50:    806e693c hal!HalpApicRebootService
62:    81bbe6f4 atapi!IdePortInterrupt (KINTERRUPT 81bbe6b8)
63:    8191a974 USBPORT!USBPORT_InterruptService (KINTERRUPT 8191a938)
             portcls!CKsShellRequestor::`scalar deleting destructor'+0x26 (KINTERRUPT 8197cbb0)
73:    81962bec USBPORT!USBPORT_InterruptService (KINTERRUPT 81962bb0)
82:    81bd4bec atapi!IdePortInterrupt (KINTERRUPT 81bd4bb0)
83:    81be1ae4 SCSIPORT!ScsiPortInterrupt (KINTERRUPT 81be1aa8)
93:    81965204 i8042prt!I8042KeyboardInterruptService (KINTERRUPT 819651c8)
a3:    81964044 i8042prt!I8042MouseInterruptService (KINTERRUPT 81964008)
b1:    81be423c ACPI!ACPIInterruptServiceRoutine (KINTERRUPT 81be4200)
b2:    81795bec serial!SerialCIsrSw (KINTERRUPT 81795bb0)
b4:    818765d4 NDIS!ndisMIsr (KINTERRUPT 81876598)
c1:    806e6ac0 hal!HalpBroadcastCallService
d1:    806e5e54 hal!HalpClockInterrupt
e1:    806e7048 hal!HalpIpiHandler
e3:    806e6dac hal!HalpLocalApicErrorService
fd:    806e75a8 hal!HalpProfileInterrupt
fe:    806e7748 hal!HalpPerfInterrupt

單純的 !idt 只會輸出 ntoskrnl.exe 之外的 interrupt handler ,若想看到全部 IDT 項目,則須加上 -a:

!idt -a

More

0: kd> !idt

Dumping IDT:

37:    806e6864 hal!PicSpuriousService37
3d:    806e7e2c hal!HalpApcInterrupt
41:    806e7c88 hal!HalpDispatchInterrupt
50:    806e693c hal!HalpApicRebootService
62:    81bbe6f4 atapi!IdePortInterrupt (KINTERRUPT 81bbe6b8)
63:    8191a974 USBPORT!USBPORT_InterruptService (KINTERRUPT 8191a938)
             portcls!CKsShellRequestor::`scalar deleting destructor'+0x26 (KINTERRUPT 8197cbb0)
73:    81962bec USBPORT!USBPORT_InterruptService (KINTERRUPT 81962bb0)

一般的情況下,!idt 已經給予足夠的資訊了,以上面的 IDE 介面來說:

  1. 它的 interrupt number 是 62
  2. ISR 是位於 atapi module 的 IdePortInterrupt

不過好奇心驅使,可以發現:當去查詢 atapi!IdePortInterrupt 的位置時,卻不是 0x81bbe6f4 、也不是 0x81bbe6b8

0: kd> x atapi!IdePortInterrupt
f980467e atapi!IdePortInterrupt = 

而是 0xf980467e ,加上一個 IDT entry 竟然有這麼多 addresses ,真是詭異。所以我們可以懷疑一下,!idt 這個指令的輸出是經過加工的。不過必須找到一些更基礎的資訊來支持這個懷疑。在 x86 處理器的 protected mode 中, IDT 是由 IDTR (Interrupt Descriptor Table Register)所紀錄,所以來看看 registers 的狀況如何:

0: kd> r
eax=00000001 ebx=ffdff980 ecx=80554780 edx=000003f8 esi=00000003 edi=19d73a7c
eip=8052c5ec esp=805523b0 ebp=805523c0 iopl=0         nv up ei pl nz na po nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00000202
nt!RtlpBreakWithStatusInstruction:
8052c5ec cc              int     3

什麼?沒有 IDTR ,原來要打 r idtr

0: kd> r idtr
idtr=8003f400

知道 IDT 的位置後,必須算出適當的位移量來找到 atapi!IdePortInterrupt ,參考一下 Intel 的文件:Intel® 64 and IA-32 Architectures Software Developer's Manual Volume 3A: System Programming Guide 5.10 可以知道 IDT entry —— interrupt gate 的格式:

InterruptObject06

所以,atapi!IdePortInterrupt 等於:

image

0: kd> dd 8003f400+0x8*0x62
8003f710  0008e6f4 81bb8e00 0008a974 81918e00
8003f720  00081d28 80548e00 00081d32 80548e00
8003f730  00081d3c 80548e00 00081d46 80548e00

跟 IDT Entry 第一個 address 是符合的,所以可以確定它是 interrupt handler 的 entry point:

62:    81bbe6f4 atapi!IdePortInterrupt (KINTERRUPT 81bbe6b8)

那麼該怎麼從這 0x81bbe6f4 entry point 到 atapi!IdePortInterrupt  去執行呢?線索就在 KINTERRUPT 81bbe6b8 上, Windows 透過 interrupt object 來管理系統上的 interrupts ,driver programmers 必須向 interrrupt object 註冊自己的 ISR ,那麼 interrupt handling flow 才會有機會執行到這個 ISR 。Windows Internals chapter 3 有針對 interrupt 作詳細的說明。透過 dt 指令可以看到存放在 0x81bbe6b8 的 kinterrupt 變數。

0: kd> dt nt!_kinterrupt 81bbe6b8
   +0x000 Type             : 22
   +0x002 Size             : 484
   +0x004 InterruptListEntry : _LIST_ENTRY [ 0x81bbe6bc - 0x81bbe6bc ]
   +0x00c ServiceRoutine   : 0xf980467e     unsigned char  atapi!IdePortInterrupt+0
   +0x010 ServiceContext   : 0x81bbd030 
   +0x014 SpinLock         : 0
   +0x018 TickCount        : 0xffffffff
   +0x01c ActualLock       : 0x81bbe91c  -> 0
   +0x020 DispatchAddress  : 0x80546660     void  nt!KiInterruptDispatch+0
   +0x024 Vector           : 0x162
   +0x028 Irql             : 0x5 ''
   +0x029 SynchronizeIrql  : 0x5 ''
   +0x02a FloatingSave     : 0 ''
   +0x02b Connected        : 0x1 ''
   +0x02c Number           : 0 ''
   +0x02d ShareVector      : 0 ''
   +0x030 Mode             : 1 ( Latched )
   +0x034 ServiceCount     : 0
   +0x038 DispatchCount    : 0xffffffff
   +0x03c DispatchCode     : [106] 0x56535554

注意到沒有?放在 nt!_kinterrupt+0x03c 位置的 DispatchCode 其實就是 IDT Eetry。

0: kd> .formats 0x81bbe6b8+0x03c
Evaluate expression:
  Hex:     81bbe6f4
  Decimal: -2118392076
  Octal:   20156763364
  Binary:  10000001 10111011 11100110 11110100
  Chars:   ....
  Time:    ***** Invalid
  Float:   low -6.90244e-038 high -1.#QNAN
  Double:  -1.#QNAN

既然確定了 0x56535554 就是 entry point ,那麼可以反組譯一下 DispatchCode 來確定是否會執行到 atapi!IdePortInterrupt

0: kd> u 81bbe6f4 81bbe6f4+0x106*0x4
; Setup Trap Frame
81bbe6f4 54              push    esp
81bbe6f5 55              push    ebp
81bbe6f6 53              push    ebx
81bbe6f7 56              push    esi
81bbe6f8 57              push    edi
81bbe6f9 83ec54          sub     esp,54h
81bbe6fc 8bec            mov     ebp,esp
81bbe6fe 89442444        mov     dword ptr [esp+44h],eax
81bbe702 894c2440        mov     dword ptr [esp+40h],ecx
81bbe706 8954243c        mov     dword ptr [esp+3Ch],edx
81bbe70a f744247000000200 test    dword ptr [esp+70h],20000h
81bbe712 0f852b010000    jne     81bbe843
81bbe718 66837c246c08    cmp     word ptr [esp+6Ch],8
81bbe71e 7423            je      81bbe743
81bbe720 8c642450        mov     word ptr [esp+50h],fs
81bbe724 8c5c2438        mov     word ptr [esp+38h],ds
81bbe728 8c442434        mov     word ptr [esp+34h],es
81bbe72c 8c6c2430        mov     word ptr [esp+30h],gs
81bbe730 bb30000000      mov     ebx,30h
81bbe735 b823000000      mov     eax,23h
81bbe73a 668ee3          mov     fs,bx
81bbe73d 668ed8          mov     ds,ax
81bbe740 668ec0          mov     es,ax
81bbe743 648b1d00000000  mov     ebx,dword ptr fs:[0]
81bbe74a 64c70500000000ffffffff mov dword ptr fs:[0],0FFFFFFFFh
81bbe755 895c244c        mov     dword ptr [esp+4Ch],ebx
81bbe759 81fc00000100    cmp     esp,10000h
81bbe75f 0f82b6000000    jb      81bbe81b
81bbe765 c744246400000000 mov     dword ptr [esp+64h],0
81bbe76d fc              cld
81bbe76e 8b5d60          mov     ebx,dword ptr [ebp+60h]
81bbe771 8b7d68          mov     edi,dword ptr [ebp+68h]
81bbe774 89550c          mov     dword ptr [ebp+0Ch],edx
81bbe777 c74508000ddbba  mov     dword ptr [ebp+8],0BADB0D00h
81bbe77e 895d00          mov     dword ptr [ebp],ebx
81bbe781 897d04          mov     dword ptr [ebp+4],edi
81bbe784 64f60550000000ff test    byte ptr fs:[50h],0FFh
81bbe78c 750d            jne     81bbe79b
; Pass KInterrupt object to KiInterruptDispatch
81bbe78e bfb8e6bb81      mov     edi,81BBE6B8h ; 0x81bbe6b8 是我們的 interrupt object
81bbe793 e9c87e98fe      jmp     nt!KiInterruptDispatch (80546660); 0x80546660 interrupt object 中的 DispatchAddress 一欄

如果我們再追進 nt!KiInterruptDispatch :

0: kd> u nt!KiInterruptDispatch L50
nt!KiInterruptDispatch:
80546660 64ff05c4050000  inc     dword ptr fs:[5C4h]
80546667 8bec            mov     ebp,esp
80546669 8b4724          mov     eax,dword ptr [edi+24h]
8054666c 8b4f29          mov     ecx,dword ptr [edi+29h]
8054666f 50              push    eax
80546670 83ec04          sub     esp,4
80546673 54              push    esp
80546674 50              push    eax
80546675 51              push    ecx
80546676 ff1590904d80    call    dword ptr [nt!_imp__HalBeginSystemInterrupt (804d9090)]
8054667c 0bc0            or      eax,eax
8054667e 7440            je      nt!KiInterruptDispatch+0x60 (805466c0)
80546680 83ec0c          sub     esp,0Ch
80546683 833d6c56568000  cmp     dword ptr [nt!PPerfGlobalGroupMask (8056566c)],0
8054668a c745f400000000  mov     dword ptr [ebp-0Ch],0
80546691 7541            jne     nt!KiInterruptDispatch+0x74 (805466d4)
80546693 8b771c          mov     esi,dword ptr [edi+1Ch]
80546696 f00fba2e00      lock bts dword ptr [esi],0
8054669b 722b            jb      nt!KiInterruptDispatch+0x68 (805466c8)
8054669d 8b4710          mov     eax,dword ptr [edi+10h]
805466a0 50              push    eax
805466a1 57              push    edi
; 注意到了嗎?di 是 interrupt ojbect; edi+0Ch 是欄位 ServiceRoutine 也就是 0xf980467e atapi!IdePortInterrupt+0
805466a2 ff570c          call    dword ptr [edi+0Ch] 

終於,水落石出了!最後用 interrupt object 做一個總結:

image

  1. 當 interrupt 發生時,CPU 根據 interrupt number 到 IDT 描述找到對應的 DispatchCode
  2. DispatchCode 產生 trap frame ,然後呼叫對應的 kernel interrupt handler (nt!KiInterruptDispatch),並將 interrupt object 當作參數傳入
  3. Kernel interrupt handler 轉而呼叫對應的 ISR

那 !idt 輸出的格式就是:

Interrupt Number: IDT Entry Code Kernel/User Defined ISR (Corresponding interrupt object address)

是吧!再看一次:

0: kd> !idt

Dumping IDT:

37:    806e6864 hal!PicSpuriousService37
3d:    806e7e2c hal!HalpApcInterrupt
41:    806e7c88 hal!HalpDispatchInterrupt
50:    806e693c hal!HalpApicRebootService
62:    81bbe6f4 atapi!IdePortInterrupt (KINTERRUPT 81bbe6b8)
63:    8191a974 USBPORT!USBPORT_InterruptService (KINTERRUPT 8191a938)
             portcls!CKsShellRequestor::`scalar deleting destructor'+0x26 (KINTERRUPT 8197cbb0)
73:    81962bec USBPORT!USBPORT_InterruptService (KINTERRUPT 81962bb0)
82:    81bd4bec atapi!IdePortInterrupt (KINTERRUPT 81bd4bb0)
83:    81be1ae4 SCSIPORT!ScsiPortInterrupt (KINTERRUPT 81be1aa8)
93:    81965204 i8042prt!I8042KeyboardInterruptService (KINTERRUPT 819651c8)
a3:    81964044 i8042prt!I8042MouseInterruptService (KINTERRUPT 81964008)
b1:    81be423c ACPI!ACPIInterruptServiceRoutine (KINTERRUPT 81be4200)
b2:    81795bec serial!SerialCIsrSw (KINTERRUPT 81795bb0)
b4:    818765d4 NDIS!ndisMIsr (KINTERRUPT 81876598)
c1:    806e6ac0 hal!HalpBroadcastCallService
d1:    806e5e54 hal!HalpClockInterrupt
e1:    806e7048 hal!HalpIpiHandler
e3:    806e6dac hal!HalpLocalApicErrorService
fd:    806e75a8 hal!HalpProfileInterrupt
fe:    806e7748 hal!HalpPerfInterrupt

: )

相關閱讀

  1. Windows Debugging – Kernel Debugging with WinDbg and VMWare

Reference

  1. Windows Internals chapter 3
  2. Intel® 64 and IA-32 Architectures Software Developer's Manuals: http://www.intel.com/products/processor/manuals/

C++ and Beyond

大師們要開講了~

Scott Meyers, Herb Sutter, Andrei Alexandrescu, Seattle area, summer 2010!!!

This summer, Scott, Herb, and Andrei will host an intensive three-day technical event focusing on … Attendance will be limited to provide face time and direct interaction of attendees with speakers.

C++ and Beyond: 2010

C++ and Beyond will take place near Seattle, but you get to decide when it will take place. Please let us know what you think.

看了一下可以選的時段有:

  1. June 21-23, 2010
  2. August 16-18, 2010

題目可以選:

  1. Concurrency and parallelism - scalability and throughput
  2. Concurrency and parallelism - avoiding races and deadlocks
  3. Concurrency and parallelism - application responsiveness
  4. New programming languages (e.g., D, Go)
  5. High performance, maximizing execution speed
  6. C++0x lambdas
  7. What's new in C++0x (and starting to be available in compilers)

哇,好想去看看~

如果有興趣的話,可以去投票

程式設計師

Document

出來跑的,總是要還~

Design Review

Architect:加油,好嗎?

Coding

寫 Code 是一時的,maintain 是一世的…

Bug

We are extremely fortunate not to know precisely that kind of bug we live in.

Code Review

曾經有一段複製來的 code 擺在我的面前,
但是我沒有 refactor ,
直到新 feature 來的時候才後悔莫及,
如果上天能再給我一次機會的話,
我還是會 copy-and-paste,
如果非要在這分 code 上修改的話,
我希望是…下一版…

Performance

客戶:出來面對~

逛書店

趁著寒流發威,跑去重慶南路逛書店,心想這種冷天氣,應該比較少人出門,沒想到如意算盤打錯,書店滿滿都是人,還看到很多媽媽特地把小孩帶來書店消磨時間,好吧,我以我們是個書香社會為榮(那出版社怎麼一直倒?!)

貨幣崛起 Computer Architecture

一本娛樂用,一本自強不息~

Weekly Report (20091123 ~ 20091127)

每到星期五或星期一,我都會用 outlook 寫下跟這篇文章有著極類似標題的信給主管和同事們,不過同樣內容寫在這邊就會 considered harmful~(這是工程師的冷笑話吧!)


星期二 regular meeting 後,一直感到頭暈,龜毛地我在位子與廁所來回走了四次,爾後才決定要請假回家,心中一直懸在一份即將要截止的 design 上,這是某種程度的任重道遠嗎?還是不見棺材不掉淚?

往診所的路上,寒風颼颼,腦袋倒是熱轉不停,想起有人曾說程式設計師都很樂觀,不確定我是不是這樣,但上禮拜著手一份 POC 程式,打從星期一下手之際,就覺得快做完,當晚下班時審視一下,雖不中亦不遠矣!樂觀持續到星期四,連帶延誤 design 的進度…

咳咳,帶著深深地罪惡感走進問診室,醫生診斷後,說:快感冒了,但是還沒感冒,喉嚨有發炎,趕快吃個藥回去睡覺,對了,這不是流感!走出聽診室,總覺得話中有話,我看起來很像是會主動要求施打流感疫苗的人嗎?


POC 的驗證是大陸的同事負責,這禮拜有很多跟他們講電話的機會。原先覺得都是中文應該很好溝通,沒想到電話那頭,講得又快又有口音,加上一些英文單字,發現不太能習慣,倒是他們似乎很快就習慣我們!official build 聽成 official way、checked build 聽成七個 build … 這一定是 VoIP 掉封包了…


星期五晚上的公司總有一股特別的氣氛,彷彿阿宅都不阿宅了,每個人都有行程,好像再忙,都不能禮拜五加班啊!不過逆向操作的好處就是有股安定感,沒有嘈雜聲、步調也慢了下來,有種喝販賣機咖啡都像是在左岸咖啡館、design doc 也變文學鉅作了…

公車站牌前,過了好幾部新店客運的公車,我都選擇等下一班,明明就十點了,公車上的人卻異常的多,從他們的表情看來,不太像 happy 完後要回家,原來我剛剛並不孤單!等公車總是會胡思亂想:可以的話,希望可以永遠保持清醒!可是為了什麼,我們要保持清醒呢?為了做更多的工作?為了有更多的時間做讓自己快樂的事?什麼是讓自己快樂的事?什麼是人生的意義?渾渾噩噩為什麼就不好?因為渾渾噩噩後,我們會後悔?可是能接受自己大半輩子渾渾噩噩的人,最後又怎麼會後悔呢?


上個禮拜是到部門一年,老闆很 high 地跟我說:開口,你破冬了唷!

我茫茫然然的,總覺得最近有點沒勁~

Invalid Parameter Handler and Security Enhancements in the CRT

在 VC++ 的 CRT 中,除了標準的 CRT 之外,Microsoft 也加入安全性的檢查和報錯機制,許多原有的 functions 都被加上 _s 的後贅字。舉例來說:strcpy_s(), wcscpy_s(), _mbscpy_s() 各別是 strcpy(), wcscpy(), mbscpy() 的 security enhanced 版。這些 Security enhancement 的重點在於:

  1. Parameter validation
  2. Sized buffers
  3. Null termination
  4. Enhanced error reporting
  5. Filesystem security
  6. Windows security
  7. Format string syntax checking

針對上述項目的檢查與 enforcement ,security-enhanced CRT 一旦發現問題,是不會嘗試去修正或繞過的,錯了就是錯了,舉例來說:wcscpy_s() 在 copy 記憶體時,一旦發現 destination buffer 比 source buffer 小,它並不會自動進行 truncation ,參考下面這個從 MSDN 來的例子:

errno_t wcscpy_s(
   wchar_t *strDestination,
   size_t sizeInWords,
   const wchar_t *strSource 
);
strDestination sizeInBytes, sizeInWords strSource Return value Contents of strDestination
NULL any any EINVAL not modified
any any NULL EINVAL not modified
any 0, or too small any ERANGE not modified

從表中可以看到,一旦 destination 和 source 有不正確的配對,那麼就不會對 destination 進行動,自然也就沒有把字串 truncate 後複製過去的結果發生。因此如果 truncation 是期望的結局,那就必須改用 strncpy_s(), _strncpy_s_l(), wcsncpy_s(), _wcsncpy_s_l(), _mbsncpy_s(), _mbsncpy_s_l() 這類原本就有 truncate 語意的函式。所以了,引用一下 MSDN 總結:

... the secure functions do not prevent or correct security errors; rather, they catch errors when they occur. They perform additional checks for error conditions, and in the case of an error, they invoke an error handler (see Parameter Validation).

沒錯,security enhancement 強調的是參數使用前的檢查以及可錯誤發生後的處理,不是幫忙修正錯誤。

Parameter Validation

Parameter validation 機制會檢查傳入 _s functions 的參數,檢查包括:

  1. Checking pointers for NULL
  2. Checking that integers fall into a valid range
  3. Checking that enumeration values are valid
  4. Validate that the buffer is large enough before writing to it

當 CRT 發現 invalid parameter 時,它會呼叫 invalid parameter handler 來處理。 CRT 有預設的 invalid parameter handler ,它會呼叫 Dr. Watson 產生 dump ,然後跳出一個 Error report 程式詢問使用者要不要上傳這個錯誤報告。而程式若是以 debug mode 編譯,則是會跳出 assertion。

drwtsn32

Fig 1. Dr. Watson (不是這個華生?也不是裘德洛?)

WER

Fig 2. Windows Error Reporting 對話方塊

AssertionFig 3. Debug Mode 下跳出的 assertion failed 視窗

Where have all the Exceptions gone?

const wchar_t*  src = L"Hello";
wchar_t         dest[ 5 ];

__try {
    wcscpy_s( dest, _countof( dest ), src );
}
__except ( EXCEPTION_EXECUTE_HANDLER ) {
    // Error handle...
}

在某些程式裡頭會看到像上面那樣的片段程式碼,看起來似乎四平八穩:我們指定了 dest 和其大小,若是 src 長度超過了 dest , wcscpy_s() 透過 exception 報錯,我們就可以透過 SEH 來處理。但程式一執行,ㄜ,好像 crash 了… 不過現在我們已經可以可能的問題是什麼:若是程式沒有置換過 invalid parameter handler ,那麼是丟不出 exception 的,程式的 crash 便是可預期的結果。

Install my own Invalid Parameter Handler

/*!
 * \param[in] expression argument expression
 * \param[in] function   the name of the CRT function that received the invalid argument
 * \param[in] file       file name in the CRT source
 * \param[in] line       line in file
 * \param[in] pReserved  reserved
 */
void _invalid_parameter(
    const wchar_t* expression,
    const wchar_t* function, 
    const wchar_t* file, 
    unsigned int   line,
    uintptr_t      pReserved
);

上述是 invalid parameter handler 的 prototype。我們很容易仿造一個自己的,延續之前 wcscpy_s() 的例子,我們就可以實作一個會丟出 exception 的 handler:

void myInvalidParameterHandler( const wchar_t* expression,
                                const wchar_t* function, 
                                const wchar_t* file, 
                                unsigned int line,
                                uintptr_t reserved )
{
    wcout << L"expr:" << expression << endl
          << L"func: " << function << endl
          << L"file:" << file << endl
          << L"line:" << line << endl;

    RaiseException( 0,          // exception code
                    0,          // continuable exception
                    0, NULL );  // no arguments
}

然後透過 _set_invalid_parameter_handler() 來註冊:

wchar_t   dest[ 5 ];
const wchar_t*  src = L"Hello";

_invalid_parameter_handler oldHandler = _set_invalid_parameter_handler( myInvalidParameterHandler );

_CrtSetReportMode( _CRT_ASSERT, 0 );

__try {
    wcscpy_s( dest, _countof( dest ), src );
}
__except ( EXCEPTION_EXECUTE_HANDLER ) {
    cerr << "exception ... " << GetExceptionCode() << endl;
}

_CrtSetReportMode() 在這裡可以幫助 debug mode 下取消 assertion failed 視窗的跳出。如果對於 wcscpy_s() 的 errno_t 的 error code 念念不忘,則可以修改 SEH 的 filter expression 從 EXCEPTION_EXECUTE_HANDLE 到 EXCEPTION_CONTINUE_EXECUTION。

__try {
    errno_t e = wcscpy_s( dest, _countof( dest ), src );

    if ( EINVAL == e ) {
        // ...
    }
    else if ( ERANGE == e ){
        // ...
    }
}
__except ( EXCEPTION_CONTINUE_EXECUTION ) {
}

PS: EINVAL, ERANGE 這類 pre-defined error code 定義於 ERRNO.H 裡頭。

Reference

  1. MSDN on Security Enhancements in the CRT: http://msdn.microsoft.com/en-us/library/8ef0s5kh.aspx
  2. MSDN on Parameter Validation: http://msdn.microsoft.com/en-us/library/ksazx244.aspx
  3. MSDN on _set_invalid_parameter_handler: http://msdn.microsoft.com/en-us/library/a9yf33zb.aspx
  4. MSDN on errno, _doserrno, _sys_errlist, and _sys_nerr: http://msdn.microsoft.com/en-us/library/t3ayayh1.aspx

Qt 為什麼要有 QScopedPointer 呢?

不久前在 Qt Labs Blogs 上的一篇文章引起了我的注意,文章標題是:Introducing QScopedPointer ,介紹了即將在 Qt 4.6 現身的新成員 — QScopedPointer , Qt 4.6 的 preview 也已經放進了相關的文件。在 C++ 中使用 RAII 的手法是相當常見的,而且也納入標準之中的,或是 boost 也提供了 Smart Pointers library

為什麼 Qt 需要提供自己一套 Smart pointers 呢?

Qt 背後的動機令我感到好奇,一如我在 KAutoPointer: a new autoptr class for QPointer 討論串上看到鄉民 Ingo Klöcker 說的:

... Unfortunately, there appears to be quite some aversion against using boost's pointer template classes (a trivial compile time dependency) in KDE …

而的確,這也引起了一陣討論,下面收集了一些討論來瞭解一下:

  1. QT Labs Blogs:
    1. Introducing QScopedPointer
    2. Count with me: how many smart pointer classes does Qt have
    3. Some more pointing
  2. Mailing list or news
    1. KAutoPointer: a new autoptr class for QPointer
    2. OS news: Introducing QScopedPointer

題外話一下:有沒有興趣猜猜 Qt 提供了幾種 smart pointers 呢?在 Count with me: how many smart pointer classes does Qt have? 一文中列出了八種:

  1. QPointer (4.0)
  2. QSharedDataPointer (4.0)
  3. QExplicitlySharedDataPointer (4.3/4.4)
  4. QtPatternist::AutoPtr (internal class, 4.4)
  5. QSharedPointer (4.5)
  6. QWeakPointer (4.5)
  7. QGuard (internal class, 4.6)
  8. QScopedPointer (4.6)

呵呵(或,科科),是不是很有趣呢?回到正題, Count with me: how many smart pointer classes does Qt have? 一文解釋了為什麼不採用既有的 scoped pointers:

  1. C++0x 尚未通過,其實作也只有在 GCC 4.3 之後以及 MSVC 2010 beta 中才有;
  2. TR1 的實作品不夠;
  3. Boost 呢,ㄜ,Qt 認為它是“un-Qt-ish” API,或者該說是“horrible API”,這句話其實是沒有惡意的,只是想說兩者的命名哲學不同。此外,Qt 與 KDE 對於 binary compatibility 有著嚴格、複雜的要求,若是採用 boost 實作會破壞這相容性。(如果有興趣,可以參考一下 KDE TechBase 上的一篇文章:Policies/Binary Compatibility Issues With C++ ,最近的貢獻者就是 Count with me … 一文的作者: Thiago Macieira。)
  4. Qt 還是希望握有 scoped pointer 的 knowledge。

在我看來, Qt 期許自己成為一個成熟易用又跨平台的函式庫,它的支援環境可能沒有完整的標準 C++ 函式庫,又因為 C++ 缺乏統一的 ABI 支援,在某些應用上若沒有一些事先規範會讓事情發展到某種程度後窒礙難行,至於有多窒礙難行呢?我還不能說個所以然,因為自己沒有真的痛過,無法把那種罄竹難書講得感同身受 Orz …

延伸閱讀:

一些關於 ABI 、binary compatibility、modular mechanism 的討論:

  1. Qt Labs Blogs: Some thoughts on calling convention
  2. Qt Labs Blogs: Some thoughts on binary compatibility
  3. Imperfect C++: Part 2 - Surviving The Real World
  4. Essential COM: COM as a Better C++
  5. C++0x 有過相關的討論:
    1. Modules in C++
    2. Draft Proposal for Dynamic Libraries in C++
    3. 不過被打槍了!

Qt 4.5.2

自從 Qt 被 Nokia 併購後,似乎越來越活耀(?!),網站也改版了,不過以前的都會把 source code 版本直接放一個 link 出來,現在好像得去 ftp 下載,

Qt_FTP

編譯

官方的 binary 好像(不確定,我只試過 Qt SDK: Complete Development Environment 這包而已)只有給 mingw32 用的,如果想在 Windows 搭配 Visual C++ 使用,可以自己 compile ,請下載 qt-win-opensource-src-4.5.2.zip 這個檔案,不要下載 qt-all-opensource-src-4.5.2.tar.gz,編譯時可能會有下面的錯誤:

qscriptdebuggercodefinderwidgetinterface.cpp
debugging\qscriptdebuggercodefinderwidgetinterface.cpp(43) : fatal error C1083:
Cannot open include file: 'qscriptdebuggercodefinderwidgetinterface_p_p.h': No s
uch file or directory
qscriptdebuggercodefinderwidget.cpp
debugging\qscriptdebuggercodefinderwidget.cpp(43) : fatal error C1083: Cannot op
en include file: 'qscriptdebuggercodefinderwidgetinterface_p_p.h': No such file
or directory
qscriptdebugoutputwidgetinterface.cpp
qscriptdebugoutputwidget.cpp
qscriptbreakpointswidgetinterface.cpp
qscriptbreakpointswidget.cpp
qscriptbreakpointsmodel.cpp
qscripterrorlogwidgetinterface.cpp
qscripterrorlogwidget.cpp
qscriptenginedebugger.cpp
Generating Code...
NMAKE : fatal error U1077: '"C:\Program Files\Microsoft Visual Studio 8\VC\BIN\c
l.EXE"' : return code '0x2'
Stop.
NMAKE : fatal error U1077: '"C:\Program Files\Microsoft Visual Studio 8\VC\BIN\n
make.exe"' : return code '0x2'
Stop.
NMAKE : fatal error U1077: 'cd' : return code '0x2'
Stop.

而且編一次都要很久,禁不起這種錯誤打擊的!編譯可以使用這個指令,是我試過 ok 的:

configure -debug-and-release -no-sql-sqlite -no-qt3support -no-opengl -platform win32-msvc2005 -no-libtiff -no-dbus -no-phonon -no-phonon-backend -no-webkit 

其中最重要的是要指定 -debug-and-release ,因為預設是不會產生 debug 版本,有時會有 debug 或是 trace 上的困擾。如果電腦上安裝多個開發環境,請透過 -platform win32-msvc2005 指定 toolchain

nmake 後就是看 CPU 運動了!不過 vc 似乎無法吃光四核心 + 4G Ram 的效能 :(

好多 nmake

Qt_nmake

一個 cl

Qt_cl

吃不了什麼 CPU 和 RAM

Qt_CPU_Usage

The C++0x "Remove Concepts" Decision

正如 COdEfr3@K 所說:大件事!花了點時間 survey 一些網站~

  1. 大師說話了
    1. Bjarne Stroustrup: The C++0x “Remove Concepts” Decision
    2. Doug Gregor: What Happened in Frankfurt? (由 COdEfr3@K 補充)
    3. Herb Sutter: Trip Report: Exit Concepts, Final ISO C++ Draft C++ Draft in ~18 Months
    4. Bjarne Stroustrup Expounds on Concepts and the Future of C++ 
    5. Danny Kalev:
      1. The Removal of Concepts From C++0x
      2. The Rise and Fall of C++0x Concepts
      3. The Future of C++ 0x, Part 1
    6. The View (or trip report) from the July 2009 C++ Standard meeting
    7. The View (or trip report) from the July 2009 C++ Standard meeting (Part 2)
    8. Concepts Removed from C++0x
  2. 鄉民的討論
    1. Concepts Get Voted Off The C++ 0x Island
    2. The Removal of Concepts From C++0x
    3. TopLanguage: {C++} concept给毙了:(
  3. 翻譯~ 
    1. Concept 的道路
    2. Concept 失敗的原因

下面是從 DDJ 翻譯的,讓 Bjarne 說說看整個事件吧 : )

The C++0x "Remove Concepts" Decision

在 2009 七月的德國法蘭克福會議上, C++ Standards Committee 決定將 concepts 移出 C++0x。雖然這對那些致力於 concepts 多年以及熟知 Concepts 潛力的人們來說是個沈重的打擊,但幸運地,移除 concepts 對大多數的程式設計師並不太有直接的影響。對於現實世界的軟體開發, C++0x 和 C++98 相比,仍舊是個更極具表達力(expressive)和更有效用的(effective)語言。 Committee 對於 remove concepts 的用意:在於希望能限制它帶能的風險與確保整個時程(The committee acted with the intent to limit risk and preserve schedule),或許更好的 concepts 會在未來五年後出現。這篇文章將簡述移除 concepts 的理由以及 committee 做出此決定背後的的考量,此外也讓大家知道 C++ 的天還沒有塌下來啊~(儘管大把大把的謠言剛好說得相反!)

No "Concepts" in C++0x

2009 七月 ISO C++ Standards Committee (WG21) 的法蘭克福會議上,在討論 concepts 應用在 specifying requirements for template arguments 時被"脫鉤(decoupled)"了,對於我來說,一個比較不那麼外交的用語是被"猛烈扯開(yanked out)"了,這代表 concepts 不會出現 C++0x 或是它的 library 裡頭,在我看來,這是 C++ 的一大挫敗,但不是災難一場(disaster);一些替代方法都只會更糟。(That -- in my opinion -- is a major setback for C++, but not a disaster; and some alternatives were even worse)

我在 concepts 已經努力超過七年了,而花了更甚於此的時間在解決這類問題。許多對於 concepts 的工作已經非常久了,舉例來說(按年代排序):

唉,這不用說,我跟其他人對於這件事感到相當失望,而事實上,一些替代方案都只是更糟、於事無補的,而我也無法提供快速、便利的解法。

請注意到 C++0x 對於 C++ features 的改善與進步對大多數程式設計師看到和使用到的是沒有影響的。C++0x 與 C++98 相比,將仍舊是更具表達力的(expressive),它支援了:

  • Concurrent programming
  • A better standard library
  • 許多的改善使得寫出好程式碼更加容易、簡單(像是更有效率、更好維護)

尤其,對於那些我給的 C++0x 範例,要是沒用到 concepts 或是 requires 的更是不受影響(像是在 Evolving a language in and for the real world: C++ 1991-2006 at ACM HOPL-III )。請參考我寫的 C++0x FAQ,一些人們甚至對於 C++0x 比他們想像中的還簡單而感到歡欣。

Concepts 曾經是 C++0x 對於 template 有更好的理論基礎的重要核心 feature,像是說:

  • 提供更加穩固的 standard library specification
  • 扮演讓泛型程式設計(generic programming)成為主流使用的核心角色

現在,人們將在沒有語言的直接支援下使用 concepts。我對於未來最好的想像(scenario)是:我們在五年後的 C++ 裡頭得到了比現有 concepts 更好的 concepts。要能得到這樣的成果將會需要許多人非常專注的研究(但可不是"design by committee"喔!)。

What Happened?

Concepts 在過去幾年被發展出來,並在 2008 年,經過某些技術上的妥協後,進入到了 C++0x working paper 裡頭(妥協是自然而且必須的)。在實驗性的實作中已經足以應付"conceptualized" standard library,但還不到產品品質(but was not production quality),但我個人認為已經足夠稱為 proof of concept (POC)了!

(此段怕誤導、失真,段落後附有原文節錄)

我跟幾位 members 對於 concepts 有著同樣的考量:

Concepts 的設計,尤其是可用性(usability)這點,是否可為一般普通的程式設計師所掌握

會這麼說,是因為這決定了 concepts 設計的目標:

使 generic progarmming 可被大多數的程式設計師接受[BS&GDR2003]

但這目標在我現在看來已經做了極大的妥協:

Concepts 已經變成了(只存在)專家手上的工具,而不是一個讓 generic programming 更加可被接受的工具了。

過去一年半裡,我試著從使用者的角度去檢驗 C++0x,我擔心即用了 concepts 加持的 libraries 仍然對於程式設計師設下了一道新門檻。我覺得 concepts 的設計和它在 standard library 的使用並沒有充分反應我們過去幾年對於 concepts 的經驗。

「原文節錄」:My concern was with the design of "concepts" and in particular with the usability of "concepts" in the hands of "average programmers." That concern was shared by several members. The stated aim of "concepts" was to make generic programming more accessible to most programmers [BS&GDR2003], but that aim seemed to me to have been seriously compromised: Rather than making generic programming more accessible, "concepts" were becoming yet another tool in the hands of experts (only). Over the last half year or so, I had been examining C++0x from a user's point of view, and I worried that even use of libraries implemented using "concepts" would put new burdens on programmers. I felt that the design of "concepts" and its use in the standard library did not adequately reflect our experience with "concepts" over the last few years.

接著,幾個月以前,Alisdair Meredith (一位極具洞察力的 UK committee)和 Howard Hinnant (the head of the standard library working group)問了幾個很好的問題,問題是關於:

  • 誰應該直接使用到 concepts、
  • 使用 concepts 哪個部份、
  • 如何使用

「原文節錄」:who should directly use which parts of the "concepts" facilities and how

這問題導引出各方考量以及不同角度出發對於 usability 的討論。最後,在經過多次令人混淆的討論後,我把我的結論放在 [BS2009]。

幾個可能有點過度簡化的結論:

  • 現行定義的 concepts 難以使用,並將導致 concepts 不被使用(disuse),這可能是 template 不被使用或是 C++0x 不被公開採納。
  • 一些簡化方法[BS2009]仍舊可以描述 concepts ,使得 C++0x 可以出貨或是僅須稍稍的修改。

請注意到:這只是個非常簡略、囫圇吞棗的結論,standards committee 在討論時是非常有 polite 的,我們有著同樣的目標,總是避免直接的針鋒相對、對質詢問。不幸的是,當時的(內部)討論實在過於龐雜(許許多多牽涉到細節或沒有牽涉到細節的訊息)、令人困惑,在對於問題的定位及如何處理沒有浮現共識下,我在法蘭克福會議裡頭報告了幾項替代方案:

  • "fix and ship"
    • Remaining work: remove explicit "concepts," add explicit refinement, add "concept"/type matching, handle "concept" map scope problems
    • Risks: no implementation, complexity of description
    • Schedule: no change or one meeting
  • "Yank and ship"
    • Remaining work: yank (core and standard library)
    • Risks: old template problems remain, disappointment in "progressive" community ("seven year's work down the drain")
    • Schedule: five years to "concepts" (complete redesign needed) or never
  • "Status quo"
    • Remaining work: details
    • Risks: unacceptable programming model, complexity of description (alternative view: none)
    • Schedule: no change

我和一些其他的人贊同第一個方案("fix and ship")並認為這是可行的。然而,大多數的 committee 不是這麼認為,他們選擇了第二個方案("yank and ship," renaming it "decoupling")。在我看來兩者都比第三個方案("status quo")好,我對於投票結果的解讀是這樣的:(這段怕有情緒性字眼,也保留原文)

  • 對於 concepts 的支持者而言,他們沒有一個共識,整個想法看起來太過爭議
  • 一些人則是擔心 C++0x 雄心勃勃的時程表開始(ㄜ,在我不公正地看來,開始指責 concepts)
  • 一些人則是從頭到尾對於 concepts 就是興致缺缺

「原文節錄」:My interpretation of that vote is that given the disagreement among proponents of "concepts," the whole idea seemed controversial to some, some were already worried about the ambitious schedule for C++0x (and, unfairly IMO, blamed "concepts"), and some were never enthusiastic about "concepts."

有鑑於此,"fixing concepts"不再是一個迫於現實的選則了,本質來說,所有對於 concepts 的支持都將是更久之後的事("later" and "eventually")。我對於現在把 concepts 移除這件事也提出了會有無可避免的延遲的警告, 因為在沒有時程的壓力下,所有的設計抉擇都將被重新評估(譯注:我想是對於 concepts 的設計抉擇)。

「原文節錄」:I warned that a long delay was inevitable if we removed "concepts" now because in the absence of schedule pressures, essentially all design decisions will be re-evaluated.

令人驚訝的(或許吧),在法蘭克福的會議上沒有關於 concepts 的技術報告和討論。整個討論都只著重在時間問題上,我想這投票結果主要反應時間考量吧~

請不要譴責 committee 的謹慎,這不是 "Bjarne vs. the committee fight", 而是試著在大多數人的考量中取得平衡點。我跟其他人會失望,是因為我們沒有抓住"fix and ship"這個機會,而且 C++ 不是一個學院派、實驗性質的語言,除非成員們相信這麼做對於程式碼撰寫上帶來的風險非常低,否則他們就得、也必須拒絕接受它。整體來說,committee 代表的是數十億行程式碼,舉例來說,如果 C++0x 無法被公開採納或是無法在有 concepts 的情況下讓 unconstrained templates 得到長期的使用,C++ 社群將會分崩離析。因此,一個很虛(poor)的 concepts 設計可能會比沒有 concepts 帶來更糟的後果。兩難之間,我也選擇了移除 concepts ,我寧願選擇挫敗也不要一個災難來臨。

Technical Issues

懸而未解的 concepts,其問題在於,當把焦點放到 distinction between explicit and implicit "concept" maps 時(參考 [BS2009]):

  1. Should a type that meets the requirements of a "concept" automatically be accepted where the "concept" is required (e.g. should a type X that provides +, -, *, and / with suitable parameters automatically match a "concept" C that requires the usual arithmetic operations with suitable parameters) or should an additional explicit statement (a "concept" map from X to C) that a match is intentional be required? (My answer: Use automatic match in almost all cases).
  2. Should there be a choice between automatic and explicit "concepts" and should a designer of a "concept" be able to force every user to follow his choice? (My answer: All "concepts" should be automatic).
  3. Should a type X that provides a member operation X::begin() be considered a match for a "concept" C<T> that requires a function begin(T) or should a user supply a "concept" map from T to C? An example is std::vector and std::Range. (My answer: It should match).

我的建議和在"status quo before Frankfurt" 提到的解法(answer)是完全不同的。很明顯地,在這裡我必須忽略大部份的細節和設計理念、簡化一下我的解釋。

我無法在這裡重現整個技術討論,但是下面是我的結論:在 "status quo" 的設計裡頭, "concept" maps 被用來做到兩件事:

  • To map types to "concepts" by adding/mapping attributes and
  • To assert that a type matches a "concept."

不知何故(Somehow),後者未被視作不幸的、少見的必要性,反而被視為基本的功能。當兩個 concepts 有著語意上的差別時,我們需要的不是一個用來說明這個 type 符合這個 concept 但不符合那個 concept 的 assertion (這充其量,只是種 workaround,一種不直接接、用說明回擊問題本身的手法,原文:this is, at best, a workaround -- an indirect and elaborate attack on the fundamental problem),而是一個 assertion 可以說明這 type 帶有與這 concept 相同的語意,但沒有另外一個  concept 的語意。(fulfills the axiom(s) of the one and not the other "concept")

「原文節錄」:Somehow, the latter came to be seen an essential function by some people, rather than an unfortunate rare necessity. When two "concepts" differ semantically, what is needed is not an assertion that a type meets one and not the other "concept" (this is, at best, a workaround -- an indirect and elaborate attack on the fundamental problem), but an assertion that a type has the semantics of the one and not the other "concepts" (fulfills the axiom(s) of the one and not the other "concept").

舉例來說, STL 的 input iterator 和 forward iterator 有著關鍵性的語意差別:

You can traverse a sequence defined by forward iterators twice, but not a sequence defined by input iterators;

像是,在 input stream 上施行 multi-pass algorithm 不是個好主意!在 "status quo" 解法中,會強迫每個使用者說明:

What types match a forward iterator and what types match an input iterator

而我的建議的解決方式是:

若且唯若(If (and only if))

  1. 你想使用的語意並不是兩個 concepts 的共同部份,
  2. 而且 compiler 無法幫你推導出哪個 concept 才是較好的 metch 時,

你必須說明 type 的語意,像是說:

"my type supports multi-pass semantics."

其他人則可能是這麼說:

"When all you have is a 'concept' map, everything looks like needing a type/'concept' assertion."

在法蘭克福的會議上,我是這樣做結論的:

  • Why do we want "concepts"?
    • To make requirement on types used as template arguments explicit
      • Precise documentation
      • Better error messages
      • Overloading

不同的人們有不同的出發點以及排出不同的優先順序,然而,站在這高階的角度來看,可能會有些混淆,但應該是極少或不相衝突的。每個合理的、才走一半的 concept design 都提供了這些特性。

  • What concerns do people have?
    • Programmability
    • Complexity of formal specification
    • Compile time
    • Run time

我個人關注的是 programmability (ease of use, generality, teachability, scalability) ,其次是正式規格書的 complexity (40 頁的標準文件),再其他則關於 compiler time 和 run time 的考量。然而,我覺得實驗性質的實作(ConceptGCC [Gregor2006])中,constrained templates (使用 concepts)的 run time 可以做的跟 nonconstrained templates 一樣好、甚至更好。ConceptGCC 實際上非常慢,但我並未將它視為根本考量,當它用來驗證一個想法時,我們陷入了傳統上的兩難。這裡描述一下有點過度簡化的兩難問題:

  • "Don't standardize without commercial implementation"
  • "Major implementers do not implement without a standard"

各方面來說,細節設計和實驗實作構成了基本的妥協。

我對 concepts 的原則是:

  • Duck typing
    • The key to the success of templates for GP (compared to OO with interfaces and more)
  • Substitutability
    • Never call a function with a stronger precondition than is "guaranteed"
  • "Accidental match" is a minor problem
    • Not in the top 100 problems

我對於 concepts 的"minimal fixes"寫在 pre-Frankfurt working paper:

  • "Concepts" are implicit/auto
    • To make duck typing the rule
  • Explicit refinement
    • To handle substitutability problems
  • General scoping of "concept" maps
    • To minimize "implementation leakage"
  • Simple type/"concept" matching
    • To make vector a range without redundant "concept" map

細節請參考 [BS2009]。

No C++0x, Long Live C++1x

即使在 concepts 已經被移除後,下一版的 C++ standard 仍可能延遲推出。悲慘地說,將不會有 C++0x 了(除非你把 C++0x 這個小改版算進去)。我們一定得等到 C++1x,希望這個 x 是個很小的數字。希望還是在的,因為 C++1x 已經 feature complete 了(除非標準對於一些在 formal proposal for the standard 還有所堅持)。 "所有" 剩下、懸而未決的都只是龐雜的技術問題和意見了。

在我的 C++0x FAQ 中你可以看到一些 features 和討論,這裡僅是列出部份:

和 libraries:

  • Improvements to algorithms
  • containers
  • duration and time_point
  • function and bind
  • forward_list a singly-liked list
  • future and promise
  • garbage collection ABI
  • hash_tables; see unordered_map
  • metaprogramming and type traits
  • random number generators
  • regex a regular expression library
  • scoped allocators
  • smart pointers; see shared_ptr, weak_ptr, and unique_ptr
  • threads
  • atomic operations
  • tuple

    即使沒有了 concepts,C++1x 仍將會是個基於 C++98 大量改善的版本,尤其當你注意到這些 features 都讓你可以在最大的表達力(expressiveness)和彈性(flexibility)間的互動的。我希望我們會在新版的 C++ (或許五年後)中看到 concepts ,也或許我們應該稱呼 C++1y 或是 C++y!

    心得

    該看書了 >__<

  • How Lord of The Rings Should Have Ended

    讓我想到小時候寫作文應該要:

    1. 小題大做
    2. 大題小做
    3. 不要做作

    Boost.Program_options – Tutorial – Option Details and Multiples Sources

    Vocabulary

    1. Program options, options
    2. pair. (name, value)
    3. Token
    4. Position options
    5. Composing options

    Option Details

    沒錯,你得到它了!option value 不僅只侷限於 int 型別,此外還可以有其它下面的屬性(proerties)。完整程式可以在

    example/options_description.cpp

    找到。

    想像你在寫一個 compiler ,你可以設定它的 options:

    1. Otimization level
    2. Include paths
    3. Input files
    4. 其它做些有趣的事。

    下面的程式碼試著描述這些 options :

    int opt;
    po::options_description desc("Allowed options");
    desc.add_options()
        ("help", "produce help message")
        ("optimization", po::value<int>(&opt)->default_value(10), 
      "optimization level")
        ("include-path,I", po::value< vector<string> >(), 
      "include path")
        ("input-file", po::value< vector<string> >(), "input file")
    ;
    1. help:就像上個範例一樣。最好讓每個 case 都有自己的 help option。
    2. optimization:這裡的 optimization 有兩個特點:
      1. 我們在程式裡頭指定了變數的位址(&opt)。如此一來,當 values 被分析儲存後,我們就可以透過該變數取得 optimization 的 value。
      2. 我們指定了變數的 default value 為 10 。因此,當使用者沒有給 optimization 指定一個 value 時, value 會是 10。
    3. include-path:include-path 在這裡是一個特殊的例子,它只從 command line 讀入資料來使用 options_description。(原文是:The "include-path" option is an example of the only case where the interface of the options_description class serves only one source -- the command line.  看了上下文,總覺得這句有點詭異、跳 tone)。對於常用到的 options,使用者往往喜歡使用它們的 short option name 形式,而這也就是程式碼裡頭 "include-path,I" option name 想表達的意思:"I" 是 include-path 的 short name。
      另外,值得注意的是 "include-path" 的型別是 std::vector。Boost.Program_options 對於 vectors 有特別的支援,它可以多次出現在 option 輸入中,而所有的 values 都會被存放在一個 vector 裡頭。
    4. input-file:描述了一連串、將被處理的檔案。當然,一開始的時候像下面的方式指定 input file 是可以的:
      compiler --input-file=a.cpp
      但,如果和下面的方法相比,上面的方式似乎就有點不標準囉!
      compiler a.cpp

      我們將針對這問題來探討一下:

    像 a.cpp 這種在 parsing 的時候找不到 option name 的 token 的,在 Boost.Program_options 裡頭稱為 positional options。透過一些使用者的幫助,Boost.Program_options 有辦法可以知道 a.cpp 實際上就是 "--input-file=a.cpp" ,下面是妳/你需要的額外程式碼:

    po::positional_options_description p;
    p.add("input-file", -1);
    
    po::variables_map vm;
    po::store(po::command_line_parser(ac, av).
              options(desc).positional(p).run(), vm);
    po::notify(vm);

    開始兩行程式碼的意思是:所有的 positional options 需要被轉會成 "input-file" options。此外,這邊我們使用 command_line_parser 類別,而不是 parse_command_line function。parse_command_line 是一個只在簡單情況下會用到的 wrapper function 而已,但我們現在需要更多的資訊來做正確分析。

    現在,所有的 options 都已經被描述和分析了,我們就饒了自己吧,不要去管怎麼實作 compiler 的邏輯、細節,先用 print 的方式來表示各個 options。

    if (vm.count("include-path"))
    {
        cout << "Include paths are: " 
             << vm["include-path"].as< vector<string> >() << "\n";
    }
    
    if (vm.count("input-file"))
    {
        cout << "Input files are: " 
             << vm["input-file"].as< vector<string> >() << "\n";
    }
    
    cout << "Optimization level is " << opt << "\n";     

    下面是個編譯、執行這個範例的方法:

    $bin/gcc/debug/options_description --help
    Usage: options_description [options]
    Allowed options:
      --help                 : produce help message
      --optimization arg     : optimization level
      -I [ --include-path ] arg : include path
      --input-file arg       : input file
    $bin/gcc/debug/options_description
    Optimization level is 10
    $bin/gcc/debug/options_description --optimization 4 -I foo a.cpp
    Include paths are: foo
    Input files are: a.cpp
    Optimization level is 4

    歐喔,還有一個小問題!我們還是可以使用"input-file"來指定,而 Usage 也是這樣寫的,這可能會混淆了我們的使用者,如果能把這資訊隱藏應該會不錯,不過就留到下一個例子吧。

    Multiptles Sources

    很快地,妳/你就會發現都得把所有的 compiler options 填好是一件令人抓狂的事。想想看,難道妳/你希望使用者裝了一個新的 library 後每次使用妳/你的 compiler 都得一個一個把 options 填好嗎?如果能她/他決定了一些選項是每次都要使用到的呢?最好的情況是產生一個 common settings 然後搭配 command line 來使用。

    當然,把 config file 跟 command line 搭配使用是有必要的。舉例來說:config file 的 optimization level 會被 command line 的輸入蓋過;然而 include path 卻是需要有累加效果的。

    來看個 code 先,完整的程式可以在

    example/multiple_sources.cpp

    找到。option 定義有兩個有趣的點:

    1. 我們宣告了多個 options_descriptions 實體(instances)。為什麼呢?因為不是所有的 options 都是相近的,有些 options,像是 input-file 不應該出現在自動化 help message 裡頭;有些 options 只在 config files 裡頭才有意義。
    2. 最後,讓 help message 有點結構化、而不只是串列表是不錯的主意。

    下面讓我們宣告幾個 option groups 吧:

    // Declare a group of options that will be 
    // allowed only on command line
    po::options_description generic("Generic options");
    generic.add_options()
        ("version,v", "print version string")
        ("help", "produce help message")    
        ;
        
    // Declare a group of options that will be 
    // allowed both on command line and in
    // config file
    po::options_description config("Configuration");
    config.add_options()
        ("optimization", po::value<int>(&opt)->default_value(10), 
              "optimization level")
        ("include-path,I", 
             po::value< vector<string> >()->composing(), 
             "include path")
        ;
    
    // Hidden options, will be allowed both on command line and
    // in config file, but will not be shown to the user.
    po::options_description hidden("Hidden options");
    hidden.add_options()
        ("input-file", po::value< vector<string> >(), "input file")
        ;        

    請注意到宣告 include-path option 時呼叫的 composing method,這告訴了 library 不同來源的 values 應該被組合在一起(composed together)。

    我們可以用 options_description 的 add method 來進一步的組織 option groups 中:

    po::options_description cmdline_options;
    cmdline_options.add(generic).add(config).add(hidden);
    
    po::options_description config_file_options;
    config_file_options.add(config).add(hidden);
    
    po::options_description visible("Allowed options");
    visible.add(generic).add(config);

    分析和儲存 values 跟以往的作法相同,除了我們得額外呼叫了 parse_config_file 和呼叫 store 兩次之外。如果有些 value 在 command line 和 config file 中都出現了,那會發生什麼事?通常來說,先儲存下來的 value 會被視為優先,這也是 -optimization option 的設計,而對於 composing options 來說(像是 include-file ),這些 values 則是會被合併。

    下面是個編譯、執行這個範例的方法:

    $bin/gcc/debug/multiple_sources
    Include paths are: /opt
    Optimization level is 1
    $bin/gcc/debug/multiple_sources --help
    Allows options:
    
    Generic options:
      -v [ --version ]       : print version string
      --help                 : produce help message
    
    Configuration:
      --optimization n       : optimization level
      -I [ --include-path ] path : include path
    
    $bin/gcc/debug/multiple_sources --optimization=4 -I foo a.cpp b.cpp
    Include paths are: foo /opt
    Input files are: a.cpp b.cpp
    Optimization level is 4

    第一個呼叫是使用 config file。第二個則多使用了 command line 的輸入。正如我們所看到的,include path 的 value 是從 command line 和 config file 合併來的,而 optimization 則是從 command line 取得。

    Reference

    1. Boost 1.39.0 Doc: Chapter 13. Boost.Program_options

    See Also

    1. Boost.Program_options – Introduction and Tutorial 

    Boost.Program_options – Introduction and Tutorial

    最近因為需要一些自動化的程式看了 Boost.Program_options 這個 library ,這系列文章主要內容都是翻譯Boost 1.39.0 Doc 裡頭,希望還有點時間可以加上一些 trace 的心得和紀錄,希望沒有誤人子弟、教壞仔大小、對大家有幫助~

    Vocabulary

    1. Program options, options
    2. Config file
    3. Command line
    4. pair. (name, value)

    Introduction

    Boost.Program_options 是個可以讓程式設計師獲得 program options 的函式庫,什麼是 program options 呢?就是那種由使用者提供,可能是 command line 或是 config file 的 (name, value) 組合(pairs)

    那為什麼需要使用 Boost.Program_options 呢?為什麼說它比我們依靠直覺寫出來的程式碼還好呢?

    • 簡單。Boost.Program_options 很小、提供的新增 options 的語法也簡單。像是自動化的 option value 的型別轉換(type conversion)、變數存放。
    • 比較好的錯誤回報。 當分析(parsing)command line input 出錯時,Boost.Program_options 會回報所有的錯誤,但 hand-written code 可能會誤判。
    • Options 可以來自任何地方。當使用者可能對你的程式只能藉由 command line 輸入感到不滿,妳/你很快就會用到 config file 或是 environment variables 了。這些都可以幾乎無痛得透過 Boost.Program_options 來擴充。

    Tutorial

    在這個 section ,我們將從最簡單的地方開始,之後逐漸知道大部分 Boost.Program_options 的 usage scenarios 。這部份的文件都只有部份的、重要的程式碼,完整的範例可以在:

    BOOST_ROOT/libs/program_options/example

    資料夾找到。此外整個範例,都假設下面的 namesapce alias 是有效的!

    namespace po = boost::program_options

    Getting Started

    第一個也可能是最簡單的一個範例是:

    處理兩個 options

    下面是程式碼部份(完整程式可以在 example/first.cpp 找到)

    // Declare the supported options.
    po::options_description desc("Allowed options");
    desc.add_options()
        ("help", "produce help message")
        ("compression", po::value<int>(), "set compression level")
    ;

    一開始的地方宣告了一個 options_descriptions 來存放允許的 options。呼叫 options_description class 的 add_options method 會回傳一個提供了 operator() 操作的 proxy object 。呼叫 operator() 就是宣告了 options 。參數分別是:

    (option name, information about value, option description)

    這個範例裡頭,第一個 option 沒有 value,第二個 options 有一個型別是 int 的 value 。

    po::variables_map vm;
    po::store(po::parse_command_line(ac, av, desc), vm);
    po::notify(vm);    

    之後,宣告一個型別為 variables_map 的 object -- vm。variables_map 是用來存放任意型別的 value 。接著呼叫:

    1. store
    2. parse_command_line
    3. notify

    去讓 vm 獲得所有 command line 來的 value。

    if (vm.count("help")) {
        cout << desc << "\n";
        return 1;
    }
    
    if (vm.count("compression")) {
        cout << "Compression level was set to " 
     << vm["compression"].as<int>() << ".\n";
    } else {
        cout << "Compression level was not set.\n";
    }

    最後我們可以任意地使用 options 了,variable_map 提供了像是 std::map 的操作,除了取得 value 時,必須使用 as method外。(要是你不小心誤用了 as 中的型別參數,使得 as 參數想取出的型別和實際不合時,會出一個 exception)

    試著 compile 你的程式吧,下面是個範例:

    $bin/gcc/debug/first
    Compression level was not set.
    $bin/gcc/debug/first --help
    Allowed options:
      --help                 : produce help message
      --compression arg      : set compression level
    $bin/gcc/debug/first --compression 10
    Compression level was set to 10.

    Reference

    1. Boost 1.39.0 Doc: Chapter 13. Boost.Program_options

    See Also

    1. Boost.Program_options – Option Details and Multiple Sources

    MiniFilter InstanceSetupCallback is not called?

    一般來說,MiniFilter 的 InstanceSetupCallback 會在 filter manager 把 minifilter attache 到 volume 後呼叫。如果沒有的話,可以檢查一下 minifilter 的 INF 是否把 instance fla...