2019년 12월 20일 금요일

GIT 과 SVN의 차이점

GIT 과 SVN 을 이해하려면 먼저 형상관리(버전관리)에 대해 이해해야 한다.
프로젝트를 진행할 때 각자 업무를 나눠서 맡은 부분을 개발을 하게 되는데,
각자가 개발한 코드 혹은 문서들을 하나의 관리 도구에서 통합적으로, 버전별로 관리하게 되는 것을
형상관리(Configuration Management) 혹은 버전관리(Version Management)라고 부른다.
형상관리, 버전관리, 변경관리 등 용어는 다양하지만
실무에서 사용하는 의미는 거의 비슷하기 때문에 구분하지 않고 사용하는 경우가 많다.

형상관리 방식에는 크게 중앙집중식분산관리식 으로 나뉘는데
대표적으로 사용되는 도구가
중앙집중관리식의 SVN 그리고 분산관리식의 GIT 이다.
SVN 은 내 로컬PC 에서 Commit을 하면 바로 중앙저장소에 반영이 되는 반면
GIT 은 내 로컬PC에서 Commit을 하면 로컬 저장소에 반영이 되고 로컬저장소에서 Push를 하면
원격저장소에 반영이 된다.

SVN최대의 장점은 직관적이다.
모든 사람이 중앙서버에 있는 같은 자료를 받아오고
내가 Commit을 하는 순간 모든 사람에게 공유가 된다.
이러한 방식의 단점은 만약 두 사람이 하나의 파일을 동시에 수정하고 커밋하였을 때
충돌이 일어날 확률이 높다는 얘기이다.

반면 GIT은 직관적이지 못하고 적응하는데에 시간이 필요하다.
내가 한 작업물을 원격저장소에 올리려면
우선 로컬PC 에서 작업내용을 Commit 하여 로컬 저장소에 반영한 후,
원격저장소에서 fetch로 로컬저장소로 마스터 파일을 받아와서
충돌이 나지 않게 merge를 이용하여 합친 다음,
로컬 저장소의 내용을 Push 하여 원격저장소에 올리면 그때 다른 사람들에게 나의 작업 내용이 공유가 된다.

그러나 GIT 의 장점은
모든 작업이 로컬에서 이루어지고 네트워크 사용은 원격 저장소로 저장할 때 한 번 이루어지므로
개발 시 처리속도가 빠르고,
웹 상에 저장소를 둘 수 있기 때문에
언제 어디서나 협업을 할 수 있고(이건 단점일수도…),
여러 사람이 가지가 뻗어나가듯 자신의 작업을 하기 때문에 이를 브랜치 라고 하는데,
브랜치와 머지의 상호작용을 잘 할 수 있도록 편의성을 잘 제공해주기 때문에 협업이 더욱 더 용이해진다.
그리고 원격저장소의 내용이 모든 협업자들의 로컬 저장소에 저장이 되어 있으므로
중앙저장소에 에러가 생기면 모든 작업이 마비되는 SVN 과 다르게
원격저장소에 에러가 생겨도 로컬에서 복구하기 용이하고,
히스토리 관리가 잘 제공되어 있어 히스토리 관리가 용이하다.


2019년 12월 17일 화요일

전위 증감 연산자(++i 와 i++)의 차이점

for 루프를 사용하다보면 자주 사용하게 되는 변수 i
이 i 변수에 1을 더하는 i++와 ++i는 서로 차이가 있다.
결론부터 말하면 ++i가 더 성능이 좋다고 할 수 있다.


Ex Code )

#include <stdio.h>
#include <time.h>

double be_test() {    
    clock_t start, end;
    int i,j;
    int sum = 0;
    start = clock(); //시간 측정 시작
    
    for (i = 0; i < 100000; ++i) {
        for (j = 0; j < 10000; ++j) {
            sum += i * j;
        }
    }
    end = clock(); //시간 측정 끝
    return (double)(end - start);
}


double af_test() {    
    clock_t start, end;
    int i,j;
    int sum = 0;
    start = clock(); //시간 측정 시작
    
    for (i = 0; i < 100000; i++) {
        for (j = 0; j < 10000; j++) {
            sum += i * j;
        }
    }
    end = clock(); //시간 측정 끝
    return (double)(end - start);

}

