[Advanced C++] 18. goto, loop(for, while, do while), break & continue, halt(std::exit, atexit, quick_exit, abort, terminate)

dev.kelvin·2025년 1월 29일
1

Advanced C++

목록 보기
18/74
post-thumbnail

1. goto

goto

C++에서의 unconditional jump는 goto를 사용하여 구현한다

이때 unconditional jump는 if나 switch처럼 조건을 검사하지 않고 무조건 jump한다는 의미이다

switch의 case와 마찬가지로 goto의 label은 보통 들여쓰기를 하지 않는다

goto를 이용하여 원하는 label로 이동이 가능하다

    #include <cmath>


    int main()
    {
        double x{};
    tryAgain: //label
        std::cout << "Enter a non-negative number: ";
        std::cin >> x;

        if (x < 0.0)
            goto tryAgain; //goto

        std::cout << "The square root of " << x << " is " << std::sqrt(x) << '\n';
        return 0;
    }	

위 코드로 음수값이 x로 들어온다면 tryAgain이라는 label로 무조건 이동한다

이때 label은 { }의 범위(local)도 아니고 global 범위(파일 전체)도 아닌 함수 범위이다 이는 해당 함수 내부에서는 어디서든 이 label을 참조할 수 있다는 의미이다

	int main()
    {
        goto testState;

    testState:
        std::cout << "result" << std::endl;

        return 0;
    }

이렇게 goto가 label보다 먼저 호출되지만 testState는 함수 범위이기 때문에 참조가 가능하다
(전방선언 필요 없음)

goto는 단일 함수의 경계 내에서만 가능하다 (A함수에서 B함수로 점프가 불가능함, label이 함수 범위이기 때문이다)

또한 goto 뒤에서 변수를 초기화할 수 없다

    int main()
    {
        goto skip;
        int x{ 5 };
    skip:
        x += 3;
        return 0;
    }
    
    //error

이렇게 되면 goto로 인해 x variable이 초기화 및 선언이 되지 않을 수 있기 때문에 컴파일 에러가 발생한다, 단 미리 선언하고 할당은 가능하다

    int main()
    {
        int x{};
        goto skip;
        x = 10;
    skip:
        x += 3;
        return 0;
    }
    
    //pass

하지만 이러한 goto는 현대 C++에서의 사용을 절대 권장하지 않는다, 스파게티 코드의 주요 원인이 되기 때문이다 (goto를 통해 조건을 검사하지 않고 코드 이곳저것으로 이동하기 때문)

goto를 사용하는 대신 if나 switch를 이용하여 깔끔하게 코드를 작성하는것을 훨씬 권장한다

아주 드물게 사용하는 예는 함수 자체를 종료하지 않고 2중 for문을 빠져나갈때 사용한다 (이것도 조건 + break로 충분히 구현 가능)

    int main()
    {
        for (int i = 1; i < 5; ++i)
        {
            for (int j = 1; j < 5; ++j)
            {
                if (???)
                {
                    goto end;
                }
            }
        }

    end:
        std::cout << "Finish" << '\n';

        return 0;
    }	

2. while loop

loop

코드에서 특정 조건에 계속해서 반복되어야 하는 구조가 생기게 된다, 이럴때 다양한 loop를 사용하여 반복을 구현한다

아주 간단한 예시로 숫자를 0에서부터 100까지 console에 출력하고 싶을때 loop를 사용하지 않는다면 std::cout을 100번 작성해야 한다 (굉장히 비효율적이며 런타임 조건일 경우 사용할 수 없다)

while()

	while (조건)
    {
    	반복;
    }

while()의 조건이 true면 { }의 코드를 반복한다, false면 바로 반복을 빠져나간다

	int x{ 1 };
	while (x <= 100)
    {
    	std::cout << x << std::endl;
        
        ++x;
    }

이렇게 while()을 이용하여 간단하게 1~100까지 출력이 가능하다

infinite loop

반복문을 사용할 때 가장 주의해야할 점은 infinite loop이다, 이는 반복문의 조건이 항상 true라 프로그램이 종료될 때 까지 무한 반복을 하게 되는 현상이다 (크래시 발생함)

물론 의도적인 infinite loop를 이용하고 break, return, goto, exit로 원할 때 종료도 가능하다
(while (true))

