Reduce NRV to RV

或許不用到偏執的地步,人們就會某種程度上、或多或少地對於一個 return-by-value 的 function 感到憂心!

class MyClass {
    // ...
};

MyClass foo()
{
    // ...
}

int main()
{
    MyClass obj = foo();
}

憂慮其來有自:按照字面解讀,當 foo() 結束時,會將其結果放置於一個 temporary MyClass object 裡頭,並且這個 temporary object 會被作為 MyClass 的 copy constructor 的參數來初始化 obj 。若真如此,我們就不得不小心這類 return-by-value 的 function ,因為它引入了一個 temporary object 並且牽涉到 copy,temporary object 的建立與消滅可能對 performance 帶來極大的影響,舉例來說: MyClass 若是一個 1000x1000 的 array ,它的建立與消滅牽涉到大量的 heap allocation/deallocation ,遑論它還需要透過 copy constructor 把資料傳遞到另一個 object 上。

不過事情並非想像中的那麼嚴重,現在許多的 compilers 都已經能某種程度地對 return-by-value 做出最佳化,以抑制 temporary object 和 copy 的發生,C++ Standard 中稱呼為 copy elision ; 一般則叫做 return value optimization (RVO)。

Basic RVO

我們的目標是希望能夠抑制 copy 的發生,因此最好的情形就是:打從一開始,所作的運算就是在儲存結果的 destination object 上發生。要做到這點, compiler 大多使用一種簡單明瞭的方式:對 return-by-value 的 function prototype 動手腳,將它從

MyClass foo()
{
   // ...
}

轉換成:

void foo( MyClass& result )
{
    // ...
}

如此一來,便有機會在 return-by-value 的 function 中直接操作 destination object 。在某些團隊裡頭,為了避免 performance drop ,往往會制定這樣的 coding standard :

不要讓一個 function 回傳一個 object,若真要如此,改將 object 當作 argument 傳給 function 進行處理。

將 destination object 從 return value 轉移到 argument list 上,就像是手工式的最佳化。不過即使能直接操作 destination object ,要能做出正確的最佳化判斷仍然不是件簡單的事,參考一下這個 N1377 的例子:

A f( bool b )  
{  
    A a1, a2;  
    // ...  
    return b ? a1 : a2;  
} 

Destination object 的 value 可能來自不同的 control flows 、不同的 source objects,若是 compiler 無法有好的對策來在 destination object 上生成有效率的運算,便可能迫使 compiler 放棄 RVO 。為此, communities 和 compilers 往往又在討論 RVO 時,將它分為兩類:

  1. Named return value optimization (NRVO): Destination object 來自於一個 named (具名的)的變數,上述 N1377 的例子就屬於 NRVO  的範疇。
  2. Unnamed return value optimization:名稱上似乎沒有 URVO 這說法。 Destination object 來自於一個 unnamed (不具名的)的變數,通常就是那些直接存在於 return statement 上的 temporary objects。

一般而言, compilers 對於 URV 的最佳化支援會比 NRV 好,因為它們往往不具備有太複雜的 control flow ,通常程式碼結構上就像:

MyClass foo()
{
    return MyClass( "foo()" );
}

這般簡單,易於最佳化,因此即使是 g++ 不開啟最佳化或是 VC 2005 的 debug mode (預設是不啟用最佳化), URVO 都是唾手可得的。不過 RVO 到底如何施行、能帶來多少效益,還是得視 compiler 而定,最好的策略還是得多瞭解自己工作的 compiler ,寫些程式去測試測試。當我寫這篇文章的時候,我測試了一下這樣的的程式碼:

Config get( const std::string& input, const std::string& value )
{
    Config config;

    config.setName( input );
    config.setValue( value );
    config.setValue( 10 );

    return config;
}

即使這個 get() 只是:

Config get( const std::string& input, const std::string& value )
{
    return Config( input, value );
}
的惡搞版,會發現 NRV 很快地就讓 VC2005 debug mode 放棄 RVO ,但 g++4.4.1 -o0 還是能正常執行 RVO 。你可以參考這篇文章:Named Return Value Optimization in Visual C++ 2005,知道更多關於 VC 2005 NRVO 的細節。

Reduce NRV to RV

