F.R.I.D.A.Y.

[TIPS 19TH] 09 : 2018.07.24. (월) 본문

외부활동/TIPS 19th

[TIPS 19TH] 09 : 2018.07.24. (월)

F.R.I.D.A.Y. 2018. 7. 24. 13:59
반응형

1. 구조체 정렬[각주:1]

 구조체를 간단히 설명하면 변수들의 집합으로 보면 된다. 그렇다면 구조체의 크기는 어떻게 될까? 구조체 안에 선언된 변수 크기의 합일까? 반은 맞고 반은 틀리다고 볼 수 있다.


 시대가 발전하면서 자료를 어떻게 다루어야 더 효율적으로 다룰 수 있는지에 대한 연구와 관련된 이론이 나오게 되었다. 현재의 구조체 크기도 이러한 발전의 결과물로서, 다음 코드를 보자.


struct A {
	char a;  // 1 Byte
	char b;  // 1 Byte
	short c; // 2 Byte
	int d;   // 4 Byte
};

 [ A ]라는 구조체에 [ a, b, c, d ]라는 변수가 차례로 선언되어있다. 전체 크기는 8 Byte이므로 구조체 [ A ]의 크기는 8 Byte이다. 그렇다면, 저기에서 [ b ]를 삭제하게 되면 어떻게 될까? 구조체 크기가 7 Byte가 될수도, 그렇지 않을 수도 있다. 이유를 설명하기 전에 구조체의 정렬 방식 네가지를 보자.


정렬 크기 

정렬 방식 

1 Byte 

 데이터의 주소가 1의 배수에 배치됨[각주:2] 

2 Byte 

 데이터의 주소가 2의 배수에 배치됨

4 Byte 

 데이터의 주소가 4의 배수에 배치됨

8 Byte 

 데이터의 주소가 8의 배수에 배치됨

 8 Byte 정렬을 하더라도 8 Byte 자료형이 사용되지 않으면 4 Byte처럼 정렬 


struct A {
	char a;  // 1 Byte

	short c; // 2 Byte
	int d;   // 4 Byte
};


 다시, 위 코드에서 [ b ]변수를 지웠을 때, 네가지 정렬방식을 순서대로 도식화 하면 다음과 같아진다.



 Visual Studio의 경우에는 8바이트 정렬을 기본으로 한다. 설정을 바꾸고 싶다면 아래 옵션을 변경해주면 된다.


VS 2017 Community 버전 기준 : [ 프로젝트 속성 - C/C++ - 코드 생성 - 구조체 멤버 맞춤 ]


 1바이트 정렬을 정렬을 사용하면 되는것 아니냐 하는데, 효율성 측면에서도 그렇지만 윈도우가 지원하는 API 라이브러리는 1 Byte 정렬을 지원하지 않는다. 서로 다른 정렬을 사용하는 프로그램이 만났을 때 호환성 문제가 있기때문에 가장 보편적으로 사용하는 8 Byte정렬을 사용한다.


 프로그램이 비효율이 되는 문제가 구조체 정렬에서도 발생할 수 있기 때문에 구조체를 사용하면 사용하기에 앞서 [ sizeof ]를 이용해 구조체의 크기를 알아보는 것이 좋다. 만일 예상한 크기보다 너무 크게 나온다면 구조체가 문제가 있는 것이므로 구조체 정의부분을 다시한번 체크하는 것이 좋다.


 참고

>> N바이트 정렬을 하게되면 구조체의 크기는 무조건 N바이트의 배수형태로 이뤄진다.



2. Cpp[각주:3](C++)

이전까지 배운 것은 C에 대한 설명이다. C++은 C의 단점을 보완하면서 C와 비슷한 수준의 퍼포먼스를 보이기 위해 새로 파생되어 만들어졌다. 자세한 내용은 여기[ 1번, 2번[각주:4]]를 참고하자.


 C++의 특징은 다음과 같은 것들이 있다.

>> C언어에서의 프로그래밍 실수를 방지할 수 있는 방법이 언어차원에서 제공된다.

>> 데이터의 구조가 변경되어도 프로그램 전체 구조는 변경할 필요가 없다. 데이터 구조 변화에 따른 문제를 언어선상에서 해결해준다.[각주:5]