loop variable

loop variable이란 반복이 실행되는 횟수를 제어하는데 사용되는 변수를 의미한다
ex) while (x <= 100) 에서 x

대부분 int 타입을 사용하지만 다른 타입도 충분히 가능하다

보통 loop variable의 변수명은 i,j,k를 일반적으로 많이 사용한다 (Fortran의 정수 변수 이름에서 따옴)

물론 개발자 성향에 따라 다르게 사용하지만 검색에 용이하게 하기 위해서 iii, jjj, kkk등으로 사용하는 경우도 있고 실제 의미가 있는 변수명을 사용하는 경우도 있다 (개인적으로 가장 권장하는 방식(count))

이런 loop variable들은 signed로 사용하는것이 좋다, unsigned로 해서 의도치 않은 동작이 발생할 수 있다 (예를들어 loop variable을 계속 --시켜 0일때 종료하는 반복문일 경우 unsigned하면 0 밑으로 내려가지 않기 때문에 infinite loop 발생 가능성이 존재한다)

if문과 마찬가지로 while문도 중첩이 가능하다

    int main()
    {
        int j{ 1 };

        while (j <= 5)
        {
            int i{ 1 };
            while (i <= j)
            {
                std::cout << i << " ";
                ++i;
            }

            std::cout << '\n';

            ++j;
        }

        return 0;
    }


3. do while

do while

do while은 조건이 false라도 최소 한번은 반드시 실행되는 반복문이다
(조건 검사가 맨 마지막에 있기 때문)

	do
    {
    	//명령
    }
    while (조건);

만약 조건이 true라면 다시 do while 맨 위로 올라가서 명령을 반복하게 된다

하지만 실무에서 거의 사용하지 않는다 (예상치 못한 동작 발생 가능, 가독성 저하로 유지보수가 어렵다)


4. for loop

for loop

C++에서 가장 많이 사용되는 반복문은 for loop이다 (loop variable을 쉽게 정의, 초기화, 변경할 수 있기 때문이다)

	for (초기식; 조건식; 제어식)
    {
    	명령;
    }

초기식에서 loop variable이 정의/초기화 되고 조건식에서 검사된다, 이때 true라면 명령을 실행하고 제어식으로 들어가 loop variable이 증가,감소와 같은 형식으로 제어되고 이를 조건식에서 false가 나올때까지 반복한다
(초기식은 1번만 실행됨)

초기식에서 정의 및 초기화 된 loop variable은 해당 for loop의 { }에서만 유효하다

	for (int i{ 1 }; i <= 5; ++i)
    {
    	std::cout << i << std::endl;
    }

물론 제어식에는 ++뿐 아니라 --도 가능하고 다른 연산자도 가능하다

    for (int i{ 1 }; i <= 5; i += 2)
    {
        std::cout << i << std::endl;
    }

이때 for loop의 조건식에서 operator!=를 사용하는것을 지양한다 (해당 조건을 넘어갔을 때 infinite loop이 발생할 수 있다)

	for (int i { 0 }; i < 10; ++i)
    {
         std::cout << i;
         if (i == 9) ++i; // jump over value 10
    }

    for (int i { 0 }; i != 10; ++i) // uses !=, infinite loop
    {
         std::cout << i;
         if (i == 9) ++i; // jump over value 10
    }

가능한 operator<, operator<=를 사용하는것을 권장한다

for loop에서의 생략

for에서 초기식, 제어식을 생략해서 사용하는 경우가 있다

	int i{ 0 };
    
	for( ; i < 10 ; )
    {
    	std::cout << i << std::endl;
        
        ++i;
    }
    
    //0 1 2 3 4 5 6 7 8 9 10

이렇게 비워두고 ;를 사용한다면 생략이 가능하다 (loop variable을 따로 정의하지 않거나 제어를 따로 해주고 싶다면 사용하자)

초기식, 제어식, 조건식 전부 ;로 생략한다면 infinite loop이 된다

	for(;;)
    {
    	//infinite loop
    }

for loop의 초기식 및 제어식에는 여러 loop variable이 ,로 구분되어 정의 및 초기화, 제어될 수 있다

	for (int x{ 0 }, y { 9 }; x < 10; ++x, --y)
    {
    	
    }