如前文所說,NRVO 屬於比較複雜的最佳化技術,並非每個 compiler 都提供。那麼在這種情況下,是不是代表著我們就只能妥協,將 return-by-value 變成 pass-by-reference 來手工模擬 compiler 的 transformation ,以減少 copy 的發生呢?不是的,有一個有趣的方式:將 NRV 轉換成 URV ,然後讓 compiler 去執行 URVO 。方法只要我們能提供特殊的 constructor ,並將運算放在裡頭。還記得我們一開始提到的精神:

最好的情形就是:打從一開始,所作的運算就是在儲存結果的 destination object 上發生。

Reduce NRV to RV 就像是這個精神的衍生, class 的設計者為 class 提供能夠直接在 class object 上運算的能力。實作上, class 的設計者會提供一個或一組 constructors 讓運算直接由 class object 完成。

舉例來說,一個 NRV function :

MyClass foo( int input, int filter )
{
    MyClass result;
    // Operations that involves result, intput, filter
    return result;
}

轉變為:

MyClass foo( int input, int filter )
{
    return MyClass( input, filter );
}

因為 MyClass class 的設計者將 foo() 該執行的運算搬到了 constructor 裡頭:

class MyClass {
public: 
    MyClass( int input, int filter )
    {
        // Initialize by input and filter
    }
};

那麼這麼做的缺點是什麼?嗯,一個不太自然的 constructor ,有點違反 data abstraction,而這個 MyClass 的可能充斥著各種具有這特殊功能的 constructors 。

A Mat8x8 Sample

如果想找個例子,不妨看看 Faster C++ Operations [1],它是一篇描述這種手工最佳化手法的文章,裡頭使用 Mat8x8 作為例子:

class Mat8x8 {
    double  M[ 8*8 ];
public:
    Mat8x8()                      { memset( M, 0, 64*sizeof(double) ); }
    Mat8x8( const Mat8x8& other ) { memcpy( M, other.M, 512 ); }

    double& operator() ( int row, int col ) { return M[ row*8 + col ]; }

    Mat8x8& operator = ( const Mat8x8& rhs );
    Mat8x8  operator + ( const Mat8x8& rhs );
    Mat8x8  operator - ( const Mat8x8& rhs );
    Mat8x8  operator * ( double rhs );
    Mat8x8  operator / ( double rhs );
};

Mat8x8 支援了許多 built-in operators 的能力,我們只取 + 來看:

Mat8x8::Mat8x8 operator + ( const Mat8x8& other ) {
    double Sum[64];
    for( int i=0; i<64; ++i )
        Sum[i] = M[i] + other.M[i];
    return Mat8x8( Sum );
    }

operator +() 是個典型的 NRV function ,在未能提供 NRVO 的 compiler 上,可能會產生對 performance 有所危害的程式,因此按照先前提到的 reduction ,我們需要為 Mat8x8 提供特殊的 constructor ,依照需求,這個 constructor 會是個接受兩個 arguments 的 constructors ,不過 Mat8x8 提供了不只是提供了 + 的運算,所以最好能夠連 –、*、/ 也一併處理,因此 Faster C++ Operations [1] 提供的方法是利用具有古典優雅的氣息的 function pointer ,改寫之後變成:

class Mat8x8 {
    typedef void (*PFnInitMat)( Mat8x8& Mat, void* pLHS, void* pRHS );

    // The special constructor help us to reduce NRV to RV
    Mat8x8( PFnInitMat Init, void* pLHS, void* pRHS )
    {
        Init( *this, pLHS, pRHS );
    }

public:
    union {
        double  M[8][8];
        double  A[ 64 ];
    };
    Mat8x8()                      { memset( A, 0, 64*sizeof(double) ); }
    Mat8x8( const Mat8x8& other ) { memcpy( A, other.A, 512 ); }

    Mat8x8& operator = ( const Mat8x8& rhs );
    Mat8x8  operator + ( const Mat8x8& rhs );
    Mat8x8  operator - ( const Mat8x8& rhs );
    Mat8x8  operator * ( double rhs );
    Mat8x8  operator / ( double rhs );
};

void AddMat88( Mat8x8& Mat, void* pLHS, void* pRHS )
{
    // ...
}

