這兩天寫的代碼概率性的崩潰在 XMMatrixMultiply() 函數,XMMatrixMultiply() 本身是 inline 函數可以看到崩潰處的代碼: vX = _mm_mul_ps(vX,M2.r[0]); 經查,_mm_mul_ps 是 SSE2 指令要求記憶體地址 16 位元組對齊。猜想
這兩天寫的代碼概率性的崩潰在 XMMatrixMultiply() 函數,XMMatrixMultiply() 本身是 inline 函數可以看到崩潰處的代碼:
vX = _mm_mul_ps(vX,M2.r[0]);
經查,_mm_mul_ps 是 SSE2 指令要求記憶體地址 16 位元組對齊。猜想可能是位元組未對齊引發的問題,列印出矩陣參數的地址,果然,發生崩潰時都是未對齊到 16 位元組的情況。
XMMATRIX 聲明中指定了對齊到 16 位元組,如下:
__declspec(align(16)) struct XMMATRIX { ... };
但是顯然沒起到效果,繼續查發現是用到 XMMATRIX 變數被定義成了類成員變數,該類的實例是 new 出來的:
class MyClass { XMMATRIX matrix_; }; MyClass* ptr= new MyClass(); // 這裡應該有一個編譯警告
而 new 操作符只保證對齊到不大於 alignof(std::max_align_t) 也就是 8 位元組,導致成員變數 matrix_ 也只能對齊到 8 位元組。 解決方法先將成員變數 martix_ 賦值給一個局部變數,然後通過局部變數調用 XMMatrixMultiply() 函數。直接使用 new 會有一條對象可能無法對齊到 16 位元組的警告信息,但因為我是用 std::make_shared 直接生成智能指針,編譯器沒能給出這條警告——這也是個坑,最終導致了前面的崩潰問題。
註意 XMMatrixMultiply() 的聲明,第一個參數是值傳參,第二個是引用傳參,問題就處在引用傳參上:
1 typedef const XMMATRIX FXMMATRIX 2 typedef const XMMATRIX& CXMMATRIX; 3 4 inline XMMATRIX XM_CALLCONV XMMatrixMultiply (FXMMATRIX M1, CXMMATRIX M2); //註意第二個參數是引用傳參
MSDN 中對 XMMatrixMultiply 函數的聲明和實際代碼並不一致:
XMMATRIX XMMatrixMultiply( [in] XMMATRIX M1, [in] XMMATRIX M2 );
如果真的兩個參數都是值傳參也就沒有位元組對齊的問題了。
最終結論:XMMATRIX 結構體要求 16 位元組對齊,只能用作局部變數或全局變數——在靜態存儲區或棧上分配記憶體可以確保 align(16) 聲明有效,永遠不要用 new 分配堆記憶體,也不要用作類成員變數;如果需要在成員變數中保存矩陣可以用 DirectX::XMFLOAT4X4 ,使用時再轉換為 XMMATRIX 。
順便吐槽一下,MSDN 以前被稱為寫得最好的開發文檔是當之無愧的,現在只能呵呵了。最近兩年新增的文檔(比如 UWP 和 DX11 的文檔)明顯敷衍了事,函數說明經常一句話了事,看文檔和直接對著函數聲明猜用法都沒什麼區別了,這次居然還出現了函數聲明不一致的情況,這樣下去吃棗藥丸哪。