조건식이나 while과 마찬가지로 for loop도 중첩이 가능하다

	for (int i = 0; i < 5; i++)
	{
		for (int j = 0; j < 10; j++)
		{
			std::cout << j << std::endl;
		}
	}

for loop에 사용되는 loop variable을 미리 생성하고 for loop에서 사용하는건 지양한다

    int i {}; 
    for (i = 0; i < 10; ++i)
    {
        std::cout << i << ' ';
    }

해당 loop variable이 어디에서 생성되고 어디에서 사용되는지 확인해야 하기 때문에 프로그램이 복잡해지며 최적화에 나쁜 영향을 줄 수 있고 의도치 않은 동작이 발생할 수 있다

따라서 loop 내부에서만 사용되는 변수는 loop 내부에서 정의하는걸 권장한다

명확한 loop variable이 있는 경우에는 for loop, 그렇지 않은 경우에는 while을 보통 사용한다


5. break & continue

break

switch에서 break를 사용하게 되면 그 즉시 switch문을 빠져나가게 된다(fall through 방지), 이는 while, do while, for문에도 동일하게 동작한다

반복문에서 break를 사용하게 되면 그 즉시 반복을 중단하고 다음 코드로 넘어가게 된다 (infinite loop에서도 가능)

	while (i < 5)
    {
    	if (i == 3)
        {
        	break;
        }
    }    

break와 return

break는 switch나 loop를 즉시 종료하고 그 다음 코드를 실행하지만 return은 함수를 종료하고 해당 함수가 호출된 이후의 코드를 실행하게 된다

continue

loop에서 continue를 만나게 되면 loop의 최상위로 다시 올라가서 반복이 진행된다 (loop의 맨 위로 jump하는 방식임)

	for (int count{ 0 }; count < 10; ++count)
    {
        if ((count % 4) == 0)
            continue; // go to next iteration

        std::cout << count << '\n';
    }
    
    //1 2 3 5 6 7 9

이렇게 4로 나누었을때 나머지가 0이면 console 출력을 하지 않고 for loop의 맨 위로 다시 올라가게 된다
(for문의 제어식은 continue가 되어도 계속 호출된다)

제어식이 따로 없는 while()같은 경우에는 continue를 잘못 사용하게 되면 infinite loop이 발생할 수 있으니 조심해야 한다 (loop variable을 제어하는 코드를 jump하게 되는 경우)

break, continue를 사용하여 loop variable을 줄일 수 있다(loop logic을 단순화 할 수 있다), 하지만 명확하게 사용하지 않으면 오히려 로직읠 따라가기 힘들 수 있으니 잘 사용해야 한다

	while (keepLooping)
    {
    	if ()
        {
        	keepLooping = false;
        }
    }
    
    while (true)
    {
    	if ()
        {
        	break;
        }
    }

early return

함수의 맨 마지막에서 return을 하는것이 아닌 함수를 조기 종료시키는것을 early return이라고 한다

이는 조건의 블록 중첩을 방지하는데 효과적이며 코드 가독성이 향상된다

	if (!Knight)
    {
    	return;
    }
    
    Knight->Attack();

5. std::exit()

std::exit()

C++에서 halt명령은 std::exit()로 처리한다

std::exit()은 프로그램의 종료 요청이다

정상/비정상 종료 상태는 std::exit()의 인자로 넘어가게 된다

std::exit()은 프로그램을 종료할때 다음과 같은 작업을 수행한다

  • global변수, static 변수들의 소멸자가 호출된다
  • 열린 파일들이 닫힘
  • OS로 정상, 비정상 종료 상태 전달

이때 stack unwinding을 수행하지 않아 local variable의 소멸자는 호출되지 않는다

	#include <cstdlib>
    
    std::exit(0 or 1); //0은 정상종료, 1은 비정상종료 

std::exit()의 주의할 점

