프로그래밍 일반 2014. 7. 22. 16:23

Visual studio 에서 find가 되지 않을 때..

Ctrl + ScroolLock 을 정상적으로 검색이 된다.

프로그래밍 일반 2014. 6. 24. 17:11

디스어셈블리

컴파일러 최적화 기술은 나날이 발전하고 있고 예전에 공부했던 최적화 내용은 지금과 다를 수도 있다.


최근 서버 코드를 리팩토링하면서 당연히 문제가 있을거라 생각했던 코드들[각주:1]도 아주 훌륭하게 컴파일러는 처리를 해주고 있었다.


아... 내가 알고 있는 지식이 지금도 유효한가 묘한 경험.. 뭐 어셈을 알면 코드를 검증하기도 쉽고 디버깅에도 도움이되고 무엇보다 


프로그램을 이해하는데 도움이 되니 기회가 될때마다 학습을해두는게 좋다.


VS2008 에서 Disassembly 단축키 Alt + 8


어셈코드를 읽기위해 숙지하고 있어야할 정보들..



- 범용 레지스터 (32비트)

EAX : 확장 누산기, 대부분 입출력과 산술, 논리 연산 수행


EAX 32비트, AX 16비트, AH 상위 영역(SUB 16bit), AL 하위 영역  -> 범용 레지스터 공통적으로 적용가능


EBX : 주소 지정을 확장하기 위해 인덱스로 사용, 특정 주소 지정을 위한 베이스 레지스터로서 DI, SI와 결합될 수 있다.

         산술 연산에도 사용한다.

ECX : 반복 수행할 때 횟수를 지정하는데 사용, 루프나 시프트 등에서 사용

EDX : 64비트 값들의 상위 32비트를 담는 용도로 사용

ESP : 스택포인터, 스택의 꼭대기를 가리킨다(offset, x86은 스택이 아래로 자라므로 실제로는 바닥이다).

   스택에 데이터가 push 되거나 pop 될 때마다 증감된다.

EBP : 스택프레임, 프로시저나 함수에 대한 스택프레임을 담는 용도로 사용된다. 스택의 데이터에 접근하기 위해 사용

ESI : 메모리 이동이나 비교시에 원본주소를 담는 용도로 사용

EDI : 메모리 이동이나 비교시에 대상의 주소를 담는 용도로 사용




- EIP 레지스터

EIP : 인스트럭션[각주:2] 포인트, 다음에 실행할 코드의 주소를 가지고 있다.



- 세그먼트 레지스터

cs : 코드세그먼트, 코드영역의 세그먼트를 지닌다. 32비트 어드레싱에서는 별의미가 없다.

ds : 데이터세그먼트, 데이터영역의 세그먼트를 지닌다. 역시 32비트 어드레싱에서는 별의미가 없다.

ss : 스택 세그먼트, 

es : extra segment, 데이터 전송을 받은 수신측에서의 시작 부분을 가리키고 있다.

스트링 명령에 보조적으로 사용되기도 한다.

fs, gs - es 처럼 데이터를 가리키는 역활을 하지만 보조적으로 쓰이는 역활



- 명령어

LEA - 메모리 주소값 (세그먼트 + 오프셋) 을 추출

CMP - 두 값을 비교 (깊은 의미 - 두 값의 차이 계산, 둘다 같은 값이면 차가 0 이므로 ZF가 1로 셋팅)

JMP : 비교 점프

above 크다, below 작다, greater (부호 포함 크다), less 부호 포함 작다

JNE - Jump not equal

JE - (Jump if equal) : ZF = 1

JA - Jump if (unsigned) above) : CF = 0 and ZF = 0

      앞에서 수행한 비교문 (cmp) 에서 앞에 것이 클때만 점프

JB - (Jump if below) : CF = 1 앞에서 수행한 비교문에서 앞에 것이 작을 때만 점프

JG - (Jump if greater) : ZF = 0 and SF = 0 , 앞의 것이 크거나 같다면 점프한다.

JL - (Jump if less)

LOOP - 반복

LOOPE - 같다면 반복

