🚀 들어가며
아래 링크는 C 언어로 코루틴을 구현하는 아주 유명한 글입니다.
https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html
처음 보면 굉장히 충격적입니다.
왜냐하면 C 언어에는 coroutine 문법이 없고, yield도 없고, async/await도 없는데
코루틴처럼 동작하는 구조를 만들어내기 때문입니다.
게다가 스레드도 사용하지 않고, OS context switching도 없고, 복잡한 라이브러리도 사용하지 않습니다.
그런데도 함수가 중간에서 멈췄다가 다음 호출 때 이어서 실행되는 것처럼 보입니다.
이번 글에서는 원문의 핵심 아이디어를 바탕으로
- 코루틴이 본질적으로 무엇인지
- 왜 switch-case로 구현 가능한지
- __LINE__ 트릭은 어떻게 동작하는지
- 왜 이 구조가 게임 프로그래밍과 연결되는지
쉽고 자세하게 정리해보겠습니다.
🧠 먼저 코루틴이 무엇인가?
일반 함수는 보통 이런 흐름입니다.
함수 호출
↓
코드 실행
↓
return
↓
함수 완전 종료
void Func()
{
printf("A\n");
printf("B\n");
}
예를 들어 위의 코드에서 Func 함수를 호출하면
A B
출력 후 함수가 끝납니다.
그리고 다음에 Func를 다시 호출하면 항상 처음부터 다시 실행됩니다.
🎯 그런데 코루틴은 다르다
코루틴은 👉 함수 실행 상태를 저장할 수 있습니다.
즉, 아래와 같은 실행 흐름이 가능합니다.
A 실행
↓
잠시 멈춤 (yield)
↓
다음 호출 때 멈춘 지점에서 이어서 실행
↓
B 실행
예를 들어, Unity Coroutine 느낌으로 보면
IEnumerator Routine()
{
Debug.Log("A");
yield return null;
Debug.Log("B");
}
첫 프레임
A
다음 프레임
B
가 실행됩니다.
즉 핵심은 이것입니다.
함수가 “완전히 종료”되는 것이 아니라, 중간 상태를 저장했다가 이어서 실행됩니다.
🚨 그런데 C 언어에는 코루틴이 없다
문제는 이 글이 작성될 당시 C 언어에는 아래의 기능이 존재하지 않았다는 점입니다.
- coroutine
- yield
- async
즉, 함수 실행 위치를 저장하는 기능 자체가 없었습니다.
그런데 여기서 굉장히 재미있는 아이디어가 등장합니다.
- 함수의 실행 위치를 숫자로 저장하면 되지 않을까?
이게 이 글의 핵심 아이디어입니다.
🧩 상태 머신(State Machine)으로 생각하기
예를 들어, 이런 함수가 있다고 생각해봅시다.
void Func()
{
printf("A\n");
// 여기서 잠시 멈추고 싶다
printf("B\n");
}
일반 함수라면, A와 B가 한 번에 실행됩니다.
하지만 만약, 👉 실행 위치를 저장할 수 있다면?
상태 머신 형태로 바꿀 수 있습니다.
int state = 0;
void Func()
{
switch (state)
{
case 0:
printf("A\n");
state = 1;
return;
case 1:
printf("B\n");
state = 2;
return;
}
}
🎮 실행 흐름 보기
첫 번째 호출
state = 0 ↓ case 0 진입 ↓ "A" 출력 ↓ state = 1 저장 ↓ return
두 번째 호출
state = 1 ↓ case 1 진입 ↓ "B" 출력
즉, 👉 함수가 중간부터 다시 실행되는 것처럼 보입니다.
🧠 이게 바로 코루틴의 핵심이다
사실 코루틴의 핵심은 굉장히 단순합니다.
현재 실행 위치(state)를 저장했다가,
다음 호출 때 그 위치부터 다시 실행한다.
즉, “코루틴 ≈ 스테이트 머신” 이라고 볼 수 있습니다.
실제로 현대 coroutine 컴파일러도 내부적으로는
- 상태(state)
- resume point
- suspend point
기반 state machine 코드로 변환합니다.
즉, 이 글은 👉 코루틴의 본질을 보여주는 글입니다.
🔥 이제 원문의 핵심 트릭 등장
지금까지 설명한 coroutine 상태 저장 구조를 그림으로 정리해보면 다음과 같습니다.
핵심은 Coroutine의 본질은 “현재 실행 위치(state)를 저장했다가 다음 호출 때 이어 실행하는 것”이라는 점입니다.

