본문 바로가기

C++

c++ 11 (실행구조 / Bitwise / 순수 가상함수)

Bitwise 연산

비트를 기준으로 논리연산을 하는 것을 비트와이즈 연산이라고 한다

 

  • 비트와이즈 and : & 연산자를 활용한다
  • 비트와이즈 or : | 연산자를 활용한다
  • 왼쪽 쉬프트 : << 연산자를 활용한다
  • 오른쪽 쉬프트 : >> 연산자를 활용한다

 

integer가 8비트라고 가정시 

v1

0 0 0 0 0 0 0 1

v2

0 0 0 0 0 0 1 0

 

bitwise | (or) 를 하게 되면 말 그대로 비트 별로 | (or) 연산

0 0 0 0 0 0 1 1

 

00000011 이 10진수로 3이 나옴

 

bitwise & (and) 연산을 하게 되면 0이 나온다 

 

#include <cstdio>


int main () {
    
    int v1 = 1; // 0001
    int v2 = 2; // 0010
    int v3 = 3; // 0011
    
    int bitwise_or = v1 | v2;
    printf("%d\n", bitwise_or); // 3이 출력됨
    
    int bitwise_and = v1 & v2;
    printf("%d\n", bitwise_and); // 0이 출력됨
    
    int bitwise_or2 = v1 | v3;
    printf("%d\n", bitwise_or2); // 3이 출력됨
    
    int bitwise_and2 = v1 & v3;
    printf("%d\n", bitwise_and2); // 1이 출력됨
    
    return 0;
}

 

쉬프트 연산 : << (비트를 왼쪽으로 쉬프트) 혹은 >>(비트를 오른쪽으로 쉬프트) 이용

#include <cstdio>


int main () {
    
    int v1 = 1; // 0001
    int v2 = 3; // 0011
    
    
    int shift_left = v2 << 1; // 한 칸씩 올라간다. 3이 0011 이었는데 한칸씩 왼쪽으로 가서 0110이 된다.
    printf("%d\n", shift_left); // 6이 출력됨
    
    int shift_right = v2 >> 1; // 0011이 오른쪽으로 한칸씩 이동 0001이 된다.
    printf("%d\n", shift_right); // 1이 출력됨
    
    return 0;
}

 

Bitwise 연산의 이유

컴퓨터는 기본적으로 비트와이즈 연산으로 이루어진 것을 베이스로 더하기 빼기 곱하기 나누기를 한다.

기계의 근본에 해당하는 연산

프로그래밍에서 카테고리를 선별해내기 위해 가장 많이 쓴다.

가장 빠른 방법으로 카테고리를 넣어줄 수 있는 방법이 Bitwise 연산이 된다.

 

int면 32비트 각각의 비트 하나하나에 카테고리를 할당을 해 줄 수 있다.

 

#include <cstdio>


void foo(int params) { // foo라는 함수에서 파라미터로 받았는데 파라미터로 받은 int가 여러개 카테고리를 가질 수 있다고 가정
    int category1 = 1; // 1
    int category2 = 1 << 1; // 2
    int category3 = 1 << 2; // 4
    
    // 받은 파라미터가 어떤 카테고리에 있는지 확인할 수 있다.
    
    if ((category1 & params) != 0) {
        printf("카테고리 1에 포함됩니다.\n");
    }
    if ((category2 & params) != 0) {
        printf("카테고리 2에 포함됩니다.\n");
    }
    if ((category3 & params) != 0) {
        printf("카테고리 3에 포함됩니다.\n");
    }
}




int main () {
    
    foo (1 | 4);
    
    return 0;
}

 

 

순수 (pure) 가상함수

순수 가상함수는 몸체가 없는 가상함수를 의미한다.

선언만 존재하고 그 동작(몸통)을 자식 클래스에 구현해야 한다

 

 

다리를 변수로 저장하는게 아니라 getLegs 함수를 이용해서 만들기

printInfo를 할 때마다 값이 유동적으로 변하기 때문에 알 수가 없다.

그래서 순수 가상 함수 만들기

 

#define _CRT_SECURE_NO_WARNINGS
#include <cstdio>
#include <cstring>


enum class Time {
    Morning, Afternoon, Night
};


class Animal {
public:
    char name[100];
    Animal(const char* name) {
        strcpy(this->name, name);
    }
    
    virtual int getLegs() = 0;
    // 순수가상함수는 선언 시 = 0을 붙여 주면 된다. 그러면 몸체가 필요없어짐

    // 이 가상함수를 구현하지 않으면 클래스를 만들 수 없다.
    