LOOPNE - 같지 않다면 반복

CALL - 서브루틴으로 분기

RET - 원래의 루틴으로 복귀

IRET - 인터럽트 복귀

JE/JZ - 결과가 0이면 분기

JNE/JNZ - 결과가 0이 아니면 분기

JL/JNGE  - 결과가 작으면 분기

JO - 오버플로우가 발생하면 분기

JNLE/JG  - 결과가 크면 분기


movzx - (move zero extend : 무브 제로 확장) 

movsx - (Move sign extend : 무브 부호 확장)


mov -  


int iValue = 10;

00F724FE  mov         dword ptr [iValue],0Ah    // 의미 iValue 라는 dword형 변수에 16진수 0A (10진수 10) 를 넣는다. 



- 스트링 명령어

LODS - 메모리로부터 레지스터에 데이터를 로드, ESI(포인터)의 내용을 EAX로 로드

STOP - 메모리에 데이터를 저장, EAX의 값을 EDI가 가리키는 곳에 저장

MOVS - ESI가 가리키는 곳의 값을 EDI가 가리키는 곳에 복사

CMPS - 메모리와 메모리를 비교, ESI와 EDI의 내용을 비교한 결과에 따라 플래그 설정

SCAS - 레지스터와 메모리의 내용을 비교, EDI

REP - REP 뒤에 오는 스트링 명령을 ECX가 0이 될 때까지 반복

   REP 명령과 조합하여 사용하면 ECX 레지스터가 지정하는 횟수만큼 반복하여 데이터를 전송한다.

   이때 주소는 자동으로 갱신되지 때문에 한 명령으로 연속된 여러 데이터를 전송할 수 있다.

REPE, PEPNE - 일치하는 데이터가 얻어질 때까지, 일치하지 않는 데이터가 얻어질 때까지 메모리상의 데이터를 탐색할 수 있다


우선 이정도만 알아도 왠만한 코드는 볼 수 있다..


계속 업데이트 예정..

  1. switch case 문이 점프테이블을 만드는 룰 위반 [본문으로]
  2. CPU를 동작 시키는 명령어. 인스트럭션 집한은 인스트럭션 셋이라한다. [본문으로]
프로그래밍 일반 2014. 4. 14. 16:39

C++ 11 컴파일 타임 assertion ( static_assert )

웹 컴파일러 링크 : http://melpon.org/wandbox/


컴파일 타임 assertion 은 상수식과 문자열 리터럴로 구성되어 있다.


static_assert(constant-expression, string literal);


컴파일러는 식을 계산한 다음 expression 이 거짓이라면 오류 메시지에 string 을 적는다.


static_assert 는 컴파일시에 처리되기 때문에 런타임에 의존하는 값들을 확인할 때는 사용할 수 없다.


int Func(int* p, int n) 

{

static_assert( NULL != p, "p is not null");  // 오류

}


프로그래밍 일반 2014. 4. 10. 17:26

C++ 11 생성자 상속 ( Inheriting Constructors )

웹 컴파일러 링크 : http://melpon.org/wandbox/


class CBase

{

public :    

    void Func(float f) {}  

};



class CDerived : public CBase

{

public :    

    void Func(int i) {}

};    


CBase bObj;

bObj.Func(3.1f); // Func(float f)

    

CDerived dObj;

dObj.Func(3.1f); // Func(int i) 호출 narrowing 발생



C++ 98 에서 아래 코드는 베이스 클래스로 부터 파생 클래스로 오버로딩 된 함수들을 옮길 수 있다.



class CBase

{

public :    

    void Func(float f) {}  

};



class CDerived : public CBase

{

public :

    using CBase::Func;

    void Func(int i) {}

};


CBase bObj;

bObj.Func(3.1f); // Func(float f)

    

CDerived dObj;

dObj.Func(3.1f); // CBase::Func(float f) 호출


C++ 11 에서는 멤버 함수 뿐만 아니라 생성자들에도 이러한 기능을 사용할 수 있다.


class CDerived : public CBase

{

public :