>> 전체 언어에 대해서는 상당히 빠른 속도를 가지고 있지만, C에 비해서는 떨어진다.


 C++에서는 클래스( class )라는 문법을 통해 여러 데이터와 관련된 함수들을 한데 모아 관리할 수 있다. 이러한 특성때문에 자료의 구조가 바뀌어버리면 모든 코드를 돌면서 해당 데이터에 접근/제어하는 부분을 찾아 수정해야하는 C언어와는 달리 C++은 해당 자료형을 관리하는 클래스에서 수정하기만 하면 클래스를 사용하는 모든 부분으 제어가 되기때문에 좀 더 데이터 관리 측면에서 용이하다.

 이러한 특성을 이용해 프로그래밍 하는 것을 "객체 지향 프로그래밍( OOP )"이라고 하며 C++는 객체 지향 프로그래밍 언어이다.



3. class

 기존의 C의 코딩스타일은 한 방 안에서 모든 것을 수행한 것이라고 볼 수 있다. C에서도 파일을 나누어서 관리하는 작업은 가능하지만 한 방에서 모든 것을 하는데 그 안에서 정리를 하는 구조라고 할 수 있다. 그러나 C++는 한 방에서 모든 작업을 처리하는 것이 아니라 여러 방으로 나누고 방마다 기능과 행동 등을 나눠 작업을 하게 되는데, 이러한 방을 클래스라고 보면 된다.


struct People{
    char name[10];
    int age;
    double height;

}

void AddPeople(People* p){
    scanf("%s", p->name);
    scanf("%d", &p->age);
    scanf("%lf", &p->height);

}

void main(){
    struct People data;
    AddPeople(&data);
    
}


 위 C코드는 사람의 간단한 인적사항을 입력받는 코드인데 위 코드를 C++형식에 맞게 고치면 아래와 같은 코드가 나온다.


class People{
private:
    char name[10];
    int age;
    double height;
public:
    void AddPeople(){
        scanf("%s", name);
        scanf("%d", &age);

    }
};

int main(void){
    People p;
    p.AddPeople();
    
    return 0;
}


 클래스에서 보이는 [ private: ]와 [ public: ]은 접근 제한자[각주:6]로서 접근 제한자 이후에 나오는 요소, 함수가 클래스 외부에서 접근해 사용할 수 있느지 없는지, 즉 접근권한을 부여해주는 장치이다. [ private ]접근 제한자는 해당 클래스 내부에서만 사용할 수 있으며 [ public ]접근 제한자는 해당 클래스 밖에서도 접근해 콜(Call) 하거나 값을 변경할 수 있다.

 일반적으로 [ private ]접근 제한자에는 클래스 내부에서 사용하는 변수[각주:7]들을, [ public ]접근 제헌자에서는 멤버변수의 값을 반환하거나 변경해주는 함수들을 작성한다. 멤버 변수는 C언어로 수정했을 때, [ struct ]에 들어가는 것들이라고 보면 된다.


 클래스는 [ this ]라고 하는 자기 자신을 가리키는 포인터를 제공한다. 이게 어떨 때 좋으냐 하면 아래 C++ 코드를 보자


class A{
private:
    int m_data;
public:
    void makeData(){
        int m_data;
        m_data = 5;
    }
};

 클래스 [ A ]에 이미 멤버 변수로 [ m_data ]가 선언이 되어있는데, [ makeData ]함수에 지역변수로서 [ m_data ]가 또 선언이 되어있다. 이렇게 되면 C++은 C와 마찬가지로 클래스 [ A ]에서는 어디서든 사용이 가능한 멤버 변수 [ m_data ]가 아니라 [ makeData ]에서 선언된 지역변수 [ m_data ]에 5를 넣는다고 판단한다. 그렇다면 멤버 변수와 같은 이름이 작성된 지역 변수를 갇는 함수에서는 멤버 변수를 사용하지 못하는가? 그건 아니다.