    void printInfo() {
        printf("이름 : %s / 다리 : %d\n",name, getLegs());
       // getLegs함수가 어떻게 구현될지는 자식클래스에서 정해주기
      printInfo() 함수에서 getLegs라는 함수를 만들어주고 이것을 순수가상함수로 만들어도
         이후에 이것을 자식클래스에서 구현하게 되면 좀 더 유기적인 프로그램이 만들어질 수 있다.

};


class Person : public Animal {
private:
    Time time = Time::Morning; // 처음에 morning으로 초기화
public:
    Person(const char* name) : Animal(name) {}
    // Person은 상속을 받았기 때문에 Person 즉 사람으로서 존재하기 위해서는 순수 가상함수 몸체를 구현해줘야한다.
    


    virtual int getLegs() { // 시간에 따라 사람만 동적으로 다리의 개수가 바뀌는 것
        switch (time) {
            case Time::Morning: return 4;
            case Time::Afternoon: return 2;
            case Time::Night: return 3;
                
        }
        return 2; // 기본값은 2로 두기
    }
    
    void setTime(Time t) {
        this->time = t;
    }
};


class Dog : public Animal {
public:
    Dog() : Animal("개") {}
    
    virtual int getLegs() {
        return 4;
    }
};




int main () {
    
    Person* p = new Person("영희");
    p->printInfo();
    
    Dog* d = new Dog();
    d->printInfo();
    
    return 0;
}

 

 

프로그램의 실행 구조

 

프로그램은 실행될때 Stack과 Heap 메모리가 존재하며

Stack 영역에 존재하는 값들은 '정해진' 즉 정적인 존재가 되며 (함수 콜 스택에 따라 정해진 생명을 갖는다)

 

Heap 영역에 존재하는 값들은 '유동적인' 즉 동적인 존재가 된다. (생명주기가 해제될때 까지 유지된다)

 

바이트 배열로 exe 파일이 만들어진다. 이 exe 파일이 우리가 실행을 하게 되면 이게 쭉 RAM 메모리로 올라감 (랜덤 엑세스 메모리)

그러면 os에 의해 이런식으로 맞춰지게 됨

 

Intructions : 실행코드가 만들어지게 된다. Instructions 가장 위쪽에 있는 부분부터 프로그램을 실행하게 된다.
Heap : 함수가 종료되어도 같이 지워지지 않는다. (delete 키워드 활용해서 지워줘야한다.)
--  동적메모리 할당 된 그 메모리들을 관리 해줌 <- 무조건 수동메모리, 유동적 (어떻게 Heap이 변화할지 아무도 모름)
new 키워드를 이용해 만들면 이 Heap 영역에 만들어진다. 
Stack: 어떤 값들이 하나씩 하나씩 차곡차곡 쌓여서 올라가고 빠지게 되는 형태 
-- 함수 콜스택 관리 (함수가 관리, 실행이 어떻게 될 지를 결정)  <-  정적 (무엇인가 정해져있다.)

 

#define _CRT_SECURE_NO_WARNINGS
#include <cstdio>
#include <cstring>


int sum(int a, int b) { // 값을 합하는 함수
    int result = a = b;
    return result;
}


int main () {
    
    int v1 = 1;
    int v2 = 2;
    
    int r = sum(v1, v2);
    printf("결과 : %d\n", r);
    
    return 0;
}


/*
 Stack에 v1, v2가 만들어진다. 그리고 r이라는 결과를 담는 변수가 만들어진다.
 그 후  Instructions에서 보면 다음 함수 sum호출 해서 연산
 그 결과값을 임시로 갖고 있다가 이 Stack에서 이루어진 함수 Stack 되감기를 실행해서 다시 sum을 호출한 시점으로 돌아온다
 돌아와서 이 결과를 r이라는 곳에다가 집어넣게 된다. 그러면 a | b |result는 없어지게 되고 result라는 값이 r로 들어간다.
 
 a  |  b  | result | 연산 후 result를 r에 넣고 a | b | result는 삭제
 v1 |  v2 |   r    | r 이라는 값에 3이 들어가게 되고 이것을 Instructions에서 보면 다음 함수 호출은 printf
 printf 함수를 호출하여도 콜스택이 하나 만들어진다.
 
 printf 콜스택
 v1 |  v2 |   r    |
 
 printf에서 또 다른 함수를 호출 할 수 있다. 그러면 그 함수를 호출했을 때 또 콜스택이 만들어지고 변수들을 할당하고
 그 다음에 이게 끝나면 다시 printf로 돌아올 때 이 콜스택에 있는 함수들을 다 지워주고 결국 main함수로 와서
 v1 |  v2 |   r  만 남게된다
 마지막에 return 구문을 실행할 때 프로그램이 종료가 되면서 Stack이 전부 다 파괴가 되고 Heap도 전부 다 파괴된다
 이게 함수 콜스택의 원리
 */

 

함수는 콜스택을 만들어야 하기 때문에 함수에는 함수 호출 비용이 있다.

함수 호출을 한다는 것은 성능적으로,, 메모리적으로 어느정도 손해를 감수해야 한다는 뜻