int main() {

    printf("전위 증감 연산 시간: %f", bf_test()); //결과 출력
printf("후위 증감 연산 시간: %f", af_test()); //결과 출력 return 0;
}

결과)


"전위 증감 연산 시간 : 22.000000"  (5번 정도 실행해본 결과 22초~ 23초)
"후위 증감 연산 시간 : 24.000000"  (5번 정도 실행해본 결과 24초~ 25초)


++i  :  내부적으로 다음과 같이 동작한다.

1. i의 값을 1 더한다.
2. i의 값을 반환한다.


i++ :  내부적으로 다음과 같이 동작한다.

1. i의 현재 값을 보관한다. (현재 실행되는 명령문에서는 이 보관된 값이 사용되어야 하니까)
2. i의 값을 1 더한다.
3. 보관했던 값을 반환한다.


2동작을 수행하는 것과 3동작을 수행하는 것(i++은 ++i에 보관하는 과정이 추가된 것이므로)은 많은 경우 성능 차이를 가져온다.

i++와 ++i는 현재 행에서 사용되는 값이 원래의 값을 사용하는가 1이 더해진 값을 사용하는가의 차이도 존재하지만
for(i = 0; i < 10; i++)에서와 같이 단독으로도 충분히 사용된다. 이러한 경우 i++보다는 ++i를 사용하는 것이 성능상 더 좋다고 할 수 있다.

물론, 똑똑한 컴파일러는 최적화를 수행해준다. i++로 코드를 작성했다고 하더라도 실행에 영향이 없는 내용은 ++i로 바꿔서 컴파일한다.

하지만! C++에서 연산자 오버로딩을 해서 전위, 후위 ++연산자를 직접 만든 경우에는 컴파일러가 최적화를 수행해 줄 수 없다.
때문에 이 경우 성능차이가 나게된다.
마찬가지로 C++ STL에서 vector, list 등을 사용할때 iterator를 전위 ++ 연산자로 증가시키면 성능상의 이점을 기대할 수 있다.

출처: https://iwantadmin.tistory.com/114 [실수로포스팅해버렸다.]

2019년 12월 4일 수요일

