Si consideri il seguente codice (usa Eigen):Perché il codice generato da GCC legge la posta indesiderata dallo stack?
#include <Eigen/Dense>
#include <iostream>
template<int rows, int cols, int row, class R, class Rv, int N, class... Rs>
inline typename std::enable_if<sizeof...(Rs)==0>::type
setRow(Eigen::Matrix<R,rows,cols>&)
{}
template<int rows, int cols, int row, class R, class Rv, int N=0, class... Rs>
inline typename std::enable_if<sizeof...(Rs)==cols-N-1>::type
setRow(Eigen::Matrix<R,rows,cols>& m, Rv val, Rs...args)
{
m(row,N)=val;
setRow<rows,cols,row,R,Rv,N+1>(m,args...);
}
template<class T, int R, int C, int CUR_ROW>
class MatrixConstructor
{
Eigen::Matrix<T,R,C> m;
public:
MatrixConstructor(const Eigen::Matrix<T,R,C>& m)
: m(m)
{}
MatrixConstructor()
{}
template<class...Ts>
typename std::enable_if<sizeof...(Ts)==C && CUR_ROW<R-1,
MatrixConstructor<T,R,C,CUR_ROW+1>>::type operator()(Ts... vals)
{
setRow<R,C,CUR_ROW>(m,vals...);
return MatrixConstructor<T,R,C,CUR_ROW+1>(m);
}
template<class...Ts>
typename std::enable_if<sizeof...(Ts)==C && CUR_ROW==R-1,
Eigen::Matrix<T,R,C>>::type operator()(Ts... vals)
{
setRow<R,C,CUR_ROW>(m,vals...);
return m;
}
};
void test()
{
Eigen::Matrix<double,4,3> m=MatrixConstructor<double,4,3,0>()(1,2,3)
(4,5,6)
(7,8,9)
(5,4,3);
std::cout << m;
}
int main()
{ test(); }
compilo con gcc-4.8 con le ottimizzazioni pieno e opzione per generare elenco di assemblaggio. Ecco il comando che uso: (. Il mio CPU è Intel (R) Xeon (R) CPU E3-1226 v3 esegue un sistema Linux a 64 bit - si spera ora -march=native
ha un senso per i lettori)
g++ minitest.cpp -I/usr/include/eigen3 -std=c++0x -O3 -march=native -S -masm=intel
Ciò che mi fa meravigliare è che alcune istruzioni generate con questa linea di comando sembrano essere prive di senso e persino ridondanti. Vedi per es. come la funzione test()
inizia dopo la creazione dello stack (per il codice completo per entrambi test()
e main()
vedere here):
vmovsd xmm4, QWORD PTR .LC6[rip] # 1.0
lea rsi, [rsp+96]
vmovsd xmm5, QWORD PTR .LC7[rip] # 2.0
vmovsd QWORD PTR [rsp], xmm4
vmovapd xmm3, XMMWORD PTR [rsp+16] # What does it read?!
vmovapd xmm1, XMMWORD PTR [rsp] # And this!
vmovsd QWORD PTR [rsp+32], xmm5
vmovsd xmm0, QWORD PTR .LC8[rip] # 3.0
vmovapd XMMWORD PTR [rsp+304], xmm3 # And now even save this junk?!
vmovapd XMMWORD PTR [rsp+192], xmm1
vmovapd xmm3, XMMWORD PTR [rsp+48]
vmovapd xmm1, XMMWORD PTR [rsp+32]
vmovsd QWORD PTR [rsp+64], xmm0
vmovsd xmm7, QWORD PTR .LC12[rip] # 7.0
vmovapd XMMWORD PTR [rsp+336], xmm3
vmovapd XMMWORD PTR [rsp+224], xmm1
vmovapd xmm3, XMMWORD PTR [rsp+80]
vmovsd QWORD PTR [rsp+304], xmm7 # Even stranger — overwrites the junk
ho fatto un passo attraverso queste letture di spazzatura nel debugger e ha confermato che dopo di loro i xmm3
e xmm1
registri hanno davvero dei valori privi di senso Guardando questa lettura di valori indefiniti, sto iniziando a sospettare che il mio programma cerchi effettivamente di accedere ad una memoria che dovrebbe essere inaccessibile. Ma perché? Ho introdotto un UB da qualche parte?
Ho anche provato a eseguire il programma compilato con -fsanitize=address
, ma ha funzionato senza arresti anomali.
Non è solo questo l'assembly assembly Intel? Cioè, non 'vmovapd xmm3, XMMWORD PTR [rsp + 16]' sposta * in * 'xmm3'? – Barry
@Barry esattamente, e questo è _il mistero: legge junk a '[rsp + 16]' in 'xmm3'! Quindi salvalo in '[rsp + 304]', e poi sovrascrive questo valore salvato con '7.0' da' [rip + .LC12] '. – Ruslan