별빛의 낙하지 :: 별빛의 낙하지

[C++] 어셈으로 보는 함수 호출(1)

C++ 2012. 11. 6. 23:56 Posted by byulbit

어셈블러 입장에서 C++의 파라미터를 값으로 넘기는 함수(call by value), 레퍼런스로 넘기는 함수(call by reference), 포인터로 넘기는 함수(call by pointer) 의 구현체들은 어떠한 차이가 있을까?


void callByValue(int a )
{

}

void callByReference(int& a)
{

}

void callByPointer(int* a)
{

}

int main()
{
	int number = 1;
	int value = number;
	int* pointer = &value;
	int& reference = value;
	
	int depointer = *pointer;
	int dereference = value;

	callByPointer(pointer);
	callByValue(value);
	callByReference(reference);


}

함수의 구현체는 파라미터의 종류에 관계 없이 동일한 방식이었다.

void callByPointer(int* a)

{

01091060  push        ebp  

01091061  mov         ebp,esp  

01091063  sub         esp,0C0h  

01091069  push        ebx  

0109106A  push        esi  

0109106B  push        edi  

0109106C  lea         edi,[ebp-0C0h]  

01091072  mov         ecx,30h  

01091077  mov         eax,0CCCCCCCCh  

0109107C  rep stos    dword ptr es:[edi]  


}


함수 호출전 변수에 값을 대입하는 코드는 변수의 경우 dword ptr[변수이름]을 통해서 해당하는 변수의 값을 가져와서 mov(복사) 하는 코드임을 알 수 있으며, pointer와 reference의 경우 lea 명령(load effective address)을 이용한다.

Load Effective Address란 의미로 다음의 인자를 주소값으로 인식합니다.


int number = 1;

010910AE  mov         dword ptr [number],1          // 1을 number가 가리키는 곳에 대입한다.

int value = number;

010910B5  mov         eax,dword ptr [number]      // number가 가리키는 곳의 값을 eax에 대입한다.

010910B8  mov         dword ptr [value],eax          // eax 값을 value가 가리키는 곳에 대입한다.

int* pointer = &value;

010910BB  lea         eax,[value]                          // value 값을 eax에 대입한다.

010910BE  mov         dword ptr [pointer],eax         // pointer가 가리키는 곳에 eax를 대입한다.

int& reference = value;

010910C1  lea         eax,[value]                          // value 값을 eax에 대입한다.

010910C4  mov         dword ptr [reference],eax     // reference가 가리키는 곳에 eax를 대입한다.

int depointer = *pointer;

010910C7  mov         eax,dword ptr [pointer]  // pointer가 가리키는 값을 eax에 대입한다.

010910CA  mov         ecx,dword ptr [eax]  // eax가 가리키는 값을 ecx에 대입한다.

010910CC  mov         dword ptr [depointer],ecx  // ecx 값을 depointer가 가리키는 값에 대입한다.

int dereference = value;

010910CF  mov         eax,dword ptr [value]      // value가 가리키는 값을 eax에 대입한다.

