가상함수 호출시 this 포인터의 조절
[옛날 블로그 글입니다. 2007/11/29]
멤버함수 포인터를 조사하다가 생각치 못한 점을 발견했다.
가상함수를 호출할 때도 this 포인터를 적절히 조절해야 한다는 점이다.
그런데, MSVC는 도대체 어디서 this 포인터를 조절할까?
|
class Base1 { public: virtual void Func1(); int base1; };
class Base2 { public: virtual void Func2(); int base2; }
class Derived : public Base1, public Base2 { public: virtual void Func2() { ++ derived; } int derived; }
Derived d; Base2* pb2 = &d; pb2->Func2(); |
pb2와 &d 는 다른 값이니까, 마지막 줄에서 this 포인터를 전달할 때 pb2에서 8바이트를 빼서
넘겨줘야 한다고 생각했지만, 어디에도 그런 코드는 없었다.
조사해보니, 가상함수의 코드를 생성할 때 아예 this 포인터가 부모기준으로 넘겨오겠거니..
생각하고 어셈블리 코드를 만들어준다.
-
하지만 항상 그렇게 할 수 있는 건 아니다.
아래의 예라면 위의 수법이 통하지 않는다.
|
class Base1 { public: virtual void Func(); int base1; };
class Base2 { public: virtual void Func(); int base2; }
class Derived : public Base1, public Base2 { public: virtual void Func() { ++ derived; } int derived; }
Derived d; Base2* pb2 = &d; pb2->Func(); |
Derived::Func() 를 Base1의 this를 기준으로 어셈블해주면
Base2를 통해서 호출할 때는 필히 this 포인터의 조절이 필요하다.
MSVC는 이런 경우 this포인터값을 필요한 만큼 조절해서 Derived::Func()를 호출하는
코드 청크를 만들고, 이 청크의 주소를 Derived::Base2::'vftable'에 넣어둔다.
|
@ILT+2080(?Func@Derived@?9??main@@9@W7AEXXZ): |
청크 코드를 보면 this포인터에서 8을 빼고 실제 함수를 호출한다.
|
[thunk]:`main'::Derived::Func`adjustor{8}' (void): |
사원수 Quaternions
[옛날 블로그 글입니다. 2007/12/19]
출처 : Tricks of the 3D game programming gurus p.325
[사원수란?]
복소수(Complex Numbers)처럼 실수부와 허수부로 구성된 수.
허수부가 i, j, k 세개다.
q = q0 + q1*i + q2*j + q3*k
( q는 사원수 )
( i^2 = j^2 = k^2 = i*j*k = -1 )
( q0, q1, q2, q3 은 실수 Real Numbers )
이렇게도 표현
q = q0 + qv, where qv = q1*i + q2*j + q3*k
[사원수의 켤레(Conjugate)]
켤레수는 이차방정식의 두 근을 부르는 이름 인 것 같다.
무리수(Irrational Numbers) 부분이 +/- 로 부호만 다르게 된다.
복소좌표라면 허수 부분(Imaginary part)이 +/-로 부호만 다르게 된다.
다음은 사원수와 그의 켤레수.
q = q0 + q1*i + q2*j + q3*k = q0 + qv
q* = q0 - q1*i - q2*j - q3*k = q0 - qv
다음은 켤레수의 곱. 실수부만의 제곱이 된다.
q * q* = … 중간 생략 = q0^2 + q1^2 + q2^2 + q3^2
[놈(Norm)]
놈이란 벡터 공간 안의 모든 벡터에 양수의 길이나 크기를 부여하는 함수를 말한다.
2차원 유클리드 공간(Euclidean space) R^2에서의 유클리드 놈(Euclidean norm)이 좋은 예.
왜, 2차원 데카르트 좌표계(Cartesian coordinate system)에 벡터를 매핑 시키고 그 길이를
재는 방식 말이다.
위에 말을 정리하려고 열심히 웹서핑을 했지만, 위키피디아가 제일 나은 듯.
http://en.wikipedia.org/wiki/Norm_%28mathematics%29
사원수의 놈은 4차원 유클리드 공간 R^4에서의 벡터의 길이를 재는 것처럼 할 수 있다.
( 실수부, i, j, k 를 4차원의 축에 매핑시킨다고 생각하면 될 듯.. )
다음은 사원수와 놈.
q = q0 + q1*i + q2*j + q3*k = q0 + qv
|q| = sqrt(q0^2 + q1^2 + q2^2 + q3^2)
= sqrt( q * q* ) <- 이렇게 되는 이유는 위의 켤레수 참조
|q|^2 = (q0^2 + q1^2 + q2^2 + q3^2) = ( q * q* )
[켤레와 역원(Inverse)]
역원의 정의는 당근 이렇게. q-1 은 q 빼기 1 아님 -_-;;
q * q-1 = q-1 * q = 1
위 식의 양변에 켤레를 곱하면 재밌는 결과가 나온다.
q-1 * q = 1
=> ( q-1 * q ) * q* = 1 * q*
=> q-1 * ( q * q* ) = q*
=> q-1 * |q|^2 = q* <– 위에 놈(?) 참조
=> q-1 = q* / |q|^2
그래서 q 가 단위 사원수(unit quaternion)가 되면 |q|^2 = 1 이 되어서
아래와 같은 식이 나온다.
q-1 = q*, where |q|^2 = 1
[사원수를 사용한 회전]
사원수를 사용하는 것이 회전 행렬을 사용하는 것 보다 빠르고,
짐볼락(Gimbal lock)이 없는 것으로 유명.
그리고 회전시의 보간에도 유용하다는데, 아직 여기까지는 공부가 부족 -_-;;
벡터 v1을 기준으로 θ만큼 회전하는 사원수를 만들고 싶다면 이렇게.
q = cos(θ/ 2 ) + sin(θ/ 2 ) * v1
이렇게 준비한 사원수를 가지고 벡터 v2를 회전시키려면 이렇게.
우선은 벡터를 사원수 형식으로 바꿔야 하는데, 실수부는 0으로 두면 된다.
v2 = <x,y,z> 라면 vq = <0,x,y,z> 처럼 말이다.
왼손 좌표계라면
시계방향 회전은, vres = q * vq * q*
반시계방향은 , vres = q* * vq * q
오른손 좌표계라면 위의 식을 반대로 써주면 된다.
오일러(Euler) 회전각을 알고 있는 경우라면,
각 축에 대한 회전 사원수를 만들어서 곱해주면 된다.
qfinal = qx * qy * qz <- qx, qy, qz의 순서는 바뀌어도 무관
[DirectX 와 사원수]
뭐 다양한 사원수 지원이 있겠지만, 위에서 해본 것을 DX로 구현해본다면.
x, y, z가 각 축의 회전각(라디안)이라고 가정한다면
사원수는 이렇게 구할 수 있다.
var quat = Quaternion.RotationYawPitchRoll( y, x, z )
( VS 2008에서 Managed DirectX를 쓴 경우이다. )
바로 사원수를 행렬로 바꿀 수도 있다.
var mat = Matrix.RotationQuaternion( quat );
그런데 Matrix.RotationYawPitchRoll()을 써도 같은 행렬이 나오는 듯 -_-;;
레퍼런스와 rvalue
[옛날 블로그 글입니다. 2008/01/08]
[이것도 내가 쓴 기억이 없는데 -_-;;]
rvalue를 레퍼런스에 바인딩 시킨 경우, gcc는 에러를 내는데 VC++ 9.0은 그냥 잘된다.
신기해서 조사를 해보니, VC++이 하위호환성을 위해 비표준 문법을 허용하는 것.
[ const 레퍼런스는 rvalue에 바인딩 가능]
출처 : ISO IEC 14882:2003, 8.5.3절의 5번 항목.
| double& r = 2.0; // error
const double& r = 2.0; // ok |
[ 생성자 표현으로 만든 임시객체는 rvalue]
출처 : ISO IEC 14882:2003, 5.2.3절.
ISO IEC 14882:2003, 3.10절의 6번 항목.
| class A
{ public: A(int i ) { … }; … }; A& r = A(3); // error const A&r = A(3); // ok |
[ Visual C++와 표준]
컴파일러의 비표준사양을 정리해놓은 페이지.
http://msdn2.microsoft.com/en-us/library/x84h5b78.aspx
위 문서에는 나오지 않지만, 하위호환성을 위해
레퍼런스가 rvalue에 바인딩이 가능하게 구현되어 있다.
http://msdn2.microsoft.com/en-us/library/0eestyah.aspx
경고레벨을 4로 높이면, 비표준이라면서 경고가 뜬다.
회전과 좌표계
[옛날 블로그 글입니다. 2008/01/10]
[지금 보니 다 까먹었음. 역시 적어둬야 해..]
오른손, 왼손 좌표계에 따른 회전방향이 너무 헷갈려서 개념 정리를 위해 조사를 해봤다.
DirectX의 사원수의 곱셈 구현이 수학책과 다르다는 점을 알게 되었다.
( 사실, 이것 때문에 헷갈리기 시작해서 이틀간 계속 조사만 했다 -_-;; )
[벡터를 축으로 시계방향으로 회전한다는 것의 의미는?]
관찰자가 벡터를 어느 방향에서 바라보는지 전제가 있다면 헷갈릴 것이 없다.
전제가 없다면 관찰자가 벡터의 원점에서 벡터의 끝을 바라봤을 때를 전제로 한다고 생각할 수 있다.
물론 아닐 수도 있다. -_-;; ( 많은 문헌에서 독자적인 기준을 사용하는 것 같다. )
어쨌든 수학에서는 벡터를 축으로 양의 각도만큼 회전한다는 것의 의미는, 벡터의 원점에서 끝을 바라봤을 때 시계방향으로 회전하는 것을 뜻하는게 관행이라고 한다.
(http://web.archive.org/web/20041029003853/http:/www.j3d.org/matrix_faq/matrfaq_latest.html#Q27 참조 )
2차원 유클리드 공간을 생각해보면, 2차원 회전 행렬에 양의 각도를 넣어주면. 벡터는 반시계방향으로 회전하게 된다.
이걸 3차원 유클리드 공간으로 확대해보면 z축에 대한 회전이 되는데, 수학에서는 오른손 좌표계를 사용하므로, z축은 교과서를 뚫고 나와 학생 방향을 가리키게 된다. 즉, z축의 원점에서 z축의 + 방향으로 바라본다면 2차원 회전 행렬이 만들어낸 회전은 시계방향인 것이다.
[좌표계와 회전 방향]
동일한 회전이라도 왼손좌표계와 오른손좌표계에서 그 효과는 반대로 나타난다.
위에서 양의 각도의 회전은 축이 되는 벡터를 원점에서 바라보았을 때 시계방향이라고 했는데,
왼손좌표계라면 반시계방향이 된다.
벡터 v1 = (x1, y1, z1) 이 있다고 할 때 변환 행렬 M 이나 사원수 Q를 통해서 v2 = (x2, y2, z2)가 나왔다고 가정하자. 이때 절대적인 x1, y1, z1, x2, y2, z2의 값이 좌표계에 따라서 바뀌는 것은 아니다. 하지만 해당 좌표나 벡터를 화면에 표시할 때 3개의 축중에 하나가 뒤집어지게 되므로 반대로 회전하는 효과가 난다.
[DirectX와 사원수]
DirectX에서는 사원수의 곱셈을 수학책과 다른 순서로 계산한다.
예를 들어서 회전변환을 위한 사원수 q1, q2가 있다고 치자.
어떤 벡터를 q1으로 돌리고, 이어서 q2로 돌리는 사원수를 만들고 싶다면 수학에서는 아래와 같이 한다.
q3 = q2 * q1
실제 어느 벡터를 돌리고 싶다면 ( 물론 벡터를 사원수로 변환해서 쓴다고 가정 )
v2 = q3 * v1 * conjugate(q3)
혹은
v2 = (q2 * q1) * v1 * conjugate( q2 * q1 )
= q2 * q1 * v1 * conjugate( q1 ) * conjugate( q2 )
처럼 되는 식이다. 하지만 (Managed) DirectX로 동일한 사원수를 만들려면
D3DXQuaternionMultiply( q3, q1, q2 );
혹은
q3 = Quaternion.Muliply( q1, q2 );
혹은
q3 = q1 * q2;
처럼 해야 한다. 다시 말해 피연산자의 순서를 바꿔줘야 수학식과 동일해진다.
이는 행렬의 결합과 동일한 방식으로 사원수를 결합할 수 있게 하려고 MS 분들이 일부러 이렇게 만들었다고 한다. 행렬은 최종적으로 vector * Matrix1 * Matrix2 와 같은 식이므로 Matrix2의 변환이 나중에 수행된다. 하지만 사원수는 q1 * q2 * vector * conj(q2) * conj(q1) 과 같은 식이므로 q2의 변환이 먼저 수행된다. 다시말해 행렬에 친숙한 개발자들이 q1 * q2 라고 했을 때 q1의 변환이 먼저 수행되기를 예상할 것이라 생각하고 친절하게 순서를 바꿔줬다고 한다.
XNA에서는 이런 구현을 수학과 동일하게 동작하도록 수정하고 Concatenate() 메소드를 추가했다고 한다. ( 기존의 동작방식을 그리워하는 사람들을 위해서 ). DirectX와 Managed DirectX의 경우에는 사원수를 반대의 순서로 곱해야 수학책과 동일하게 동작한다.
경험에 의한 공부법
[옛날 블로그 글입니다. 2008/01/24]
[중간에 첨삭 들어갑니다.]
[8회]
공부는 은근한 긴장상태를 유발한다.
-> 공부하는 시간을 따로 정해서 그 때만 공부한다.
-> 아무리 재밌어도 틈날때마다 공부하기 시작하면 스트레스가 누적된다.
( 운동이 재밌어도 무리하면 안되는 것과 비슷.. )
[7회]
공부의 내용이 어렵고, 복잡해서 이해가 잘안되고, 진도가 잘 안나가는 경우
-> 남들도 그럴 것이라는 생각을 갖는다.
-> 자신 있는 분야인데도 그렇다면, 남들은 2배 더 어려울 것이라는 생각을 갖는다.
[6회]
공부할 것이 많고, A가 더 공부하고 싶은데 B를 해야하는 경우
-> B를 열심히 공부할 수록, A를 공부할 시간이 많이 생긴다.
-> 그러니 B를 열심히 하자.
[2010.03.29 B를 하는 수 밖에 없는지 잘 생각해본다.
적성과 맞지 않는 일을 하고 있는 것은 아닌지 고민해본다.]
[5회]
어느 것을 공부하는 게 나을지 망설여 질 때
-> 일단 마음 가는 것 먼저 공부하고, 다른 것도 다 공부한다.
[2010.03.29 공부의 목표가 서지 않았기 때문에 이런 망설임이 있는 것이다.
공부의 목표, 나아가서는 인생의 목표를 세운 후에 공부를 한다]
망설이는 시간, 확신이 없어서 공부를 해도 집중하지 못하는 비효율성이 더 시간 낭비가 되는 것 같다.
동기와 피드백이 적절하다면 공부는 재미있으니 시간은 아깝지 않다.
[4회]
공부가 진전이 없을 때, 공부를 해도해도 너무 어려울 때
-> 도무지 공부를 해도 모르겠을 때는 공부를 더 하면 된다.
뭐든 자꾸 하면 는다.
[3회]
공부에 있어서 중요한 것.
1. 동기 – 현실에서의 활용 목적.
2. 피드백 – 성장하고 있다는 느낌.
피드백은 우리가 모르는 사이에 찾아오는 경우가 많다.
성장 그래프는 계단형의 모습을 띄는 경우가 많다.
고로, 겉보기엔 매일이 똑같아도 사실 성장하고 있다는 점을 잊지 말자.
동기와 피드백이 적절하다면 공부는 재미있다.
[2010.3.29 Inner Game에서 꼽는 즐거움, 성장, 성과의 삼각형과 대충 매핑된다]
[2회]
공부에 있어서 딱 맞는 말.
1. 두마리 토끼를 잡으려다 둘 다 놓친다.
2. 갑자기 기억안난다 -_-;;
[1회]
경험에 의한 공부법.
1. Wikipedia에 가서 용어 및 기술의 개요를 파악한다.
2. 좋은 개론서를 여러 권 읽는다.
3. 좋은 심화서적을 여러 권 읽는다.
4. 많이 응용하고 써먹어 본다.
주의 사항.
* 좀 자신있어도 2번을 건너뛰면 학습효율이 매우 떨어진다.
* 3번을 건너뛰어도 써먹는데 문제가 없더라도, 3번은 꼭 필요하다.
-> 써먹는데 문제가 없다는 건 자기 생각일 뿐이다 -_-;;
개발이외의 분야에도 유효한 것 같다.