    using CBase::CBase; // 베이스의 생성자를 파생 클래스 범위로 가져온다. C++ 11 에서만 가능

    CDerived(int i) {} // 이 생성자를 CBase::CBase(int i) 보다 선호한다.

    

    using CBase::Func; // 베이스의 Func를 파생 클래스의 범위로 가져온다. C++ 98 작동

    void Func(int i) {}

    void Func(char c) {}

    void Func(float f) {} // 이 Func 를 CBase::Func(float) 보다 선호한다.

    

private :

    int m_iValue;

};      





class CBase

{

public :

    CBase(int iValue = 0) {}

    

    void Func(float f) {}  

};



class CDerived : public CBase

{

public :

    using CBase::CBase; // 암시적으로 CDerived(int = 0) 선언

    

    using CBase::Func;

    void Func(int i) {}

    void Func(char c) {}

    

private :

    int m_iValue;

};        




int main()

{

    CBase bObj(10);

    bObj.Func(3.1f); // Func(float f)

    

    CDerived dObj(10); // 생성된다.

// dObj.m_iValue 가 초기화되지 않는다.

// 파생 클래스에서 베이스 클래스의 생성자를 상속할 때, 파생 클래스에서 새로운 멤버 변수를 

// 초기화해야 한다면 이런 실수가 가능하다.

    

    return 1;

}


위와 같은 문제는 멤버 초기화자 ( member initializer ) 를 이용하여 해결한다.


class CDerived : public CBase

{

public :

    using CBase::CBase;

    

    // ....

    

private :

    int m_iValue{0};

};



 CDerived dObj(10); // 생성되고 dObj.m_iValue 는 0    


프로그래밍 일반 2014. 4. 10. 15:09

C++ 11 Non-static data member initializers

웹 컴파일러 링크 : http://melpon.org/wandbox/


C++ 98 에서는 오직 정수 타입의 정적 상수 멤버들 만이 클래스 내부에서 초기화 될 수 있었고 초기화 값은 반드시 상수 식이어야했다.

이러한 제약 조건은 컴파일 타임에 초기화가 될 수 있게 보장을 했다. 예를 들어


int var = 7;


class CTest {

    static const int m1 = 7; // 가능

    const int m2 = 7; // 오류 : static 아님

    static int m3 = 7; // 오류 : const 아님

    static const int m4 = var; // 오류 : 초기화값이 상수 식이 아니다

// const int var = 7 는 가능

    static const string m5 = "odd"; // 오류 : 정수 타입이 아닙니다

// ...

};


C++ 11 에서는 기본적으로 비 정적 데이터 멤버들도 클래스 안에 선언된 부분에서 바로 초기화가 가능하다. 


class CTest

{

private :

    int     m_iMember = 0;

};


는 아래 코드와 동일하다.


class CTest

{

public :

    CTest() : m_iMember(0) {}

    

private :

    int     m_iMember = 0;

};



이는 타이핑을 줄여줄뿐만 아니라 여러개의 생성자를 가지고 있는 클래스들을 사용할 때 도움이 된다.

많은 경우 모든 생성자가 특정 멤버에 대해 공통적인 초기화 값을 가지는 경우가 있다.


class C {

public:

    A(): a(7), b(5), hash_algorithm("MD5"), s("Constructor run") {}

    A(int a val) : a(a val), b(5), hash_algorithm("MD5"), s("Constructor run") {}

    A(D d) : a(7), b(Func(d)), hash_algorithm("MD5"), s("Constructor run") {}

  

private:

    HashingFunction hash_algorithm; // 모든 A 인스턴스들에 대해 암호화 된 해시가 적용된다

    std::string s;                  // 객체 생존사이클에 대한 상태를 나타내기 위한 문자열

    

    int a, b;

};


hash_algorithm 과 s 모두 디폴드 값이 있다는 사실을 위의 혼잡한 코드에서 쉽게 눈치채기는 어렵고 유지 보수시에 문제가 될 가능성이 있다.

대신 C++ 11 에서는 데이터 멤버들의 초기화를 다음과 같이 수행할 수 있다.