void SubMat88( Mat8x8& Mat, void* pLHS, void* pRHS )
{
    // ...
}

void DivMat88scalar( Mat8x8& Mat, void* pLHS, void* pRHS )
{
    // ...
}

void MulMat88scalar( Mat8x8& Mat, void* pLHS, void* pRHS )
{
    // ...
}

PFnInitMat 是個 function pointer type ,它代表了一族:可以接受兩個 Mat8x8 objects 並將結果寫於特定 object 的能力,家族成員包括: AddMat88()、SubMat88()、DivMat88()、MulMat88()。Mat8x8 class 現在有了一個特殊的 constructor ,它除了接受兩個 Mat8x8 的 arguments 外,還會接受一個 function pointer 來實際執行不同的運算。有了這樣的 constructor 幫忙,現在 operators 就會變成一個個簡單的 forward functions:

Mat8x8 Mat8x8::operator + ( const Mat8x8& rhs ) {
    return Mat8x8( AddMat88, this, (void*)&rhs );
    }

Mat8x8 Mat8x8::operator - ( const Mat8x8& rhs ) {
    return Mat8x8( SubMat88, this, (void*)&rhs );
    }

Mat8x8 Mat8x8::operator * ( double rhs ) {
    return Mat8x8( MulMat88scalar, this, (pdouble)&rhs );
    }

Mat8x8 Mat8x8::operator / ( double rhs ) {
    return Mat8x8( DivMat88scalar, this, (pdouble)&rhs );
    }

Generic Revision

看過了 Faster C++ Operations [1] 的方式,不知道您有沒有什麼好奇的地方呢? COdE fr3@K 跟我覺得有些地方可以來作些實驗:

  • Type safety:Faster C++ Operations [1] 使用 void* 作為那些實際運算的 functions 的參數。因此在 AddMat88() 這類實際運算的 functions 裡頭,必須將 void* cast 成適當的 type ,少了那麼一點 type safety 的味道。或許有點吹毛求疵,不過引用作者的話:
    I just used void* for this example - they make programming flexible (under great responsibility).
  • Generic and parameterization operation :實際運算的 functions 是透過 function pointers 傳遞 constructor 去執行任務,如果能改用 C++ 的一些泛型機制呢?

Generic and Parameterization Operations

class Mat8x8 {
    struct plus_tag {
    };

    struct minus_tag {
    };

    struct division_tag {
    };

    struct multiplication_tag {
    };

    Mat8x8( const Mat8x8& lhs, const Mat8x8& rhs, plus_tag )
    {
        for ( size_t i = 0; i < 64; ++i ) {
            A[ i ] = lhs.A[ i ] + rhs.A[ i ];
        }
    }

    Mat8x8( const Mat8x8& lhs, const Mat8x8& rhs, minus_tag )
    {
        for ( size_t i = 0; i < 64; ++i ) {
            A[ i ] = lhs.A[ i ] - rhs.A[ i ];
        }
    }

    Mat8x8( const Mat8x8& lhs, double rhs, multiplication_tag )
    {
        for ( size_t i = 0; i < 64; ++i ) {
            A[ i ] = lhs.A[ i ] * rhs;
        }
    }

    Mat8x8( const Mat8x8& lhs, double rhs, division_tag )
    {
        for ( size_t i = 0; i < 64; ++i ) {
            A[ i ] = lhs.A[ i ] / rhs;
        }
    }

public:
    union {
        double  M[8][8];
        double  A[ 64 ];
    };

    Mat8x8()
    {
        memset( A, 0, 64*sizeof(double) );
    }

    Mat8x8( const Mat8x8& other )
    {
        cout << "Copy ctor\n";
        memcpy( A, other.A, 512 );
    }

    Mat8x8 operator + ( const Mat8x8& rhs )
    {
        return Mat8x8( *this, rhs, plus_tag() );
    }
    Mat8x8 operator - ( const Mat8x8& rhs )
    {
        return Mat8x8( *this, rhs, minus_tag() );
    }
    Mat8x8 operator * ( double rhs )
    {
        return Mat8x8( *this, rhs, multiplication_tag() );
    }
    Mat8x8 operator / ( double rhs )
    {
        return Mat8x8( *this, rhs, division_tag() );
    }
};