010910D2  mov         dword ptr [dereference],eax    // eax 값을 dereference에 대입한다.



  지난 3년간 안드로이드와 관련된 부분들은 주로 앱을 만드는 것부터 시작해서 안드로이드 프레임웍까지 관심을 가져왔다.  이 책은 '안드로이드의 모든 것 분석과 포팅' 이후 버전으로 나온 책으로 서문에 따르면, 원래 이전 책에서 다루고 싶었지만, 시간 관계상 미뤄졌던게 드디어 책으로 나왔다고 한다.

  국내에서 안드로이드의 구조를 분석한 책은 몇권 되지 않아서, 개인적으로 다 보았었는데(저자의 이전 책 포함), 편집이나 흐름 자체가 이전 책과 비슷한 맥락이다.

  책은 전반적으로 NDK를 다루고 있고, 현재 나와 있는 NDK로 지원안되는 부분들에 대한 방법(PDK)도 제시하고 있다.  (어차피 NDK나 SDK나 PDK에 포함되는 관계이므로 라는 설명으로)

  책 전반적인 구성은 JNI와 같은 C/C++와 자바를 이어주는 가교부터 시작해서, 빌드하는 방법, NDK 관련해서는 주로 지원되는 라이브러리(OpenGL, Neon 등)와 관련된 예제에 대한 설명이 주를 이룬다.  Makefile 관련해서는 필요한 설명만 되어 있어서,  실습할 때 발생하는 예외적인 경우는 구글링으로 찾아봐야 될 것이다. 

  책을 읽으면서 좋았던 부분중에 하나는 저자가 이전에 실제 삽질한 결과나 고생했던 것에 대한 케이스에 대한 설명이 있다는 것이고(ex) 라이브러리를 불러 오는 과정에 있어서 순서가 꼬인 경우), 전반적으로 학습하기에 정리가 잘 되어 있다는 점이다.(처음 부터 예제를 설명하지 않는다. 필요한 기본 선수 지식에 대한 설명이 되어 있다.)

  책을 읽으면서 인상깊었던 부분은 예전에 과제를 진행하면서 불편하지만, 있는지 조차 몰라서 쓰지 못했던 것들이다.  나같은 경우 JNI쪽의 SWIG을 비롯해서 디버깅쪽에 addr2line, ndk-gdb 등이다.

  또한 중반 이후로 순수(?) NDK를 지나서 PDK로 넘어가게 되면 직접적으로 binder 의 개념자체가 필수불가결하게 되는데, 이러한 설명이 구조와 연관된 부분이라 이 책에서는 많이 다루지 않아서 이 부분은 이책만 봐서는 이해하기 힘들것이다.

  그리고 Appendix 에서 안드로이드와 관련해서 대략적인 설명을 하고 있는데, NDK와 관련해서 큰 그림을 먼저 알고 싶다면, 먼저 봐도 괜찮을 것 같다.

  아쉬운점으로 IT 전공서적의 특징상 책의 소스코드가 사실 그렇게 보기 좋게 나열된 것은 아니다.  소스코드가 끝난 다음에서야 설명이 등장하는 식인데,  주석처럼은 아니지만 중간중간에 설명을 인라인화 시키는 것이 가독성 측면에서 어떨까 생각한다.


<덧붙여>

혹시나 NDK를 모르는 사람들을 위해서 한가지 이야기를 하자면, 기존의 안드로이드 앱 개발방식이 SDK를 통한 자바 개발환경이었다면, NDK는 C/C++ 환경을 제공한다는 것이다. 여기서 NDK에 대해서 자바 코드가 전혀 없이 순수하게 C/C++로 작성하는 방법이라고 잘못 알고 있는 경우도 있는데 그렇지 않다.  java 코드에서 jni 인터페이스를 통해서 C/C++ 라이브러리를 호출하는 것이 NDK이다. 심지어 액티비티를 네이티브 수준으로 끌어 내리는 NativeActivity 예제만 봐도 java 코드에서 C/C++로 작성된 so library를 로드하여 콜백 하는 형태로 되어 있다.


[C++] 헤더는 선언이지 정의가 아니다.

C++ 2012. 10. 10. 04:02 Posted by byulbit

  헤더를 설계할 때는 정의(오직 한번만 사용할 수 있다.)와 선언(여러번 사용할 수 있다.)의 차이를 반드시 기억해야 한다.  다음 문장은 정의이므로 헤더에서는 사용하지 말아야 한다.


extern int ival = 10; // 초기값이 있으므로 정의이다.

double fica_rate; // extern이 없으므로 정의이다.


같은 프로그램에 속한 파일들 가운데 두 개 이상에서 이 정의를 포함하면 여러 개의 정의를 사용했다는 불평과 함께 링커가 오류를 일으킨다.

<예외>

 헤더에 정의를 포함하지 말아야 한다는 규칙에는 예외가 3가지가 있다. 클래스, 컴파일 시점에 값을 알 수 있는 const 객체, inline 함수가 그것인데 이들은 모두 헤더에 정의를 포함한다. 이들 요소는 각 파일별 정의가 똑같을 경우 여러 소스파일에 정의할 수 있다.

왜 그럴까... 컴파일러가 코드를 만들려면 이 요소의 (선언이 아닌) 정의를 알아야 하므로 헤더에 정의해둔다.  예를 들어 클래스 타입 객체를 정의하거나 사용하는 코드를 만들려면 컴파일러는 그 타입에 어떤 멤버가 있는지 알아야 한다.  또한 이 객체에 어떤 연산을 해야 하는지도 알아야 하는데, 이런 정보는 클래스 정의에 들어 있다. const 객체를 헤더에 정의하는 이유는 좀더 설명이 필요하다.