class A {

public:

    A(): a(7), b(5) {}

    A(int a val) : a(a val), b(5) {}

    A(D d) : a(7), b(Func(d)) {}


private:

    HashingFunction hash_algorithm{"MD5"};  // 모든 A 인스턴스들에 대해 암호화 된 해시가 적용된다

    std::string s{"Constructor run"};       // 객체 생존사이클에 대한 상태를 나타내기 위한 문자열

    

    int a, b;

};


만약 멤버가 클래스 내부와 생성자 양쪽에서 초기화 된다면 생성자에 의한 초기화만 수행된다. (즉, 디폴트를 오버라이드한다.)

따라서 아래와 같이 더 간단한 표현이 가능하다.

class A {

public :

    A() {}

    A(int a_val) : a(a_val) {}

    A(D d) : b(Func(d)) {}

private:

    HashingFunction hash_algorithm{"MD5"};  // 모든 A 인스턴스들에 대해 암호화 된 해시가 적용된다

    std::string s{"Constructor run"};       // 객체 생존사이클에 대한 상태를 나타내기 위한 문자열

    

    int a = 7;

    int b = 5;

};


참고 :

위의 링크를 타고가면 생각할 거리들이 많이 있다.


프로그래밍 일반 2014. 4. 10. 12:31

C++ 11 테스트용 웹 컴파일러


C++ 11 을 접하고 새로운 기능들을 테스트해보 싶을 때 새로운 기능을 지원하는 컴파일러가 없다면


간단히 웹 컴파일러를 이용하여 공부해보자.


웹 컴파일러 링크 : http://melpon.org/wandbox/

프로그래밍 일반 2014. 4. 10. 11:52

C++ 11 생성자 위임 Delegating constructors

웹 컴파일러 링크 : http://melpon.org/wandbox/


만일 여러 생성자가 같은 일을 하기 원한다면 초기화 함수를 만들어 생성자에서 호출하도록 할 것이다.


class CTest

{

public :

CTest()

{

Init(0);

}

CTest(int iValue)

{

Init(iValue);

}

CTest(std::string szString)

{

int iCastValue = boost::lexical_cast<int>(szString);


Init(iCastValue);

}


void Init(int iValue) {


if ( 0 < iValue && iValue < MAX ) {

m_iMember = iValue;

}

else throw error_value(iValue);

// ....

}


private :

int m_iMember;

};


하지만 위의 코드는 유지 보수가 불편하고 오류가 발생할 확률도 높을 뿐더러 작성해야하는 코드도 길다.

이런 불편을 해결하기 위해서 C++ 11 에서는 한 개의 생성자를 정의하여 다른 생성자에서 사용할 수 있게 하였다.



class CTest

{

public:

CTest(int iValue)

{

if ( 0 < iValue && iValue < MAX ) {

m_iMember = iValue;

}

else throw error_value(iValue);

// ....

}


CTest() : CTest{0} {}

CTest(std::string szString) : CTest{ boost::lexical_cast<int>(szString) } {}


private:

int m_iMember;

};


참고 :


프로그래밍 일반 2014. 4. 10. 11:13

C++ 11 줄어듬 narrowing 방지

웹 컴파일러 링크 : http://melpon.org/wandbox/


C와 C++의 문제점으로 아래와 같은 것을 암시적으로 잘라낸다.


int x = 7.3; // 데이터 손실

void f(int);

f(7.3); // 데이터 손실


하지만 C++ 11 에서 {} 초기화는 이와 같은 narrowing cast 를 허용하지 않는다.



int x0 {7.3}; // 오류 : 줄어듬

int x1 = {7.3}; // 오류 : 줄어듬

double d = 7;

int x2{d}; // 오류 : 줄어듬 (double 에서 int 로)

char x3{7}; // 가능 : 7 이 비록 int 이지만 이는 줄어듬이 아니다

vector<int> vi = { 1, 2.3, 4, 5.6 }; // 오류 : double 에서 int 로 줄어듬