使用了幾個 tag classes 來幫助我們標示實際要求 constructor 執行的運算。不過這只是第一步,目前的實作還無法支援使用 Mat8x8 時,可以客製化運算,我們還需要做出這樣的修改:

template <typename OperationT>
class Mat8x8 : public OperationT {
    struct plus_tag {
    };

    struct minus_tag {
    };

    struct division_tag {
    };

    struct multiplication_tag {
    };

    Mat8x8( const Mat8x8& lhs, const Mat8x8& rhs, plus_tag )
    {
        add( A, lhs.A, rhs.A );
    }

    Mat8x8( const Mat8x8& lhs, const Mat8x8& rhs, minus_tag )
    {
        minus( A, lhs.A, rhs.A );
    }

    Mat8x8( const Mat8x8& lhs, double rhs, multiplication_tag )
    {
        multiply( A, lhs.A, rhs );
    }

    Mat8x8( const Mat8x8& lhs, double rhs, division_tag )
    {
        divide( A, lhs.A, rhs );
    }

public:
    union {
        double  M[8][8];
        double  A[ 64 ];
    };

    Mat8x8()
    {
        memset( A, 0, 64*sizeof(double) );
    }

    Mat8x8( const Mat8x8& other )
    {
        cout << "Copy ctor\n";
        memcpy( A, other.A, 512 );
    }

    Mat8x8 operator + ( const Mat8x8& rhs )
    {
        return Mat8x8( *this, rhs, plus_tag() );
    }
    Mat8x8 operator - ( const Mat8x8& rhs )
    {
        return Mat8x8( *this, rhs, minus_tag() );
    }
    Mat8x8 operator * ( double rhs )
    {
        return Mat8x8( *this, rhs, multiplication_tag() );
    }
    Mat8x8 operator / ( double rhs )
    {
        return Mat8x8( *this, rhs, division_tag() );
    }
};

一個 policy-based class 來提供運算,並讓使用 Mat8x8 的人可以選擇要使用的矩陣計算方式;例如說最基本的運算可透過 MatOp class 來提供:

struct MatOp {
    template <typename ValueT,
              size_t size>
    static void add( ValueT (&result)[ size ], const ValueT (&lhs)[ size ], const ValueT (&rhs)[ size ] )
    {
        for ( size_t i = 0; i < size; ++i ) {
            result[ i ] = lhs[ i ] + rhs[ i ];
        }
    }

    template <typename ValueT,
              size_t size>
    static void minus( ValueT (&result)[ size ], const ValueT (&lhs)[ size ], const ValueT (&rhs)[ size ] )
    {
        for ( size_t i = 0; i < size; ++i ) {
            result[ i ] = lhs[ i ] - rhs[ i ];
        }
    }

    template <typename ValueT,
              size_t size>
    static void multiply( ValueT (&result)[ size ], const ValueT (&lhs)[ size ], ValueT rhs )
    {
        for ( size_t i = 0; i < size; ++i ) {
            result[ i ] = lhs[ i ] * rhs;
        }
    }

    template <typename ValueT,
              size_t size>
    static void divide( ValueT (&result)[ size ], const ValueT (&lhs)[ size ], ValueT rhs )
    {
        for ( size_t i = 0; i < size; ++i ) {
            result[ i ] = lhs[ i ] / rhs;
        }
    }
};

2010/05/03 Update

今天巧遇 COdE fr3@K ,他給了我幾個建議:

  1. 少用繼承這種高耦合性的機制,尤其當你想描述的關係並非 is-a 而是 is-implemented-in-terms-of 。
  2. 給 class template 一個名稱,並用 typedef 宣告另一個使用時的名稱。

所以今天的作業 XD

template <typename OperationT>
class Mat8x8Base {
    struct plus_tag {
    };

    struct minus_tag {
    };

    struct division_tag {
    };

    struct multiplication_tag {
    };

    Mat8x8Base( const Mat8x8Base& lhs, const Mat8x8Base& rhs, plus_tag )
    {
        OperationT::add( A, lhs.A, rhs.A );
    }

    Mat8x8Base( const Mat8x8Base& lhs, const Mat8x8Base& rhs, minus_tag )
    {
        OperationT::minus( A, lhs.A, rhs.A );
    }