class A{
private:
    int m_data;
public:
    void makeData(){
        int m_data;
        this->m_data = 5;
    }
};


 [ this->m_data ]와 같은 형태로 멤버 변수에 접근할 수 있다. 이 [ this ]는 클래스안에 선언된 함수에서 모두 가지고 있으며 [ [ class Name ] * const this ]와 같은 형태로 컴파일 직전에 추가되어 컴파일된다. 즉, 이 [ A ]클래스에서는 [ A * const this ]로 선언되는 것이다. 이 [ this ]라는 포인터는 클래스 자신의 주소가 들어가기 때문에 멤버 변수를 가리킬 수 있다.


this 포인터가 사용되는 이유 [ 원문 ]

>> 객체 자신의 주소를 리턴하고 할 때

>> 객체 자신의 참조자를 리턴하고자 할 때

>> 객체 자신을 복사하여 리턴하고자 할 때

>> 동일 이름의 변수와 구분을 하기 위해서

>> 객체 내부에서 자신의주소값이 사용될 때


msdn의 this 포인터 설명

 일반적으로 this 포인터는 암시적으로 사용된다. 클래스 멤버 변수를 참조할 때는 [ this ]를 명시적으로 사용하는 것이 좋다.

 [ this ]는 const로 묶여있기 때문에 [ this ]의 값은 변경할 수 없다.


 가끔 다른 사람이 작성한 클래스를 보면 다음과 같이 [ 함수명() ] 뒤에 [ const ]가 붙는 경우가 있다


void showPeople() const
{
    printf("이름 : %s\n", name);
    printf("나이 : %d\n", age);
    printf(" 키 : %lf\n", height);
}


 이러한 [ const ]는 해당 클래스의 멤버 변수를 참조할 수는 있지만, 멤버 변수의 값을 변경하지는 못하도록 막는 장치이다.

 간단히 해서, 컴파일시에 코드가 아래와 같은 형태로 바뀐 후 프로그래밍 된다고 보면 되겠다.


void showPeople() const A * const this
{
    printf("이름 : %s\n", name);
    printf("나이 : %d\n", age);
    printf(" 키 : %lf\n", height);
}

 참고

>> 제한 접근자는 각 클래스당 한번만 작성할 수 있는것이 아니다. 몇번이고 사용할 수 있다.



4. Overload(과적)

 C언어에서 어떤 기능을 수행하는 함수를 만든다고 해보자.


int sum(int a, int b) {
        return a + b;
}

 위 코드는 두 수의 합을 반환해주는 함수이다. 그런데 만일 반환형을 실수로 받고싶다면? 매개변수의 자료형을 실수형으로 받고싶다면? C언어에서는 이름을 중복해 함수를 만들 수 없기때문에 함수 이름을 [ sumf[각주:8] ] 등의 형태로 지어 같은 기능을 해당 함수에 또 넣어야한다.


 C++은 언어 생성 과정에서 이러한 문제를 잘 알고있었기 때문에 이를 오버로딩, 과적 기능을 통해 해결했다.


int sum(int a, int b) {
        return a + b;
}

float sum(float a, float b) {
        return a + b;
}
기존 C에서는  함수 이름이 중복되기때문에 컴파일 에러가 발생하지만, C++의 경우에는 단순히 컴파일과정에서 함수의 이름만 가지고 판단하는 것이 아니라 함수의 이름, 매개변수의 특성(자료형, 개수)을 구분해 컴파일을 진행하기 때문에 오류를 띄우지 않고 정상 작동하게된다. 단, 반환형은 구분 대상에서 제외된다.


 이러한 내용때문에 C로 만든 라이브러리 파일을 C++ 프로그램에서 사용가능하지만, C++로 만든 라이브러리 파일을 C 프로그램에서 활용할 수 없는 이유인 것이다. 사람으로 친다면 단순히 이름만 가지고 구별하는 것보다 주민등록번호 등과 같이 개인의 특성을 몇개 더 참고해 사람을 찾는것이 찾고자 하는 대상을 더 정확하게 찾을 수 있는것과 같은 이치이다.


 오버로딩은 두가지로 나뉘는데 연산자 오버로딩과 함수 오버로딩이다. 함수 오버로딩의 경우에는 오버로딩의 기본적인 특성을 설명하면서 위에서 언급했기 때문에 여기 아래부터는 연산자 오버로딩에 대해 설명하겠다.


class Pos {
private:
	int x;
	int y;
public:
	Pos() {
		x = 0;
		y = 0;
	} // 생성자

