첫 번째 C++ 프로그램
Hello World 프로그램의 줄마다 #include와 main 함수, std::cout과 << 연산자, return 0이 하는 일을 읽고, 직접 빌드해 실행한 뒤 화면의 출력과 종료 상태가 서로 다른 결과라는 것을 확인하며, 주석을 쓰는 두 방법을 익힙니다.
앞 글에서 실행해 본 프로그램은 몇 줄밖에 안 되지만, C++ 프로그램이 갖춰야 할 기본 요소를 모두 담고 있습니다. 표준 라이브러리의 기능을 가져오는 줄, 프로그램이 시작하는 함수, 화면에 글자를 쓰는 문장, 프로그램을 끝내며 결과를 알리는 문장입니다.
이 글은 그 네 부분을 한 줄씩 읽고, 같은 프로그램을 직접 써서 빌드하고 실행합니다. 이어서 화면에 나온 글자와 프로그램의 종료 상태가 서로 다른 결과라는 것을 확인하고, 코드에 설명을 남기는 주석을 익힙니다.
프로그램의 네 부분
#include <iostream>
int main() {
std::cout << "Hello, C++ World!" << std::endl;
return 0;
}첫 프로그램의 생명 주기
소스 작성에서 hosted 실행 환경의 main 진입·stdout 출력·종료 상태 0으로 이어지는 구체적 생명 주기다.
C++ · BUILD AND EXECUTION
소스 작성·빌드·실행·종료를 나누어 읽는다
이 절의 hosted 프로그램은 빌드된 파일을 시작하면 실행 환경 초기화 뒤 main 본문에 진입합니다.
- 소스 작성
include · main · 출력
- 컴파일·링크
빌드 성공 → 실행 파일
- 프로그램 시작
실행 환경이 main 호출
- 출력과 종료
문자 기록 · 상태 0 반환
첫 프로그램에서 관찰할 두 결과
출력 Hello, C++ World!와 줄바꿈이 표준 출력으로 기록됩니다. std::endl은 스트림을 flush합니다.
종료 return 0;은 main을 마치고 성공 종료 상태를 실행 환경에 전달합니다.
완결된 번역 단위가 컴파일되고 실행 환경이 main을 호출해 종료 상태를 받는다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 도표 제목 | 첫 프로그램의 생명 주기 |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 소스에서 실행 파일 main과 출력문이 빌드된다. |
|
|
| main 실행 main의 출력문이 stdout에 문자열과 줄바꿈을 보낸다. |
|
|
| 정상 종료 main이 반환해 프로그램의 종료 결과를 전달한다. |
|
|
첫 프로그램의 생명 주기
소스 작성에서 hosted 실행 환경의 main 진입·stdout 출력·종료 상태 0으로 이어지는 구체적 생명 주기다.
C++ · BUILD AND EXECUTION
소스 작성·빌드·실행·종료를 나누어 읽는다
이 절의 hosted 프로그램은 빌드된 파일을 시작하면 실행 환경 초기화 뒤 main 본문에 진입합니다.
- 소스 작성
include · main · 출력
- 컴파일·링크
빌드 성공 → 실행 파일
- 프로그램 시작
실행 환경이 main 호출
- 출력과 종료
문자 기록 · 상태 0 반환
첫 프로그램에서 관찰할 두 결과
출력 Hello, C++ World!와 줄바꿈이 표준 출력으로 기록됩니다. std::endl은 스트림을 flush합니다.
종료 return 0;은 main을 마치고 성공 종료 상태를 실행 환경에 전달합니다.
완결된 번역 단위가 컴파일되고 실행 환경이 main을 호출해 종료 상태를 받는다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 도표 제목 | 첫 프로그램의 생명 주기 |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 소스에서 실행 파일 main과 출력문이 빌드된다. |
|
|
| main 실행 main의 출력문이 stdout에 문자열과 줄바꿈을 보낸다. |
|
|
| 정상 종료 main이 반환해 프로그램의 종료 결과를 전달한다. |
|
|
그림처럼 이 소스는 컴파일과 링크를 거쳐 실행 파일이 되고, 그 파일을 실행하면 실행 환경이 준비를 마친 뒤 main을 부릅니다. 프로그램은 표준 출력에 글자를 쓰고, main이 끝나면서 종료 상태를 실행 환경에 돌려줍니다. 각 줄이 이 흐름에서 맡는 일은 다음과 같습니다.
첫 줄 #include <iostream>은 전처리기 지시자(preprocessor directive)로, 컴파일하기 전에 <iostream> 헤더의 내용을 이 자리에 끌어오라는 뜻입니다. <iostream>은 입출력 스트림(input/output stream)의 기능을 선언한 표준 라이브러리 헤더이며, 아래의 std::cout과 std::endl을 쓰려면 이 헤더가 필요합니다. 꺾쇠괄호로 감싼 이름은 컴파일러가 정한 헤더 검색 경로에서 찾습니다.
int main() { ... }은 프로그램이 시작하는 main 함수입니다. 운영체제 위에서 실행하는 보통의 프로그램에는 반드시 main이 하나 있어야 하며, 실행 환경이 전역 객체의 초기화 같은 준비를 마친 뒤 이 함수를 부릅니다. 앞의 int는 이 함수가 정수를 돌려준다는 뜻이고, 이름 뒤의 빈 괄호는 받는 인자가 없다는 뜻이며, 중괄호 안이 함수가 실행할 문장들을 담은 본문입니다.
std::cout << "Hello, C++ World!" << std::endl;은 화면에 글자를 쓰는 문장입니다. std::cout은 표준 출력 스트림 객체로, 보통 터미널의 화면에 연결되어 있고, 앞의 std::는 이 이름이 표준 라이브러리의 이름 공간(namespace)인 std에 속한다는 뜻입니다. <<는 오른쪽의 값을 왼쪽의 스트림으로 보내는 삽입 연산자(insertion operator)이며, 큰따옴표로 감싼 "Hello, C++ World!"는 문자열 리터럴(string literal)입니다.
std::endl은 줄을 바꾸는 문자를 쓴 뒤 스트림의 버퍼를 비워, 모아 둔 글자를 바로 화면으로 내보냅니다. 줄만 바꾸면 될 때는 "\n"을 써도 되며, 버퍼를 비우는 일은 비용이 들므로 출력이 많은 프로그램에서는 "\n"이 더 빠릅니다. 문장 끝의 세미콜론은 이 문장이 여기서 끝난다는 표시이며, 빠뜨리면 컴파일러가 다음 줄과 이어 읽다가 오류를 냅니다.
마지막 return 0;은 main을 끝내며 실행 환경에 0을 돌려줍니다. 0은 프로그램이 성공했다는 관례적인 값이고, 오류가 있었다면 0이 아닌 값을 돌려 알립니다. main만은 예외적으로 return 문을 빠뜨려도 끝에 이르면 0을 돌려준 것으로 처리되지만, 이 교재에서는 뜻을 분명히 하려고 적습니다.
직접 빌드하고 실행하기
VS Code에서 새 파일을 만들어 위 코드를 쓰고, 프로젝트 폴더에 hello_world.cpp라는 이름으로 저장합니다. .cpp는 C++ 소스 파일을 나타내는 확장자입니다. 보기 메뉴의 터미널로 터미널을 열고, 파일을 저장한 폴더로 이동해 컴파일합니다.
g++ -std=c++20 -Wall -Wextra hello_world.cpp -o hello_world-o hello_world는 만들 실행 파일의 이름을 정하는 옵션이며, Windows의 MSYS2 GCC는 이름에 .exe를 붙여 hello_world.exe를 만듭니다. 아무 메시지 없이 명령이 끝나면 컴파일이 성공한 것이고, 오류나 경고가 있으면 그 내용이 출력됩니다. 실행은 Windows의 PowerShell에서는 .\hello_world.exe로, macOS와 Linux에서는 ./hello_world로 합니다.
Hello, C++ World!앞의 .\나 ./는 지금 폴더에 있는 파일을 실행하라는 뜻입니다. macOS와 Linux의 셸은 보안을 위해 지금 폴더에서 명령을 찾지 않으므로 이 표시가 반드시 필요하고, PowerShell도 같은 이유로 지금 폴더의 실행 파일에 .\를 붙이게 합니다.
화면의 출력과 종료 상태
프로그램이 돌려주는 결과는 두 가지입니다. std::cout이 쓴 글자는 화면에 나타나고, return 0;이 돌려준 종료 상태(exit status)는 화면에 나타나지 않고 프로그램을 실행한 셸에 전달됩니다. 셸에서 그 값을 보려면 실행한 직후에 다음 명령을 씁니다.
.\hello_world.exe
$LASTEXITCODE./hello_world
echo $?Hello, C++ World!
0두 경우 모두 둘째 줄의 0은 프로그램이 출력한 것이 아니라, 둘째 명령이 바로 전에 실행한 프로그램의 종료 상태를 보여 준 것입니다. 다른 명령을 그사이에 실행하면 그 명령의 상태로 바뀌므로 실행한 직후에 확인합니다. 스크립트나 빌드 도구는 이 값으로 프로그램의 성공과 실패를 판단하며, return 1;로 바꾸면 화면의 글자는 같지만 상태는 1이 됩니다.
더 깊이: main의 반환값이 셸에 닿기까지
main에서 돌아오는 것은 그 반환값을 인자로 std::exit를 부르는 것과 같아서, return 뒤에도 할 일이 남아 있습니다. 먼저 main의 지역 변수가 정리되고, 이어서 전역 변수처럼 정적 저장 기간을 가진 객체의 소멸자와 std::atexit로 등록한 함수가 실행됩니다. 그다음 C 스트림을 모두 비우고 닫은 뒤에야 실행 환경으로 제어가 넘어갑니다. 반대로 main 안에서 std::exit를 직접 부르면 그 자리에서 이 과정이 시작되므로, main의 지역 변수는 소멸자가 실행되지 않습니다.
표준이 뜻을 정해 둔 반환값은 셋뿐입니다. 0과 <cstdlib>의 EXIT_SUCCESS는 성공을, EXIT_FAILURE는 실패를 나타내며, 실제로 실행 환경에 어떤 숫자가 전달되는지와 그 밖의 값을 어떻게 다룰지는 구현이 정합니다.
운영체제가 그 값을 받는 폭도 다릅니다. POSIX는 부모 프로세스가 wait()나 waitpid()로 받을 수 있는 것이 상태 값의 낮은 8비트뿐이라고 정하고 셸도 이 8비트를 종료 상태로 쓰므로, Linux나 macOS에서 return 256;을 하면 셸의 $?는 0이 되어 성공처럼 보입니다. Windows의 GetExitCodeProcess는 종료 코드를 32비트 DWORD로 돌려주므로 256이 그대로 보이지만, 259는 아직 실행 중이라는 뜻의 STILL_ACTIVE와 값이 같아 오류 코드로 쓰지 말라고 문서에 적혀 있습니다. 그래서 실패의 종류를 숫자로 나누고 싶다면 1부터 255 사이에서 고르는 편이 어느 환경에서나 같은 뜻으로 읽힙니다.
주석
주석(comment)은 컴파일러가 무시하는 글로, 코드를 읽는 사람에게 설명을 남깁니다. C++의 주석은 두 가지입니다.
int age = 30; // 나이를 저장하는 변수
/*
* 두 수를 받아 합을 돌려줍니다.
* 결과가 int의 범위를 넘는 입력은 호출하는 쪽에서 막아야 합니다.
*/
int calculateSum(int a, int b) {
return a + b;
}//는 그 뒤부터 줄의 끝까지를 주석으로 만들고, /*와 */는 그 사이의 여러 줄을 주석으로 만듭니다. /* */ 주석은 안에 다시 /* */를 넣을 수 없어서, 이미 주석이 있는 코드를 감싸 주석으로 만들면 첫 */에서 주석이 끝나 버립니다. 좋은 주석은 코드가 무엇을 하는지를 다시 적기보다, 왜 그렇게 했는지와 쓸 때 조심할 점을 남깁니다.
정리
#include <iostream>은 컴파일 전에 입출력 기능의 선언을 가져오며,std::cout과std::endl을 쓰려면 필요합니다.- 프로그램은 실행 환경이 준비를 마친 뒤 부르는
main함수에서 시작하고,main의 반환값은 종료 상태로 셸에 전달됩니다. std::cout << 값은 값을 표준 출력 스트림으로 보내며,std::endl은 줄을 바꾸고 버퍼를 비우고,"\n"은 줄만 바꿉니다.- 화면에 나온 글자와 종료 상태는 서로 다른 결과이며, 종료 상태는 PowerShell의
$LASTEXITCODE나 셸의$?로 확인합니다. //는 줄의 끝까지,/* */는 그 사이를 주석으로 만들며,/* */는 겹쳐 쓸 수 없습니다.
연습 문제
1. 줄을 바꾸지 않는 출력
main의 출력 문장을 다음 두 줄로 바꾸었습니다. 실행하면 무엇이 출력되나요? 첫 줄의 앞에 //를 붙이면 어떻게 되나요?
std::cout << "Ready";
std::cout << "Go" << std::endl;해설 보기
두 문장이 차례로 같은 스트림에 쓰고, 첫 문장은 줄을 바꾸지 않으므로 한 줄에 ReadyGo가 출력되고 줄이 바뀝니다. 첫 줄을 주석으로 만들면 Go만 출력됩니다. 이 두 줄은 main의 중괄호 안, return 0;보다 앞에 넣어야 하며, 함수 밖에 출력 문장만 두면 컴파일되지 않습니다.
2. 0이 화면에 찍히는가
return 0;을 return 3;으로 바꾸고 실행했습니다. 화면에 3이 출력되나요? Bash에서 실행 직후 echo $?를 실행하면 무엇이 보이나요?
해설 보기
화면에는 3이 출력되지 않고 Hello, C++ World!만 보입니다. 반환값은 글자가 아니라 종료 상태로 셸에 전달되므로, 실행 직후의 echo $?가 3을 보여 줍니다. 셸과 스크립트는 0이 아닌 이 값을 실패로 봅니다.
3. 세미콜론을 빠뜨리면
출력 문장 끝의 세미콜론을 지우고 컴파일하면 어떻게 되나요? 진단은 어느 줄을 가리킬 가능성이 큰가요?
해설 보기
컴파일러는 문장이 끝나지 않았다고 보고 다음 줄의 return과 이어 읽다가 오류를 냅니다. 그래서 진단은 세미콜론을 빠뜨린 줄이나 그다음 줄인 return 0;을 가리키며, GCC는 보통 빠진 세미콜론이 앞에 있어야 한다고 알려 줍니다. 진단이 가리키는 줄에 문제가 없어 보이면 바로 앞 줄의 끝부터 확인합니다.
다음 글 컴파일과 실행 과정에서는 g++ 한 번에 묶여 있던 전처리, 컴파일, 어셈블, 링크를 단계별로 나누어 실행하고, 각 단계가 만드는 파일과 그 단계에서 나는 오류를 살펴봅니다.