    Mat8x8Base( const Mat8x8Base& lhs, double rhs, multiplication_tag )
    {
        OperationT::multiply( A, lhs.A, rhs );
    }

    Mat8x8Base( const Mat8x8Base& lhs, double rhs, division_tag )
    {
        OperationT::divide( A, lhs.A, rhs );
    }

public:
    union {
        double  M[8][8];
        double  A[ 64 ];
    };

    Mat8x8Base()
    {
        memset( A, 0, 64*sizeof(double) );
    }

    Mat8x8Base( const Mat8x8Base& other )
    {
        cout << "Copy ctor\n";
        memcpy( A, other.A, 512 );
    }

    Mat8x8Base operator + ( const Mat8x8Base& rhs )
    {
        return Mat8x8( *this, rhs, plus_tag() );
    }
    Mat8x8Base operator - ( const Mat8x8Base& rhs )
    {
        return Mat8x8( *this, rhs, minus_tag() );
    }
    Mat8x8Base operator * ( double rhs )
    {
        return Mat8x8( *this, rhs, multiplication_tag() );
    }
    Mat8x8Base operator / ( double rhs )
    {
        return Mat8x8( *this, rhs, division_tag() );
    }
};

typedef Mat8x8Base<MatOp> Mat8x8;

以 Mat8x8Base 取代原本的 class template 名稱 Mat8x8 ,使用 typedef 宣告常用的名字 – Mat8x8 ;取消繼承,改用 qualified names 去呼叫 add(), minus() 之類的 functions。

C++0x 的救贖

真是的,說得口沫橫飛!其實重點在 C++0x 提供了 move semantics 幫助我們從 copy 的地獄中離開,N1377 :

Move semantics is mostly about performance optimization: the ability to move an expensive object from one address in memory to another, while pilfering resources of the source in order to construct the target with minimum expense.

有興趣的,可以參考一下 COdE fr3@K 寫的一連串關於 move、r-value reference 的文章:

  1. C++0x: Rvalue References:http://fsfoundry.org/codefreak/2008/11/16/cpp0x-rvalue-references/
  2. C++0x: More on Rvalue References:http://fsfoundry.org/codefreak/2008/11/16/cpp0x-more-on-rvalue-references/
  3. I Like to Move It:http://fsfoundry.org/codefreak/2009/05/19/i-like-to-move-it/

Furthermore

  1. RVO vs. NRVO vs. URVO?
    前面提過,比較少有 URVO 這樣的稱呼,這篇文章會做這樣的區別,只是單純覺得 RVO 應該是包涵性較 NRVO 和 URVO 廣泛的最佳化,URVO 只能算是 RVO 裡頭較簡單的一種形式,不過許多文章談及 RVO 可能只牽涉到 URVO ,或者它們說是 apply RVO to unnamed temporaries [6]。
  2. 為什麼 C++ Standard 會特別的定義 copy elision 呢?
    無論是 C++03 或是 C++0x 都會特別提及 copy elision :

    When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects. … . This elision of copy/move operations,  called copy elision,  is permitted in the following circumstances

    因為一旦 copy elision 施行,原本發生在 return value 上的 side effect 也就不可得,雖然少見,但或許真有程式碼倚賴這樣的 side effect ,object counting 可能算是一類?!但為了效率的追求, RVO 是必要的,弭平爭議的方法就是在 standard 中好好說明。

Reference

  1. Faster C++ Operators: http://www.codeproject.com/KB/recipes/FasterCPPOperators.aspx
  2. Named Return Value Optimization in Visual C++ 2005: http://msdn.microsoft.com/en-us/library/ms364057%28VS.80%29.aspx
  3. N1377 A Proposal to Add Move Semantics Support to the C++ Language: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm
  4. The Name Return Value Optimization: http://blogs.msdn.com/slippman/archive/2004/02/03/66739.aspx
  5. C++ FAQ – 10. Constructors: http://www.parashift.com/c++-faq-lite/ctors.html 
  6. Move Constructors: http://www.drdobbs.com/cpp/184403855#3

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!

    心得

    該看書了 >__<

  • MiniFilter InstanceSetupCallback is not called?

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