	Pos(int x, int y) {
		this->x = x;
		this->y = y;
	} // 생성자

	Pos(Pos *p) {
		this->x = p->x;
		this->y = p->y;

	} // 복사생성자

	int x() {
		return this->x; // [ this-> ]는 생략가능
	}
	int y() {
		return this->y;
	}

};

int main(void) {
	Pos p;
	p = p + 1;

}


 X와 Y 좌표를 포함하는 클래스를 만들었다. [ main ]에는 p를 생성하고 p의 X와 Y에 각 1씩 더하는 의미로 [ p + 1 ]을 작성했다. 그런데, 이렇게 작성하면 연산자 오류가 발생한다. 컴파일러는 의미파악을 하지 못했기 때문이다. 따라서 해당 ' + '연산자가 어떤 일을 하는지 정확하게 연산자를 정의내려주어야한다.


public:
	Pos operator+(int up) {
		return Pos(x + up, y + up);
	}


 이렇게 작성하면 정수형 N을 더했을 때, X와 Y 값이 각 N만큼 증가한다. 여기서 함수명에 [ operator ]를 붙여주는 이유는 함수명에 ' + '등과 같은 특수문자는 사용할 수 없는데, 이것이 연산자를 의미한다는 것을 알리기 위해 작성하는 것이다. 따라서 [ operator ]와 다른 일반 문자열을 함께 작성하면 함수가 되어버린다.


 또, 이렇게 작성한 것은 연산자처럼 사용하기는 하지만, 실제 구성은 아래와 같이 된다.


p.operator+(1);



 즉, [ operator+ ]라는 함수를 정의하고 사용하는 방식이 두가지인 것이다. 이것이 가능한 이유는 컴파일러가 연산자를 만나면 양쪽의 자료형을 파악한 후 해당 특성에 맞게 연산자를 사용하기 때문이다.


 [ A + B ]라는 식을 만나게 되면,  ' + ' 양 쪽에 있는 [ A ]와 [ B ]가 클래스인지 판단한다. 하나라도 클래스라면 해당 클래스로 들어가서 ' + '에 대한 연산이 정의되어있는지 확인한 후, 정의되어있다면 정의되어있는 연산을 수행한다.


 참고

>> 연산자 양쪽이 모두 클래스인 경우에는 선행하는 클래스에 정의된 연산자를 이용한다. 단, 이 경우에는 서로 다른 클래스에 대한 연산을 진행하기 때문에 연산자 오버로딩으로 해당 클래스와 어떻게 연산할것인지 작성해주어야한다.

>> 연산자를 만나게 되면 양측의 피연산자 타입을 무조건 검사한다.

>> 모든 연산자를 오버로딩할 수는 없다.

>> 재정의 가능한 연산자 목록


  1. 구조체를 모른다면 이전 강의를 보고 올것. [본문으로]
  2. 변수 크기의 합이 곧 구조체의 크기이다. [본문으로]
  3. 일반적으로 씨쁠쁠이라고 읽는다. 강한 발음 하기 싫어하는 편이라서 나는 씨피피라고 읽는다. C++ 코드를 작성한 파일의 확장자도 cpp이다. [본문으로]
  4. 설명이 장황히 늘어져있으므로 이해하지 못하겠다면 접근하지 않는 편이 오히려 좋을수도 있다. 어느정도 기본기를 쌓고나서 접근하는 것도 한 방법. [본문으로]
  5. class에서 다룬다. [본문으로]
  6. private, public 외에도 friend라는 접근제한자가 존재한다. [본문으로]
  7. 이렇게 클래스 내부에서 사용하는 변수를 "멤버 변수"라고한다. [본문으로]
  8. sum float [본문으로]
728x90
반응형

'외부활동 > TIPS 19th' 카테고리의 다른 글

[TIPS 19TH] 11 : 2018.07.30. (월)  (2) 2018.07.31
[TIPS 19TH] 10 : 2018.07.26. (목)  (2) 2018.07.27
[TIPS 19TH] 08 : 2018.07.19. (목)  (2) 2018.07.21
[TIPS 19TH] 07 : 2018.07.16. (월)  (2) 2018.07.17
[TIPS 19TH] 06 : 2018.07.13. (목)  (2) 2018.07.13
Comments