ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • C++ 정리(게임개발자를 위한 C++ 참고)
    @ 16. 1 ~ 17. 1/면접관련 2016. 12. 28. 23:11

    실행시간에 메모리가 할당 : 동적 메모리 할당

    일반적인 변수 선언문 같은것은 컴파일 시간에 변수의 메모리 위치가 결정 : 정적 메모리 할당


    바인딩 : 변수나 함주의 주소를 결정하는 것을 바인딩이라고 한다.


    C의 Malloc과 C++ new의 차이점

    1. 동적 메모리 할당이 빈번히 사용됨에도 불구하고 함수인것이 못마땅함..C++에서는 동적 메모리 할당이 함수에서 연산자로 승격되었다.

    연산자는 함수보다 훨씬 유동적이고 함수 호출의 오버헤드가 없다. 또한 C에서 하던 매번 형변환의 불필요한 연산이 없어진다.

    (C의 경우 (int*)malloc(2) 이런식인데 C++ 의 경우 ip = new int; 이런식이다)

    2. new는 메모리 할당과 동시에 초기화를 하는것을 허락한다. 


    함수의 시작 주소를 다른 변수에 저장할 필요가 있을때 함수 포인터 변수를 사용한다.


    객체 : 실세계의 대상물을 의미하며, 정적인 특성과 동적인 특성을 가진다. 정적은 멤버변수, 동적은 멤버함수로 구현이된다.

    C에서는 구조체로 표현이 불가능하다..


    클래스의 입장에서 생성되는 a는 (A클래스의)자신의 인스턴스이다. 하지만 a객체 입장에서 자신은 A의 객체이다.

    (에를 들어 A클래스 A a일 경우)


    volatile 변수 : 레지스터 할당 및 최적화를 금지한다. 이변수를 참조하는 코드는 어떤 최적화도 하지 않기 떄문에 값을 읽어올때 반드시 메모리의 현재 값을 읽어온다.

    mutable : 상수 멤버함수에서 부득이 어떤 변수 값을 변경해야할때 사용하는 선언자



    함수 호출규약이란

    호출자(Caller)와 피호출자(Callee) 간에 '함수를 호출할 때 전달되는 인자의 순서나 사용이 끝나고 나서의 스택 정리 등'에 대한 약속

    __cdecl 방식은 C/C++ 함수에서 기본적으로 사용되는 호출 규약이며, 호출자가 스택을 정리합니다. 그리고 인자는 오른쪽에서 왼쪽으로 전달되며 호출자가 피호출자를 호출 시에 전달되는 인자의 개수를 알고있기 때문에 가변 인수 함수를 만들 수 있다는 장점을 지니고 있습니다.


    _stdcall 방식은 Win32 API에서 사용되며, 피호출자가 스택을 정리합니다. 그리고 인자는 __cdecl 방식과 마찬가지로 오른쪽에서 왼쪽으로 전달되며 Win32 API에서는 가변 인수 함수가 없기 때문에, 매개변수의 개수가 고정적입니다. 이는 호출자에서 스택을 정리하는 것보다, 피호출자가 스택을 정리하는게 더욱 효율적입니다


    fastcall 방식은 이름부터가 정말 빨라보이지 않나요? 이 __fastcall 방식에선 스택이 아닌 가까운 레지스터를 사용함으로써 호출 속도가 빠르며 피호출자가 스택을 정리하나 스택을 사용하지 않고 레지스터를 이용하므로 정리할 내용이 없어 따로 정리를 하지 않습니다. 

    이 __fastcall 방식을 사용하게 되면 앞에 두개의 매개변수는 ECX와 EDX 레지스터를 이용하지만, 나머지 매개변수는 위에서 설명드린 __cdecl 방식과 __stdcall 방식과 동일하게 오른쪽부터 왼쪽으로 인자가 스택에 올라갑니다. 우선 피호출자를 확인하여 어떻게 덧셈이 이루어지는지 살펴보도록 합시다


    _cdecl과의 차이점

    __cdecl과 __stdcall의 가장 큰 차이점은 스택 정리 주체가 누구인가하는 점인데 사실 이 차이점이 컴파일된 결과 코드에 미치는 영향은 별로 없다. 스택 정리 주체와는 상관없이 스택은 항상 호출 전의 상태로 복구되며 프로그램의 동작도 완전히 동일하다. 실행 속도는 거의 차이가 없으며 프로그램의 크기는 비록 무시할만한 수준이기는 하지만 __stdcall이 조금 더 작다. 왜냐하면 함수를 여러 번 호출하더라도 스택을 정리하는 코드는 함수 끝의 접미에 딱 한 번만 작성되기 때문이다. 반면 __cdecl은 호출원이 스택을 정리하므로 호출할 때마다 정리 코드가 반복되어 프로그램 크기가 조금 더 커진다.



    또 다른 중요한 차이점은 가변 인수 함수를 만들 수 있는가 아닌가 하는 점이다. __stdcall은 함수가 직접 스택을 정리하기 때문에 가변 인수 함수를 지원하지 않는다. 함수 접미에 스택 정리 코드를 작성하려면 인수의 총 크기를 미리 알아야 하는데 가변 인수 함수는 전달되는 인수 개수가 가변이므로 이 크기가 고정적이지 않아 접미에서 스택을 직접 정리할 수 없다. 컴파일러가 접미의 ret n 명령에 대해 n을 결정할 수 없는 것이다.

    이에 비해 __cdecl은 함수가 스택을 정리할 책임이 없으며 호출원이 함수를 부를 때마다 스택을 정리한다. 함수를 호출하는 쪽에서는 인수를 몇개나 전달했는지 알 수 있으므로 실제 전달한 인수 크기만큼 스택을 정리할 수 있다. 그래서 printf나 scanf같은 가변 인수를 지원하는 함수는 모두 __cdecl 호출 규약을 사용한다. 또한 윈도우즈 API 함수의 기본 호출 규약은 __stdcall이지만 wsprintf는 예외적으로 __cdecl로 작성되어 있다.

    호출 규약 중 호출원이 스택을 정리하는 것은 __cdecl밖에 없으며 그래서 가변 인수를 지원할 수 있는 호출 규약도 __cdecl이 유일하다. 가변 인수 함수를 만들려면 반드시 __cdecl 호출 규약을 사용해야 한다. 만약 가변 인수 함수를 __stdcall로 작성하면 컴파일러는 이를 무시하고 __cdecl로 강제로 바꾸어 버린다.



    edx, ecx 레지스터를 통해 전달받은 인수 둘을 순서대로 지역변수 영역에 복사한 후 사용하는데 어차피 인수도 지역변수의 일종이므로 이렇게 해도 별 상관이 없다. VC는 fastcall 호출시 ecx, edx로 인수를 넘기기는 하지만 이를 다시 스택의 지역변수로 만드는데 이렇게 되면 fastcall을 하는 의미가 없다. 비주얼 C++은 fastcall을 형식적으로만 지원할 뿐 fastcall의 장점을 취하지는 않는데 이는 컴파일러 구현상 ecx, edx 레지스터가 꼭 필요하기 때문이다. 

    스택 정리는 함수가 하는데 Add 함수의 경우 인수가 두 개 뿐이므로 인수 전달을 위해 스택을 사용하지 않았으며 그래서 정리할 내용이 없다. 만약 인수가 세 개라면 제일 끝의 ret는 ret 4가 될 것이다. 레지스터는 스택보다 훨씬 더 빠르게 동작하기 때문에 __fastcall은 이름대로 호출 속도가 빠르다. 대신 이식성에 불리하다는 단점이 있다. 이 호출 규약은 ecx, edx 레지스터를 사용하도록 되어 있는데 이 두 레지스터가 모든 CPU에 공통적으로 존재하는 것이 아니기 때문이다. 그래서 윈도우즈 API는 이 호출 규약을 지원하기는 하지만 사용하지는 않는다. 볼랜드의 델파이가 __fastcall을 사용한다


    thiscall

    thiscall은 클래스의 멤버 함수에 대해서만 적용되는데 ecx로 객체의 포인터(this)가 전달된다는 것이 특징이며 나머지 규칙은 __stdcall과 동일하다. 예외적으로 가변 인수를 사용하는 멤버 함수는 __cdecl로 작성되며 이때 this는 스택의 제일 마지막에(그러므로 첫 번째 인수로) 전달된다.

    이 호출 규약은 컴파일러가 멤버 함수에 대해서만 특별히 적용하는 것이므로 일반 함수에는 이 호출 규약을 적용할 수 없다. thiscall은 이 호출 규약의 이름일 뿐 키워드가 아니기 때문에 함수 원형에 thiscall이라고 쓸 수도 없다. 멤버 함수이기만 하면 컴파일러가 알아서 thiscall 호출 규약을 적용한다. 객체니 멤버 함수니 this니 하는 것들은 C++편에서 배우게 될 것이다.


    컴파일러는 함수의 호출 규약에 맞게 스택 프레임을 작성하고 관리한다. __cdecl을 쓰면 이 규약에 맞게 호출원이 스택을 정리하도록 컴파일할 것이며 __stdcall을 쓰면 함수가 스택을 정리하도록 컴파일할 것이다. 호출 규약에 따라 내부 코드가 약간 달라지고 무시해도 좋을 정도의 미세한 속도, 크기 차이가 발생하지만 실행 결과는 동일하다.

    그래서 함수의 호출 규약을 어떤 것으로 쓸 것인가는 고민할만한 문제가 아니다. 컴파일러가 지정한 호출 규약대로 함수를 컴파일하고 이 함수를 호출하는 적합한 코드도 같이 만들기 때문이다. 그러나 아주 특수한 경우 호출원과 함수의 호출 규약이 달라질 수도 있다. 어떤 때 이런 불일치가 발생할 수 있는가 하면 함수를 작성한 언어와 호출하는 언어가 다를때, 분리된 DLL에 있는 함수를 호출할 때, 원형이 이미 정해져 있는 콜백함수를 호출할 때 등이다. 



    오버로딩은 OOP 객체지향 프로그래밍의 특징을 결정짓는 3가지 특성중 한 가지 이다.

    추상화 : 클래스가 디자인 될 떄 내부적인 자세한 사항은 숨겨져서 클래스의 사용자가 내부적인 사항은 모르더라도 공개된 멤버만을 사용하여 클래스를 사용할 수 있는 것을 의미한다.


    캡슐화 : 추상화가 클래스를 디자인하는 사람의 관점에서 클래스를 바라본 것이라면, 캡슐 화는 클래스를 사용하는 사람의 관점에서 클래스를 바라본 것이다. 공개된 멤버만을 사용할 수 있으므로 외부적인 요인에 의해서 클래스가 변경되거나 파괴디지 않는 다는 보장이 생긴다.


    다형성은 OOP의 두번째 특성이다. 같은 이름의 함수나 연산자가 다른 기능을 하는 것을 허락한다. 

    상송성은 OOP의 세번째 특성이다 비슷한 일을 하지만 다른일을 하는 많은 응용을 위해 사용. 계층적 구조 ..

    객체지향 프로그래밍 이란 캡슐화, 다형성, 상속 을 이용하여 코드 재사용을 증가시키고, 유지보수를 감소시키는 장점을 얻기 위해서 객체들을 연결 시켜 프로그래밍 하는 것 입니다.


    첫째, 캡슐화는 말 그대로 클래스 또는 객체와 이것이 가질 수 있는 메서드를 하나의 단위로 묶어서 생각한다고 보면 됩니다. 즉, 사람(클래스 또는 객체)과 이 사람의 특징(걷고, 말하고, 보고, 듣고, 뛰고, ... 등등)을 하나로 묶어서 덩어리로 보는 것입니다. 외부에 공개된 것은 인터페이스가 되는 메서드가 되고, 외부(또는 내부)로부터 ‘걸어라’라는 지시에 의해 메서드가 작동하면 사람에게 부여된 걷는 방법에 의해 두발로 걷게 되는 것입니다. 캡슐화는 내부의 클래스의 속성은 보이지 않지만 공개된 메서드에 의해서 객체의 조정이 가능한 특징을 정의한 것입니다.

    하나의 문제를 해결하기 위한 데이터와 메서드를 하나의 단위로 묶는다는 것으로서클래스의 내부 정의에 대해 외부에서 볼 수 없도록 하는 것이 특징이다클래스에 정의된 메서드(Interface)만 볼 (사용할수 있으며내부의 속성과 구현은 볼 수 없게 디자인한다캡슐화는 객체를 외부의 부정적인 방법(또는 잘못된 방법)으로 사용하는 것을 방지하며사용자가 클래스의 내부에 대해 알지 못하는 상황에서도 외부에 공개된 메서드(Interface)를 통해 객체사용을 가능하게 해준다.

    캡슐화란 관련있는 데이터와 함수를 하나의 단위로 묶는 것이다.



    둘째, 추상화는 다양한 객체의 유사한 속성들을 묶어 일반화 한다고 이해하면 될 듯 합니다. 즉, 사람도 걷고, 개도 걷고, 새도 걷고 하는 걷는다는 속성을 추상화 하여 하나의 범주로 묶어서 보는 것이라고 이해하면 됩니다.

    모델(Object)의 자세한 성질을 무시하고(숨기고그들의 일반적인 성질을 나타낸다는 것으로서일반적으로 클래스는 클래스로 표현할 서브클래스(또는 객체)의 공통적인 성질과 행위를 일반화하여 디자인 되게 되며 그로부터 생성된 객체는 자신의 고유의 성질을 가지게 된다.


    셋째, 다형성은 추상화와는 약간 상반되는 성격을 띤다고 볼 수도 있는 것이지만 유사한 관계속에서 이해하는 것이 좋을 듯 합니다. 즉, ‘걷는다’는 메서드가 사람에게 부여되면 두발로 걷고 한발이 공중에 떠 있을 때 다른 한발은 땅에 붙어 있어야 한다는 룰에 의해 작동되고 전/후/좌/우 방향에 상관없이 어느 방향으로 이동 가능한 특징이 있다면, 개의 경우는 동일한 ‘걷는다’는 메서드에 의해 4발로 움직이며 전진과 후진, 회전만이 가능하며 두발이 공중에 있을 때 나머지 두발은 땅에 대고 있어야 한다는 특징을 갖는 것과 같습니다. 즉, 같은 메서드에 대해 달리 움직일 수 있지만 추상화 하면 다 걷는다는 범주에 들어간다는 것입니다.


    넷째, 상속성입니다. 즉, 클래스에 의해 현실적으로 발현한 것이 객체라고 한다면, 이 클래스의 특징을 고스란히 간직한 체 약간 다른 속성과 메서드를 더 갖는 클래스를 만들어 낼 수 있다는 특징이 상속성입니다. 즉, 사람이라는 클래스를 이용해서 사람의 특징을 고스란히 이어받았지만 피부색이 다른 백인종, 흑인종, 황인종 등을 만들어 내는 것을 상속이라고 보는 것입니다.


    객체지향개발(Object Oriented Programming)의 특성은 크게 추상화, 캡슐화, 상속성, 다형성이 있다.

    1) 추상화(Abstraciton)
    공통의 속성이나 기능을 묶어 이름을 붙이는 것
    - 객체 지향적 관점에서 클래스를 정의하는 것을 바로 추상화라고 정의 내릴 수 있겠다.
    - 좀 더 살펴보면 물고기, 사자, 토끼, 뱀이 있을 때 우리는 이것들을 각각의 객체라 하며 이 객체들을 하나로 묶으려 할 때,
      만약 동물 또는 생물이라는 어떤 추상적인 객체로 크게 정의한다고 하자. 이때 동물 또는 생물이라고 묶는 것을 추상화라고 한다.

    2) 캡슐화(Encapsulation)
    데이터 구조와 데이터를 다루는 방법들을 결합 시켜 묶는 것. 다시 한번 말하자면 변수와 함수를 하나로 묶는것을 말한다.
    - ex)
    public String test() {
              string aa = "aaa";
          }
    - 하지만 무작정 한대 묶으면 되는 것이 아니라
      객체가 맡은 역할을 수행하기 위한 하나의 목적을 한데 묶는다고 생각해야한다. 이것이 많이 들어본 의미로는 은닉화라고 한다.
    - 또한 데이터를 절대로 외부에서 직접 접근을 하면 안되고 오로지 함수를 통해서만 접근해야하는데 이를 가능하게 해주는 것이 바로 캡슐화이다.
    - 따라서 캡슐화에 성공하면 당연히 은닉화도 자연스럽게 효력이 나타난다.

    3) 상속성, 재사용(Inheritance)
    상위 개념의 특징을 하위 개념이 물려받는 것
    - 객체지향의 하이라이트 부분이라고 생각한다. 상속이란 개념이 없으면 객체지향이나 절차지향이나 도진개진
    - 간단히 예를 들자면, 자동차라는 부모클래스가 있다.
      기름을 먹거나 달리는 기능을 하는 자동차인데,
      만약 지붕뚜껑이 열리는 특수한 기능을 추가하고 싶다면 기존의 자동차에서 스포차카를 생성한다.
      그러면 스포츠카는 기름도 먹고 달리면서 지붕두껑이 열리는 기능도 갖춘 자동차가 되는 것

    4) 다형성(Polymorphism) 오버로딩과 오버라이딩이 특징으로 포함됨~~
    부모클레스에서 물려받은 가상 함수를 자식 클래스 내에서 오버라이딩 되어 사용되는 것
    - 간단히 예를 들자면
      군대에서 나는 K2 소총을 잡았고 동기는 K1 소총을 잡았다. 사격 훈련이 있을 때 중대장이 '준비된 사수부터 발사!'라고 외치면
      나와 내친구는 명령을 받고 앞의 사로를 향해 총을 쏜다. 이때 중대장은 추상적 객체를 상속받은 모든 객체들에게 명령을 내린것이고
      그 병사가 총이 뭐든간에 그냥 발사를 하라는 명령을 한것이다.
      즉, 다형성이 없다면 K1 소총을 든 병사 발사, K2 소총을 든 병사 발사 라며 명령을 하나하나 내려야 할 것이다







    함수가 오버로드가 되었을때 컴파일러가 구분을 할 수 없다면 모호함이 발생하게 된다. 컴파일 시간 에러!

    리턴형으로는 함수를 구분할 수 없다..const와 volatile한정자로 파라미터의 변경은 구분이 가능하다네..

    그리고 일반 변수랑 포인터 변수와의(갯수와 자료형이 같다면) 구분도 가능하고

    단 일반변수랑 참조형변수와의 구분은 모호함이 발생된다(갯수와 자료형이 다 같다)



    함수 오버로드는 클래스 범위와 블락 범위가 엄격하게 지켜진다. 즉, 함수의 오버로드는 같은 범위에서만 동작한다. 상위클래스에서 정의된 멤버함수와 같은 이름으로 하위 클래스에서 함수가 정의되었을때 상위 클래ㅡ의 함수는 무시되며 오버로드 되지 않는다.



    디폴트 매개변수 : 디폴트 값은 함수의 선언 부분에만 표현하면 되고 함수에 전달되는 인자는 왼쪽에서부터 오른쪽으로 채워지기

    때문에  반드시 오른쪽 매개변수의 디폴트 값부터 채우는 형태여야 한다.


    디폴트 매개변수에는 대부분이 상수 표현식이지만..현재 범위에서 볼 수 있는 함수? 상수표현식? 전역변수가 될 수 있다고 하네..

    하지만 지역변수나 비정적 클래스 맴버변수를 포함할 수는 없다.






    '@ 16. 1 ~ 17. 1 > 면접관련' 카테고리의 다른 글

    문자열 관련  (0) 2017.01.04
    C++ 템플릿의 종류  (0) 2017.01.04
    3D 좌표, 투영관련  (0) 2016.12.21
    포트폴리오 예상질문  (0) 2016.12.16
    디자인패턴 정리  (0) 2016.12.16
Designed by Tistory.