具有 __declspec(align('16')) 的形參將不被對齊
【IT168 文件】在CUDA程式設計中,結構的對齊(alignment)是非常重要的。在我寫的CUDA 結構對齊的C++模擬這篇文章中,我介紹瞭如何在C++裡模擬CUDA對結構的記憶體對齊機制,從而使得我們可以在C++裡構造結構,然後傳到CUDA裡進行處理。但是隨後我又遇到一個問題。比如,我們有這麼一個對齊了的結構:
template <unsigned int Len, typename ValueType>struct ALIGN16 Foo
{
ValueType x[Len];
...
};
當我們程式裡使用了std::vector< Foo<5, float> > v(1);,也就是構造一個記憶體自管理的陣列,並設定其初始大小為1。這段程式碼在g++裡能夠正常編譯,但當我們用msvc(我用的是vc80)來編譯的話,則會遇到報錯:
TDCALL /O2 /MD /I. /c test\base\test_carray.cpp /Fotest\base\test_carray.obj
test_carray.cpp
C:\Program Files\Microsoft Visual Studio 8\VC\INCLUDE\vector(694) : error C2719:
'_Val': formal parameter with __declspec(align('16')) won
iation 'std::vector<_Ty>' being compiled
with
[
_Ty=carray_type
]
scons: *** [test\base\test_carray.obj] Error 2
scons: building terminated because of errors.
我們搜尋STL vector的694行,發現有這樣的程式碼:
{ // determine new length, padding with _Val elements as neededif (size() < _Newsize)
_Insert_n(end(), _Newsize - size(), _Val);
elseif (_Newsize < size())
erase(begin() + _Newsize, end());
}
問題就出在其第二個引數上,我們可以看到,這是個傳值的引數,需要在函式呼叫的引數棧上建立一個臨時的引數,但當這個型別是被對齊了的話,msvc編譯器就會報錯。
那麼,怎麼解決呢?直接修改STL程式碼,顯然不是一個好方法。這時我想到了模板的特例化,可以用這種非侵入式的改變來解決這個問題,同時保證不修改STL程式碼。
首先,我們建立一個新的檔案,比如叫foo_vector.hpp,並大致寫成:
#ifndef FOO_VECTOR_HPP#define FOO_VECTOR_HPP
// only msvc need to be fix#ifdef _MSC_VER // 1400 for vc80
#include <vector>
#include "foo.hpp"
namespace std
{
// our specialization goes here...
}
#endif // _MSC_VER
#endif
這裡,我們首先判斷編譯器,如果是msvc,才進行我們的特例化,因為g++沒有這個問題(並且我發現,msvc使用的STL程式碼和g++使用的STL程式碼是不相同的)。這裡我沒有進行msvc版本的判斷,因為是demo嘛。
然後開啟msvc自帶的STL vector程式碼(在”Microsoft Visual Studio 9.0\VC\include\vector”),將整個vector類的定義拷貝過來,大致在434行到1292行之間。然後修改模板的頭,改為我們的特例化:
// TEMPLATE CLASS vectortemplate<unsigned int Len, typename ValueType,class _Ax>class vector < Foo<Len, ValueType>, _Ax >: public _Vector_val<Foo<Len, ValueType>, _Ax>
{ // varying size array of valuespublic:
typedef Foo<Len, ValueType> _Ty;
typedef vector<_Ty, _Ax> _Myt;
typedef _Vector_val<_Ty, _Ax> _Mybase;
.......
這樣,我們就完成了vector對我們Foo結構的特例化,接下來找到剛才產生問題的resize()方法,改為:
void resize(size_type _Newsize, const _Ty & _Val){ // determine new length, padding with _Val elements as neededif (size() < _Newsize)
_Insert_n(end(), _Newsize - size(), _Val);
elseif (_Newsize < size())
erase(begin() + _Newsize, end());
}
注意其中紅色部分,將傳值改為傳參,避免在引數棧上建立被對齊的結構的物件。然後,在我們使用std::vector< Foo<5, float> >之前包含我們的foo_vector.hpp標頭檔案,就可以正常使用了。