위 구조처럼 원문의 coroutine 구현은
- switch-case 기반 상태 머신
- __LINE__ 매크로를 이용한 실행 위치 저장
- yield 지점 복원
을 조합해 함수가 중간부터 다시 실행되는 것처럼 동작하게 만듭니다.
즉 이 구조는 단순한 매크로 트릭이 아니라,
Coroutine이 실제로 어떤 원리로 동작하는지를 아주 낮은 수준에서 보여주는 예제라고 볼 수 있습니다.
원문에서 가장 유명한 부분은 바로 이것입니다.
#define crBegin static int state = 0; switch(state) { case 0:
#define crReturn(x) do { state = __LINE__; return x; case __LINE__:; } while (0)
#define crFinish }
처음 보면 굉장히 이상합니다.
특히
case __LINE__:
부분이 충격적입니다.
🧠 __LINE__이 무엇인가?
__LINE__은 현재 코드 라인 번호를 의미하는 매크로입니다.
예를 들어, 아래 코드와 같이 __LINE__을 출력하면 현재 코드의 줄 번호(라인)가 출력됩니다.
printf("%d", __LINE__);
즉, __LINE__을 통해 각 코드 위치마다 고유 번호를 얻을 수 있습니다.
원문은 이걸 coroutine 상태 번호로 사용합니다.
🎯 실제 동작 흐름
예제를 보겠습니다.
int Coroutine()
{
crBegin;
printf("A\n");
crReturn(1);
printf("B\n");
crReturn(2);
crFinish;
}
매크로를 개념적으로 펼쳐보면 대략 이런 느낌입니다.
int Coroutine()
{
static int state = 0;
switch(state)
{
case 0:
printf("A\n");
state = 10;
return 1;
case 10:
printf("B\n");
state = 20;
return 2;
case 20:
break;
}
}
즉, 👉 yield 위치를 라인 번호로 저장하는 것입니다.
🎮 실행 흐름 다시 보기
첫 번째 호출
A 출력 ↓ 현재 라인 번호 저장 ↓ return 1
두 번째 호출
저장된 라인 번호의 case로 점프 ↓ B 출력 ↓ return 2
즉, 함수가 👉 중간부터 이어서 실행되는 것처럼 보입니다.
🔥 왜 이게 충격적이었을까?
이 글이 유명해진 이유는 언어 기능 없이 coroutine 느낌을 구현했기 때문입니다.
특히, 스택 저장 없음 / 스레드 없음 / OS 스케줄링 없없음에도 “함수 중간에서 멈췄다가 이어서 실행”하는 것처럼 보입니다.
즉, Coroutine의 핵심이 “스택 저장”이 아니라 “실행 위치 저장”이라는 점을 보여줍니다.
⚠️ 하지만 한계도 크다
물론 이 방식은 진짜 coroutine과는 차이가 있습니다.
1. 지역 변수 문제가 있다
예를 들어, 아래 코드는 문제가 발생합니다.
void Func()
{
int x = 10;
crReturn();
printf("%d", x);
}
왜냐하면, “함수가 return되면 지역 변수는 사라지기 때문“입니다.
즉, 👉 상태를 유지하려면 static 또는 별도 struct에 저장해야 합니다.
2. switch-case 트릭이다
실제로는 switch-case 진입 위치 조작을 이용한 꼼수입니다.
따라서, 디버깅 어려움 / 가독성 낮음 / IDE 친화적이지 않음 등의 문제가 있습니다.
3. Stackless Coroutine이다
이 구조는 👉 현재 함수 상태만 저장합니다.
즉, 함수 호출 스택 전체를 저장하지 않습니다.
그래서 Stackless Coroutine이라고 부릅니다.
반면, 일부 coroutine 시스템은 전체 call stack을 저장하기도 합니다.
🎮 그런데 게임 프로그래밍에서는 굉장히 중요하다
게임 로직은 원래 👉 여러 프레임에 걸쳐 실행되는 경우가 많습니다.
예를 들어, 아래와 같은 실행 흐름을 구현해야 할 때가 많습니다.
3초 대기
↓
문 열기
↓
애니메이션 재생
↓
적 생성
즉, “중간 상태 저장 후 이어 실행” 구조가 매우 중요합니다.
그래서 게임을 제작할 때 아래에 나열된 기능을 자주 사용하게 됩니다.
- Coroutine
- Latent Action
- Async Task
- FSM(State Machine)
🧠 Unity Coroutine과도 연결된다
Unity의 yield return null; 도 결국 👉 상태 머신(state machine)입니다.
컴파일러가 내부적으로
- 현재 실행 위치
- yield 지점
- 다음 resume 위치
를 저장하는 구조로 변환합니다.
즉, Unity의 코루틴도 실제로는 상태 머신 기반의 코드입니다.
이 점에서 원문의 아이디어와 굉장히 비슷합니다.
🔥 현대 C++ Coroutine과 비교하면?
지금은 C++20 coroutine이 존재합니다.
- co_await
- co_yield
- co_return
같은 문법이 있습니다.
하지만, 내부 원리는 여전히 비슷합니다.
컴파일러는 coroutine 코드를 👉 state machine 기반 구조로 변환합니다.
즉, Coroutine의 본질은 여전히 “실행 위치 저장”입니다.
🎯 이 글의 진짜 핵심
사실 이 글의 핵심은 “코루틴 구현법” 자체보다 코루틴이 실제로 무엇인가?를 보여주는 데 있습니다.
즉, 코루틴 = 현재 실행 위치 저장 + 다음 호출 때 이어 실행이라는 점을 아주 직관적으로 보여줍니다.
🎯 핵심 정리
- 일반 함수는 return 시 완전히 종료된다
- Coroutine은 실행 상태를 저장하고 이어 실행할 수 있다
- 원문은 switch-case와 __LINE__을 이용해 coroutine처럼 동작하는 구조를 구현했다
- 핵심은 현재 실행 위치(state)를 저장하는 것이다
- 이 구조는 결국 state machine과 매우 유사하다
- Unity Coroutine과 현대 C++ coroutine도 내부적으로 state machine 기반이다
🧩 마무리
처음 보면 이 글은 단순한 C 매크로 꼼수처럼 보일 수 있습니다.
하지만 조금 더 깊게 보면 👉 코루틴의 본질을 아주 직관적으로 설명하는 글입니다.
특히, 게임 프로그래밍에서는 아래의 내용이 모두 연결됩니다.
- FSM
- Coroutine
- 비동기 처리
- 게임 루프
즉, “현재 상태를 저장했다가, 다음 프레임에 이어 실행한다”는 개념이 게임 로직의 핵심 구조 중 하나입니다.
이 글은 그 구조를 아주 낮은 수준에서 직접 보여준다는 점에서 지금 봐도 굉장히 흥미로운 글이라고 생각합니다.