C++ 11 이 많은 수의 호환성 문제를 방지할 수 있는 이유는 바로 가능한 초기화자의 타입이 아닌 실제 값을 비교해서 줄으듬인지 아닌지 판단하기 때문이다. 만일 어떠한 값이 목표한 타입과 정확히 동일하게 같은 값으로 표현할 수 있다면 그 타입 변환은 줄어듬이 아니다.


char c1{7}; // 7 은 int 이지만 char 에 포함되므로 가능하다

char c2{77777}; // 오류 : 줄어듬


부동 소수점 - 정수 간 변환은 언제나 줄어듬으로 판단한다. 


int ivalue = 7.0; // 오류 : 줄어듬


프로그래밍 일반 2014. 4. 9. 18:14

C++ 11 Initializer lists 초기화자 리스트

웹 컴파일러 링크 : http://melpon.org/wandbox/


std::vector<double> v = { 1, 2, 3.456, 99.99 };

std::list<std::pair<std::string,std::string>> languages = {

{”Nygaard”,”Simula”}, 

{”Richards”,”BCPL”},

{”Ritchie”,”C”}

};


std::map<std::vector<std::string>,std::vector<int>> years = {

{ {”Maurice”,”Vincent”, ”Wilkes”},{1913, 1945, 1951, 1967, 2000} },

{ {”Martin”, ”Ritchards”}, {1982, 2003, 2007} },

{ {”David”, ”John”, ”Wheeler”}, {1927, 1947, 1951, 2004} }

};


초기화자 리스트는 단순히 배열만을 위해 사용되는 것은 아니다. {} 리스트를 받는 메커니즘은 std::initializer_list<T> 를 인자로 받는 함수(많은 경우 생성자)와 같다. 예를 들어


void Func(std::initializer_list<int>);


Func({1,2});

Func( { 23, 2344, 23434, 23523} );

Func( {} ); // 비어 있는 리스트

Func{1, 2}; // 오류 : 함수 호출 ()가 없다.


years.insert( {{”Martin”, ”Ritchards”}, {1982, 2003, 2007}} );


초기화자 리스트는 임의의 길이가 될 수 있지만 모두 같은 타입이어야 한다. ( 즉 모든 원소들이 템플릿 인자 타입 T 이거나, T 로 변환될 수 있어야 한다. )


template<class T> class vector {

public:

vector (std::initializer_list<T> s) // 초기화자 리스트 생성자

{

reserve(s.size()); // 올바른 크기의 공간을 얻는다

// 원소들을 초기화 한다. (elem[0:s.size()) 안에 있는 것들을)

uninitialized_copy(s.begin(), s.end(), elem);

sz = s.size(); // vector 크기를 설정한다

}

// ... as before ...

};


직접적으로 초기화하는 것과 복사 초기화하는 것의 차이는 {}를 이용한 초기화에서도 마찬가지로 적용된다.

예를 들어 std::vector 에는 int 로 부터 생성하는 명시적 생성자와 초기화자 리스트 생성자가 있다.


std::vector<int> v1(10); // 가능 : v1에는 10개의 원소가 있다.

v1 = 10; // 오류 : 허용되는 변환이 없다.


std::vector<int> v2 = 10; // 오류 : int 를 std::vector<int> 로 변환할 수 없다.


void Func( const std::vector<int>& );


Func( 9 ); // 오류 : int 를 std::vector<int> 로 변환할 수 없다.


std::vector<int> v1{10}; // 가능 : v1에는 1개의 원소가 있다 (값이 10)


v1 = {9}; // v1 에는 1개의 원소가 있다. (값이 9)


Func( {9} ); // Func는 리스트 {9} 로 호출



std::vector<std::vector<int>> vs = {

std::vector<int>(10), // 가능 : 명시적 생성 (10 개의 원소)

std::vector<int>{10}, // 가능 : 명시적 생성 (값이 10 인 1 개의 원소)

10 // 오류 : vector 의 생성자는 명시적

};


함수는 initializer_list 를 인자로 접근할 수 있다. 예를 들어


void f(initializer_list<int> args)