ue4 - 데디케이티드 서버(dedicated server tutorial)

  • 신고

  • 0. 프로젝트 소스가 없거나 다른 엔진 버전으로 작성되었거나 단순히 Blueprint 만으로 시작한 경우 일부 C++ 코드를 추가할 때까지 전용서버를 만들 수 없음


    0-1. 소스 디렉토리가 있으면 .uproject 파일을 마우스 우클릭 후 프로젝트 파일 생성


    0-2. 해당 포스팅은 4.14~17까지를 범위로 보며, 필자는 4.16.3 버전을 기준으로 작성


    1. Unreal Engine GitHub(https://github.com/EpicGames/UnrealEngine)에서 원하는 버전의 엔진 다운로드


    2. 엔진 폴더에서 [Setup.bat] 실행


    3. 이상 없이 종료될 경우 [GenerateProjectFiles.bat] 실행


    3-1. 만약 Visual Studio 2017을 사용할 경우 CMD 프롬프트 창을 이용해 해당 경로로 이동 후 [GenerateProjectFiles.bat -2017] 명령 실행


    4. 이상 없이 종료될 경우 [UE4.sln] 실행


    5. 상단 메뉴의 드랍 박스에서 솔루션 구성을 [Development Editor]로 선택


    6. UE4 솔루션을 시작 솔루션으로 설정하고 실행하여 빌드


    7. 빌드가 정상적으로 완료되어 에디터 창이 생성될 경우 셰이더 컴파일링이 끝날 때까지 대기


    8. [New Project] > [C++] > [Third Person] > [With Starter Content] > [Create Project]


    9. 생성과 로드가 완전히 끝나면 프로젝트 종료


    10. [(ProjectPath)/Source/] 경로에서 [(ProjectName)Editor.Target.cs] 파일을 같은 경로에 복사 후 [(ProjectName)Server.Target.cs]로 이름 변경


    10-1. 괄호 안의 내용은 프로젝트를 만들 때 기입되는 내용이기 때문에 상대적이며, 이후 내용들도 마찬가지


    11. [(ProjectName)Server.Target.cs] 편집


    12. 내용을 전부 지우고 엔진 버전에 따라 다음과 같이 입력


    12-1. 아래 내용은 4.14 버전에 해당
    ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    // Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
    using UnrealBuildTool;
    using System.Collections.Generic;
    public class (ProjectName)ServerTarget : TargetRules
    {
        public (ProjectName)ServerTarget(TargetInfo Target)
        {
            Type = TargetType.Server;
            bUsesSteam = true;
        }
        //
        // TargetRules interface.
        //
        public override bool GetSupportedPlatforms(ref List<UnrealTargetPlatform> OutPlatforms)
        {
            // It is valid for only server platforms
            return UnrealBuildTool.UnrealBuildTool.GetAllServerPlatforms(ref OutPlatforms, false);
        }
        public override void SetupBinaries
        (
             TargetInfo Target, ref List<UEBuildBinaryConfiguration> OutBuildBinaryConfigurations,
             ref List<string> OutExtraModuleNames
        )
        {
            OutExtraModuleNames.Add("Test");
        }
    }
    cs
    ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


    12-2. 아래 내용은 4.15 버전에 해당
    ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    // Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
    using UnrealBuildTool;
    using System.Collections.Generic;
    [SupportedPlatforms(UnrealPlatformClass.Server)]
    public class (ProjectName)ServerTarget : TargetRules      // Change this line of code as shown in the previous steps
    {
        public (ProjectName)ServerTarget(TargetInfo Target)  // Change this line of code as shown in the previous steps
        {
            Type = TargetType.Server;
            bUsesSteam = false;
        }
        //
        // TargetRules interface.
        //
        public override void SetupBinaries
        (
            TargetInfo Target,
            ref List<UEBuildBinaryConfiguration> OutBuildBinaryConfigurations,
            ref List<string> OutExtraModuleNames
        )
        {
            OutExtraModuleNames.Add("Test");     // Change this line of code as shown in the previous steps
        }
    }
    cs
    ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


    12-3. 아래 내용은 4.16 버전에 해당
    ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
    using UnrealBuildTool;
    using System.Collections.Generic;
    [SupportedPlatforms(UnrealPlatformClass.Server)]
    public class (ProjectName)ServerTarget : TargetRules    // Change this line of code as shown previously
    {
        public (ProjectName)ServerTarget(TargetInfo Target) : base(Target) // Change this line of code as shown previously
        {
            Type = TargetType.Server;
            ExtraModuleNames.Add("Test");   // Change this line of code as shown previously
        }
    }
    cs
    ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


    12-4. 아래 내용은 4.17 버전에 해당
    ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
    using UnrealBuildTool;
    using System.Collections.Generic;
    [SupportedPlatforms(UnrealPlatformClass.Server)]
    public class (ProjectName)ServerTarget : TargetRules   // Change this line as shown previously
    {
        public (ProjectName)ServerTarget(TargetInfo Target) : base(Target)  // Change this line as shown previously
        {
            Type = TargetType.Server;
            ExtraModuleNames.Add("Test");    // Change this line as shown previously
        }
    }
    cs

    ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


    13. [(ProjectPath)/] 경로에서 [(ProjectName).uproject]를 마우스 우클릭 후 [Switch Unreal Engine version] 선택


    14. 드롭 박스에서 소스 빌드 버전을 선택하고 [OK] 버튼 클릭


    14-1. 만약 이미 소스 빌드 버전이 선택되어 있다면 다른 엔진 버전으로 switch 한 뒤 다시 소스 빌드 버전으로 switch


    14-2. 이것을 하지 않을 경우 솔루션 구성을 Development Server로 하더라도 서버 바이너리 파일이 생성되지 않음 (이유 불명확)


    15. [(ProjectPath)/] 경로에서 [(ProjectName).sln] 실행


    16. 상단 메뉴의 드롭 박스에서 [Development Editor] 선택 후 (ProjectName) 솔루션을 선택하여 빌드


    17. 빌드가 정상적으로 종료될 경우 상단 메뉴의 드롭 박스에서 [Development Server] 선택 후 (ProjectName) 솔루션을 선택하여 빌드


    18. 빌드가 정상적으로 종료될 경우 [(ProjectPath)/] 경로로 돌아가 [binaries/win64/] 경로에 제대로 [(ProjectName)Server] 라는 이름의 서버 바이너리가 존재하는지 확인


    18-1. 서버 바이너리가 없다면 위의 내용을 다시 살펴보고 넘어간 부분이 있는지 체크


    19. 만약 광원을 구축할 때 소스가 비현실적으로 구축되면 [Lighting build failed. Swarm failed to kick off.]라는 오류가 출력되는데, 이 경우 프로젝트 솔루션을 열고 [UnrealLightmass] 솔루션을 선택하여 빌드하는 것으로 해결


    20. [(ProjectPath)/] 경로에서 [(ProjectName).uproject] 실행


    21. 다음과 같은 폴더 3개를 루트 경로에 생성


    21-1. [Blueprints], [Blueprints/Widgets], [Maps]


    22. [Game/ThirdPersonCPP/Maps] 안에 있는 [ThirdPersonExampleMap], [ThirdPersonExampleMap_BuiltData]를 선택하여 [Game/Maps]로 이동 후 [TestLevel]로 이름 변경


    23. 상단 메뉴의 [파일] > [새 레벨] > [공백 레벨] > [EntryMap], [TransitionMap]라는 이름의 2개의 공백 레벨 생성


    24. [EntryMap]을 열고 에디터 상단 메뉴의 [Blueprints] 선택 후 드랍 박스에서 [Open Level Blueprint] 선택


    25. [Open Level] 노드를 생성해 Level Name을 [127.0.0.1]로 설정하고 [Event BeginePlay] 노드와 연결한 뒤 컴파일 후 저장


    26. [Game/Blueprints/Widgets/] 경로에서 Content Browser에서 마우스 우클릭 후 [User Interface] > [Widget Blueprint]를 선택하여 생성


    27. Text를 하나 Widget에 배치하고 컴파일 후 저장


    28. [TransitionMap]을 열고 에디터 상단 메뉴의 [Blueprints] 선택 후 드랍 박스에서 [Open Level Blueprint] 선택


    29. [Create Widget] 노드를 생성 후 26번에서 생성한 Widget Blueprint를 선택한 뒤 [Event BeginePlay] 노드와 연결


    30. [Create Widget]의 Owning Player를 [Get Player Controller] 노드와 연결 (플레이어 인덱스 : 0)


    31. [Add to Viewport] 노드를 생성하여 [Create Widget] 노드와 연결 후 Target을 [Create Widget] 노드의 Return Value와 연결


    32. [TextLevel]을 열고 플레이어 캐릭터 삭제 후 저장


    33. 에디터로 돌아가 상단 메뉴의 [편집] > [맵 & 모드] 선택


    34. [Default Maps] 탭에서 [Editor Startup Map]을 [EntryMap]으로, [Game Default Map]을 [EntryMap]으로, [Transition Map]을 [TransitionMap]으로, [Server Default Map]을 [TestLevel]로 설정


    35. 에디터로 돌아가 상단 메뉴의 [파일] > [패키지 프로젝트] > [패키징 세팅] 선택


    36. [Packaging] 탭에서 [고급 표시]를 선택하여 탭을 확장하고, [List of maps to include in a packaged build]에 + 버튼을 3번 눌러 3개의 앨리먼트를 추가한 뒤 [0]에는 [/Game/Maps/EntryMap], [1]에는 [/Game/Maps/TestLevel], [2]에는 [/Game/Maps/TransitionMap]을 선택


    37. 에디터로 돌아가 상단 메뉴의 [파일] > [패키지 프로젝트] > [윈도우] > [윈도우(64-bit)] 선택


    38. [(ProjectPath)/Binaries/Win64/] 경로에서 [(ProjectName)Server.exe]을 복사하고 [WindowsNoEditor/(ProjectName)/Binaries/Win64/] 경로에 붙여넣기


    39. 복사한 파일의 바로가기를 생성하여 이름을 [(ProjectName)Server.exe - TestLevel]로 변경 후 마우스 우클릭하여 [속성] 선택


    40. [대상] 탭 내용의 마지막에 [ -log] 추가 후 확인을 선택하고 바로가기 실행


    41. [(ProjectName).exe]를 여러 번 실행하여 제대로 실행할 때마다 타 플레이어가 참가되는지 확인


    -> 친구가 게임에 인터넷을 통해 참여할 수 있도록 허용하려면 [EntryMap]의 레벨 블루프린트에서 로컬 IP 주소 대신 실제 공개 IP 주소를 입력


    - 만약 친구가 게임에 인터넷을 통해 참여할 수 없다면 포트가 전달되지 않고 라우터에서 제대로 설정되지 않아 고정 IP 주소를 설정해야 할 수 있음


    -> 4.4.3에서 시뮬레이트 된 물리에 따라 동작이 달라지는 액터를 복제하는 경우 SkeletalMeshComponent.bEnablePhysicsOnDedicatedServer 속성을 true로 설정


    - 독립 실행형 전용 서버는 IsRunningDedicatedServer()에 true를 반환하기 때문에 명시적으로 bEnablePhysicsOnDedicatedServer 속성을 설정하지 않은 SkeletalMeshComponents에 대한 물리 시뮬레이션을 방지 가능



    출처 : https://m.blog.naver.com/ratoa/221096604157

    2019년 12월 1일 일요일

    c++ - 생성자 상속, 상속 지정




    ●생성자 상속
    클래스를 상속한 전제에서 생성자도 상속이 가능하다.
    생성자 상속은 따로 선언을 해줘야한다.

    Ex)
    B(int x, int y) : A(x+5)
    C(int x, int y, int z) : B(x,y)


    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    #include <iostream>

    using namespace std;

    class A {

     int x;

    public:
      A() {
        x = 0;
    }

      A(int x) {
         this->x = x;
         cout << x << endl;
      }
    };

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    class B : public A {

    int y;


    public:

         B(int x, int y) : A(x+5) // A 생성자 상속
        {
            this->y = y;
            cout << y << endl;
        }
    };

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


    class C : public B {


    int m;

    public:

        C(int x, int y, int z) : B(x,y) //B 생성자 상속
       {
            m = x*y*z; cout << m << endl;
        }
    };

    int main()
    {
        C c(3,5,2);
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////






    ◆실행결과
    8
    5
    30




    ※실행과정여기서 재밌는 점은 main 에서 C class의 객체 c를 생성 인자는 (3,5,2)를 받게 된다.m = 3*5*2, m 출력상속한 생성자를 실행 B(x,y) 호출,y = 2, y 출력B(x,y)가 상속한 생성자 A(x+5) 호출,x = 3 +5, x 출력


    ※헷갈리기 쉬운건 생성자 뒤에 아무것도 설정 안해준다면 기본 생성자가 호출된다.
    ex code)

    class A{
        A()
       {
            cout << "a 생성자" << endl;
       } 

        A( int x )
       {
            cout << "a 생성자 : " <<  x << endl;
       }
    };


    class B : class A{
       B()
      {
           cout << "b 생성자" << endl;
      }

       B( int x )
       {
            cout << "b 생성자 : " <<  x << endl;
       }

    };

    int main()
    {
      B b(1);
    }




    ◆실행결과
    a 생성자
    b 생성자 : 1



    ●상속 지정(public 상속, private 상속, protected 상속)

    상속 선언시 public, private, protected의 3가지 중 하나 지정기본 클래스의 멤버의 접근 속성을 어떻게 계승할지 지정

    public : 기본 클래스의 protected, public 멤버 속성을 그대로 계승
    private는 private로 public은 public으로 계승

    class Derived : public Base
    {
         //anything
    }



    출처 : https://m.blog.naver.com/cory_kim/220860897856

    스택과 힙 그리고 Stack overflow(메모리 관련)

    스택과 힙 (Stack and Heap)

    프로그램이 사용하는 메모리는 일반적으로 세그먼트(segment)라고 하는 몇 가지 다른 영역으로 나뉜다.
    • 코드 세그먼트 : 컴파일된 프로그램이 저장되는 영역, 일반적으로 read-only 속성이다.
    • 데이터 세그먼트 : 전역 변수 및 정적 변수가 저장되는 영역
    • 힙 세그먼트 : 동적으로 할당된 변수가 할당되는 영역
    • 스택 세그먼트 : 함수 매개 변수, 지역 변수 및 기타 함수 관련 정보가 저장되는 영역

    힙 세그먼트 (Heap segment)

    힙 세그먼트는 동적 메모리 할당에 사용되는 메모리를 추적한다. C++에서 new 연산자를 사용해서 메모리를 할당하면 이 메모리는 응용 프로그램의 힙 세그먼트에 할당된다.
    int* ptr   = new int; //   ptr은 힙에서 4바이트로 할당된다.
    int* array = new int[10]; // array는 힙에서 40바이트로 할당된다.
    
    이 메모리의 주소는 연산자 new에 의해 포인터에 저장될 수 있다. 주의할 점은 이 메모리 요청이 순차적 메모리 주소를 할당하는 결과가 아닐 수 있다는 것이다.
    int* ptr1 = new int;
    int* ptr2 = new int;
    // ptr1과 ptr2은 순차적인 주소가 아닐 수 있다.
    
    동적으로 할당된 변수가 삭제되면 메모리는 힙으로 "반환"되고, 이후 다시 할당될 수 있다. 포인터를 삭제하면 변수가 삭제되는 것이 아니라 관련 주소의 메모리를 운영 체제에 반환하는 것이다.
    힙에는 장단점이 있다.
    • 힙에 메모리를 할당하는 것은 비교적 느리다.
    • 할당된 메모리는 명시적으로 할당 해제하거나 응용 프로그램이 종료될 때까지 유지된다. (메모리 릭 주의)
    • 동적으로 할당된 메모리는 포인터를 통해 접근한다: 포인터를 역참조하는 것은 변수에 직접 접근하는 것보다 느리다.
    • 힙은 큰 메모리 풀이므로 큰 배열, 구조체 또는 클래스를 할당할 수 있다.

    스택 세그먼트 (Stack segment)

    스택 세그먼트(=콜 스택)는 메인() 함수부터 현재 실행 지점까지의 모든 활성 함수를 추적하고 모든 함수 매개 변수와 지역 변수의 할당을 처리한다.
    스택은 후입선출(LIFO) 자료구조다. 즉, 가장 늦게 들어간 자료를 가장 먼저 꺼내게 된다. 함수 호출이 끝나고, 이전 함수로 돌아갈 때 이 함수의 바로 이전 함수로 돌아가야 한다. 그래서 컴퓨터는 내부적으로 스택 세그먼트를 스택 자료구조로 구현한다.
    콜 스택(call stack) 이란 컴퓨터 프로그램에서 현재 실행 중인 서브루틴(함수)에 관한 정보를 저장하는 스택 자료구조이다. -위키백과-
    응용프로그램이 시작되면 메인() 함수가 운영체제에 의해 호출 스택에 푸시된다. 그 후 프로그램이 실행되기 시작한다. 함수호출이 발생하면 함수가 콜 스택에 푸시된다. 현재 함수가 끝나면 해당 함수는 콜 스택에서 해제된다. 따라서 콜 스택에 푸시된 함수를 살펴보면 현재 실행 지점으로 이동하기 위해 호출된 모든 함수를 볼 수 있다.
    콜 스택 자체는 고정된 크기의 메모리 영역이다. 여기서 콜 스택에 넣고 빼는 데이터 자체를 스택 프레임(stack frame)이라고 한다. 스택 프레임은 하나의 함수 호출과 관련된 모든 데이터를 추적한다. 또한 스택 포인터(Stack Pointer)라고 하는 CPU의 작은 조각인 레지스터는 현재 호출 스택의 최상위 위치를 가리킨다.

    - The call stack in action

    콜 스택이 어떻게 작동하는지 좀 더 자세히 살펴보자. 함수를 호출할 때 발생하는 단계는 다음과 같다.
    1. 프로그램에 함수 호출이 발생한다.
    2. 스택 프레임이 생성되고 콜 스택에 푸시된다. 스택 프레임은 다음과 같이 구성된다:
      • 함수가 종료되면 복귀할 주소
      • 함수의 모든 매개 변수
      • 지역 변수
      • 함수가 반환할 때 복원해야 하는 수정된 레지스터의 복사본
    3. CPU가 함수의 시작점으로 점프한다.
    4. 함수 내부의 명령어를 실행한다.
    함수가 종료되면 다음 단계가 수행된다:
    1. 레지스터가 콜 스택에서 복원된다.
    2. 스택 프레임이 콜 스택에서 튀어나온다. 이렇게 하면 모든 지역 변수와 매개 변수에 대한 메모리가 해제된다.
    3. 반환 값이 처리된다.
    4. CPU는 반환 주소에서 실행을 재개한다.
    반환 값은 시스템 아키텍처에 따라 여러 가지 방법으로 처리한다. 반환 값이 스택 프레임의 일부로 포함되기도 하며, CPU 레지스터를 사용하기도 한다.
    일반적으로 콜 스택이 어떻게 동작하는지에 대한 모든 세부 사항이 중요하지는 않다. 그러나 함수가 호출될 때와 종료될 때 함수가 콜 스택에서 효과적으로 작동한다는 것을 이해하면 재귀를 이해할 때와 디버깅할 때 유용하다.

    스택 오버플로 (Stack overflow)

    스택 세그먼트는 크기가 제한되어 있으므로 제한된 양의 데이터만 저장할 수 있다. window 운영체제에서 기본 스택 세그먼트의 크기는 1MB다. 응용 프로그램이 스택 세그먼트에 너무 많은 정보를 넣으려고 하면 스택 오버플로(stack overflow)가 발생한다. 스택 오버플로(Stack overflow)는 스택 세그먼트의 모든 메모리가 할당되어 꽉 찼을 때 발생하며, 이 경우 추가 할당이 메모리의 다른 섹션으로 넘치기 시작한다.
    스택 오버플로는 일반적으로 스택 세그먼트에 너무 많은 변수를 할당하거나 중첩된 함수 호출(A calls function B calls function C calls funct D...)을 너무 많이 한 결과다. 보통 스택 오버플로가 발생하면 프로그램이 다운된다.
    다음은 스택 오버플로를 일으킬 수 있는 예제 프로그램이다.
    int main()
    {
        int stack[100000000];
        return 0;
    }
    
    위 프로그램은 스택에 거대한 크기의 배열을 할당하려고 시도한다. 그러나 스택 세그먼트 크기가 배열을 처리할 만큼 충분히 크지 않으므로 배열 할당은 응용 프로그램에서 사용할 수 없는 메모리 부분까지 오버플로 된다. 따라서 프로그램이 다운된다.
    다음은 다른 이유로 스택 오버플로를 발생시키는 또 다른 예제 프로그램이다.
    void foo()
    {
        foo();
    }
    
    int main()
    {
        foo();
    
        return 0;
    }
    
    위 프로그램은 함수 foo()가 호출 될 때마다 스택 프레임이 스택 세그먼트에 푸시된다. foo() 함수는 무한히 호출되기 때문에 결국에는 스택 메모리가 꽉 차서 스택 오버플로가 발생한다.
    • 스택에 메모리를 할당하는 것은 비교적 빠르다.
    • 스택에 할당된 메모리는 스택 범위에 있을 때만 접근할 수 있다.
    • 스택에 할당된 모든 메모리는 컴파일 타임에 알려진다. 메모리는 변수를 통해 직접 접근할 수 있다.
    • 스택은 비교적 크기가 작으므로 스택 공간을 많이 차지하는 지역 변수를 만드는 것은 좋지 않다.


    출처: https://boycoding.tistory.com/235 [소년코딩]