std::exit()은 local variable를 스스로 소멸시키지 않는다 (할당한 메모리 해제, 파일 처리, 네트워크 연결 해제 등)

    class Test 
    {
    public:
        Test() { std::cout << "객체 생성됨\n"; }
        ~Test() { std::cout << "객체 소멸됨\n"; }
    };

    void func() 
    {
        Test localObj;  // 지역 변수
        std::cout << "함수 실행 중...\n";
        std::exit(0);   // 프로그램 즉시 종료 (지역 변수 소멸자 호출되지 않음)
    }

    int main() 
    {
        func();
        std::cout << "이 코드는 실행되지 않음.\n";
        return 0;
    }
    
    //객체 생성됨
    //함수 실행중...
    //소멸은 되지 않는다!
    
    //이때 Test클래스 타입 객체를 global로 선언한다면 소멸자가 호출된다

물론 최신 OS는 종료 시 메모리를 정리하게 된다, 하지만 할당한 메모리를 수동으로 해제하는 습관을 들여야 한다 (메모리 누수 방지, 디버깅 등)

따라서 std::exit() 시 정리 작업을 위한 함수를 할당해야 한다

std::atexit();

std::atexit()으로 std::exit() 시 정리할 함수를 할당하여 호출할 수 있다

	class Test 
    {
    public:
        Test() { std::cout << "객체 생성됨\n"; }
        ~Test() { std::cout << "객체 소멸됨\n"; }
    };

    void func() 
    {
        Test localObj;  // 지역 변수
        std::cout << "함수 실행 중...\n";
        std::exit(0);   // 프로그램 즉시 종료 (지역 변수 소멸자 호출되지 않음)
    }

    void cleanUp()
    {
        std::cout << "객체 소멸됨\n";
    }

    int main() 
    {
        std::atexit(cleanUp);
        func();
        std::cout << "이 코드는 실행되지 않음.\n";
        return 0;
    }

std::atexit()에 등록되는 함수는 매개변수가 없어야 하며 반환값이 없어야 한다, 여러개의 함수를 등록할 경우 LIFO로 역순으로 호출된다

	std::atexit(cleanup1);
    std::atexit(cleanup2);
    std::atexit(cleanup3);
    
    //cleanup3,2,1순으로 호출됨

멀티스레드 환경에서의 std::exit()은 의도치 않은 동작을 발생시킬 수 있다

왜냐하면 std::exit()은 정적 객체들을 정리하게 되고 멀티스레드 환경에서는 이 정적 객체들을 사용하고 있을 수 있기 때문이다 (exit 후 해당 정적 객체 사용 시 crash)

따라서 멀티스레드 환경에서는 std::quick_exit()을 사용한다

std::quick_exit()은 정적 객체를 정리하지 않고 std::at_quick_exit()으로 정리할 함수를 등록하여 사용이 가능하다

    class Test 
    {
    public:
        Test() { std::cout << "객체 생성됨\n"; }
        ~Test() { std::cout << "객체 소멸됨\n"; }
    };

    void func() 
    {
        std::cout << "함수 실행 중...\n";
        std::quick_exit(0);   // 프로그램 즉시 종료 (static 객체의 소멸자 호출되지 않음)
    }

    Test temp;

    int main() 
    {
        func();
        std::cout << "이 코드는 실행되지 않음.\n";
        return 0;
    }
    
    //정적 객체인 temp는 소멸되지 않는다 (std::exit()으로 종료하면 소멸자 호출됨)

std::abort, std::terminate

이 두 함수도 프로그램 종료 함수이다

std::abort는 프로그램의 비정상적 종료를 의미한다 (runtime에러로 인해 프로그램을 지속 실행할 수 없는 경우 ex) 0으로 나누기 crash)

std::abort()는 정적 객체 정리가 되지 않고 std::atexit()도 실행하지 않는다 (심각한 오류일때 사요됨)

std::terminate()는 예외 처리가 되지 않을때 자동으로 호출되며 내부에서 std::abort()를 호출한다
(throw했지만 catch가 없을 때)

std::terminate()도 마찬가지로 정적 객체 소멸이 되지 않고 std::atexit()를 실행하지 않는다 (예외처리가 되지 않을때 호출됨)

사실 이러한 종료를 명시적으로 사용하는것 자체가 그리 좋은 방법은 아니다 (local variable을 정리하지 않기 때문)

잘 설계된 프로그램이라면 언제든지 종료되도 아무런 문제가 없어야 한다

profile
GameDeveloper🎮 Dev C++, DataStructure, Algorithm, UE5, Assembly🛠, Git/Perforce🌏

0개의 댓글