{

for (auto p=args.begin(); p!=args.end(); ++p) cout << *p << "\n";

}


참고 :


프로그래밍 일반 2014. 4. 8. 19:15

C++ 11 enum class

웹 컴파일러 링크 : http://melpon.org/wandbox/


 기존 enum은 암시적으로 int형으로 형변환 되기 때문에 enum이 정수로써 작동되기를 원하지 않을 때 오류를 발생 시킬 수 있다.

허용되는 범위 밖에서도 열거 이름들을 사용할 수 있었기 때문에 이름간의 충돌이 발생한다.


그러나 새로운 enum class는 타입과 정의 범위가 명확하다.



enum Alert { green, yellow, election, red };  // 이전의 enum

enum class Color { red, blue };    // 범위가 정해져있고, 타입이 잘 정해진 enum

// 이를 포함하고 있는 범위 바깥에서는 열거형을 사용 불가

// int 로 암시적으로 변환되지 않는다.

enum class TrafficLight { red, yellow, green };



Alert a = 7;             // 오류 (모든 C++ 버전에서)

Color c = 7;            // 오류 (int 에서 Color 로 변환 불가)

int a2 = red;              // 가능 (Alert 에서 int 로 변환 가능)

int a3 = Alert::red;     // C++ 98 에서는 오류. C++ 11 에서는 가능

// vs 2008 ver. 9.0.30729.1 sp 에서 warring


int a4 = blue;         // 오류 : blue 가 범위에 없다

int a5 = Color::blue; // 오류 : Color 에서 int 로 변환 불가능

Color a6 = Color::blue; // 가능

새롭게 도입한 열거형의 이름이 "enum class"인 이유는 전통적인 enum의 특징들[각주:1]과 클래스의 특징[각주:2]을 합친 형태이기 때문이다. enum의 타입 자체를 명확하게 정의하면 열거형의 크기를 보장하고, 다른 것들과 쉽게 같이 사용할 수 있다.


enum class Color : char { red, blue };             // 함축된 표현

enum class TrafficLight { red, yellow, green }; // 디폴트로 내장 타입은 int 로 처리된다.

enum E { E1 = 1, E2 = 2, Ebig = 0xFFFFFFF0U };     // E 의 크기가 얼마나 클까요?

// 이전의 규칙에 따르면 -

// 구현에 따라 다르다(implementaion defined)

enum EE : unsigned long { EE1 = 1, EE2 = 2, EEbig = 0xFFFFFFF0U }; // 이제 구체적으로 정할 수

있습니다


enum class Color code : char;    // 전방 선언,  VS 2008 에서 사용 가능

void foobar(Color code* p);        // 전진 선언을 사용

// ...

enum class Color code : char { red, yellow, green, blue };  // 정의

// VS 2008 에서 사용 가능


msdn C++ 레퍼런스를 확인하면

enum [tag] [: type] {enum-list} [declarator];   // for definition of enumerated type
enum tag declarator;   				// for declaration of variable of type tag

// 아래의 코드를 한번씩 확인해보자..

enum Alert { green, yellow, election, red = 400000000 }; //enum Alert { green = -1, yellow, election, red = 4000000000 }; //enum Alert : INT64 { green = -1, yellow, election, red = 40000000000 }; // VS 2008에서 사용 가능


enum 의 내장 타입은 반드시 부호 있는/없는 정수 타입이어야만 합니다. 디폴트는 int 입니다. 표준 라이브러리에서 enum class 는 다음과 같은 경우로 사용됩니다.

  • 시스템의 특별한 오류 코드들을 매핑하기 위해서 (enum class errc)
  • 포인터 안정성 표시자(indicator) (enum class pointer safety {relaxed, preferred, strict};)
  • I/O 스트림 오류들 (enum class io errc {stream = 1 }; )
  • 비동기화통신에서의오류처리를위해(enum class future errc {broken promise, future already retrieved,promise already satisfied }; )


참고






  1. 값을 가진 이름들 [본문으로]
  2. 범위를 가진 멤버들과 암시적 변환의 부재 [본문으로]