C++ 개발 환경 구축 | Windows·macOS·Linux 컴파일러 설치와 첫 빌드

[C++ 실전 가이드 #1] C++ 개발 환경 구축

이전 글 C++ 실전 가이드 #0: C++이란?에서는 C++의 역사와 용도, 장단점을 다뤘습니다. 이번 글에서는 Windows, macOS, Linux별로 컴파일러를 설치하고 첫 프로그램을 빌드해 실행합니다. 별도 라이브러리 없이 운영체제별 컴파일러만 있으면 됩니다.

처음에는 컴파일러 하나를 설치하고 터미널에서 g++ 한 번 실행해 보는 것으로 충분합니다. 편집기 설정은 #3 VS Code, 빌드 시스템은 #4 CMake에서 따로 다루므로, 이 글의 목표는 소스 파일을 실행 파일로 바꿔 보는 경험입니다. 글이 길게 느껴지면 자신의 운영체제 섹션(Windows, macOS, Linux)만 읽고 첫 프로그램 실행으로 넘어가도 됩니다.


컴파일러가 먼저인 이유

C++은 컴파일 언어입니다. Python처럼 소스 코드를 인터프리터가 바로 실행하는 것이 아니라, 컴파일러가 소스 코드를 기계어로 번역해 실행 파일을 만들고, 그 실행 파일을 실행합니다. 그래서 코드를 고친 뒤 다시 컴파일하지 않고 예전 실행 파일을 실행하면 수정한 내용이 반영되지 않습니다. 처음 배울 때 “코드를 고쳤는데 결과가 그대로”인 경우는 대부분 이 때문입니다.

C++ 개발에는 세 가지가 필요합니다. 소스 코드를 실행 파일로 바꾸는 컴파일러(GCC, Clang, MSVC 등), 여러 파일로 된 프로젝트의 빌드를 관리하는 빌드 도구(Make, CMake 등), 코드를 작성하는 편집기나 IDE(VS Code, Visual Studio, CLion 등)입니다. 이 중 컴파일러가 없으면 아무것도 할 수 없고, 편집기와 빌드 도구는 어떤 컴파일러를 쓸지 정해진 뒤에 그 경로를 지정해 설정합니다. 그래서 이 글은 컴파일러 설치에 집중합니다.

flowchart LR
    A[소스코드 .cpp] --> B[전처리기]
    B --> C[컴파일러]
    C --> D[어셈블러]
    D --> E[링커]
    E --> F[실행파일 .exe/.out]

g++ hello.cpp -o hello 한 줄은 내부적으로 위 단계를 모두 거칩니다. 각 단계는 #5 컴파일 과정에서 자세히 다룹니다.


컴파일러 선택

운영체제마다 기본으로 쓰는 컴파일러가 다릅니다.

컴파일러주로 쓰는 운영체제특징
MSVC (Microsoft Visual C++)WindowsVisual Studio와 통합, Windows SDK와 디버거 지원이 가장 좋음
GCC (GNU Compiler Collection)Linux대부분의 Linux 배포판 기본 컴파일러, 오픈소스
ClangmacOSApple 기본 컴파일러, 읽기 쉬운 에러 메시지, Linux·Windows에서도 사용 가능
flowchart TD
    A[어떤 OS를 사용하나요?] --> B{Windows?}
    A --> C{macOS?}
    A --> D{Linux?}
    B --> E[Windows 전용 앱 개발?]
    E -->|예| F[MSVC 권장]
    E -->|아니오| G[MinGW 또는 WSL]
    C --> H[Xcode Command Line Tools / Clang]
    D --> I[GCC build-essential]

Windows에서는 Visual Studio의 MSVC가 기본 선택입니다. MinGW-w64(Windows용 GCC 툴체인)도 Win32 API를 대부분 지원하지만, MFC나 ATL처럼 Visual Studio에만 포함된 라이브러리는 쓸 수 없습니다. Windows 전용 앱이나 DirectX, MFC를 쓸 계획이면 MSVC가, Linux와 같은 g++ 명령줄로 공부하거나 CI와 같은 컴파일러를 맞추고 싶다면 MinGW가 편합니다. macOS에서는 Xcode Command Line Tools의 Clang을, Linux에서는 배포판 패키지의 GCC를 쓰면 됩니다.


Windows 환경 구축

Windows에서는 Visual Studio(MSVC), MSYS2로 설치하는 MinGW-w64(GCC), WSL(Linux의 g++) 세 가지 방법이 있습니다. 결정하기 어렵다면 Visual Studio로 시작해도 되고, 나중에 다른 방법을 추가로 설치할 수 있습니다.

방법 1: Visual Studio (MSVC)

Visual Studio는 IDE, 컴파일러, 디버거가 하나로 묶여 있어 설치만 하면 바로 프로젝트를 만들고 디버깅할 수 있습니다. 대신 설치 용량이 수 GB 단위로 큽니다.

Visual Studio 공식 사이트에서 Community 에디션(개인과 소규모 팀은 무료)을 받아 설치 프로그램을 실행합니다. 워크로드 선택 화면에서 “C++를 사용한 데스크톱 개발”을 체크하면 오른쪽 설치 세부 정보에 MSVC 빌드 도구, Windows SDK, C++용 CMake 도구가 기본으로 포함됩니다. MSVC 빌드 도구의 버전 번호는 Visual Studio 버전에 따라 다릅니다.

설치가 끝나면 시작 메뉴에서 “Developer Command Prompt for VS”를 검색해 실행하고 cl을 입력합니다.

Microsoft (R) C/C++ Optimizing Compiler Version 19.xx.xxxxx for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

usage: cl [ option... ] filename... [ /link linkoption... ]

이런 출력이 나오면 정상입니다. MSVC 컴파일러 cl은 이 Developer Command Prompt처럼 vcvarsall.bat이 환경 변수를 설정한 터미널에서만 동작하고, 일반 CMD나 PowerShell에서는 “인식할 수 없는 명령”이라는 에러가 납니다. 간단히 확인하려면 같은 창에서 다음을 실행합니다.

cd %USERPROFILE%\Desktop
echo #include ^<iostream^> > hello.cpp
echo int main() { std::cout ^<^< "Hello!" ^<^< std::endl; return 0; } >> hello.cpp
cl /EHsc hello.cpp
hello.exe

방법 2: MSYS2로 MinGW-w64 (GCC) 설치

Visual Studio가 부담스럽다면 MSYS2로 GCC를 설치합니다. 설치 용량이 작고, Linux와 같은 g++ 명령을 그대로 쓸 수 있습니다.

MSYS2 공식 사이트에서 설치 프로그램을 받아 기본 경로(C:\msys64)에 설치합니다. 설치가 끝나면 MSYS2 터미널에서 패키지 데이터베이스를 업데이트합니다.

pacman -Syu

업데이트 도중 터미널을 닫아야 한다는 메시지가 나오면 닫고, 다시 MSYS2 터미널을 열어 같은 명령을 한 번 더 실행합니다. 그다음 GCC를 설치합니다.

pacman -S mingw-w64-ucrt-x86_64-gcc

MSYS2에는 UCRT64, MINGW64, CLANG64 등 여러 환경이 있고, 현재 MSYS2 문서가 기본으로 권장하는 것은 Windows 10 이후 기본 탑재된 UCRT 런타임을 쓰는 UCRT64입니다. 예전 자료에 자주 나오는 mingw-w64-x86_64-gcc는 MINGW64 환경용 패키지이며, 이 경우 아래 경로의 ucrt64를 mingw64로 바꾸면 됩니다.

일반 CMD나 PowerShell에서도 g++을 쓰려면 C:\msys64\ucrt64\bin을 PATH에 추가합니다. Windows 키 + R로 sysdm.cpl을 실행하고, 고급 탭의 환경 변수에서 사용자 변수의 Path를 편집해 경로를 추가한 뒤, 열려 있는 터미널을 모두 닫고 새로 엽니다. PATH는 운영체제가 명령어 이름으로 실행 파일을 찾을 때 뒤지는 폴더 목록이라, 이미 열린 터미널에는 변경이 반영되지 않습니다. PATH를 건드리지 않으려면 시작 메뉴의 “MSYS2 UCRT64” 터미널을 쓰면 됩니다. 이 터미널에는 경로가 자동으로 설정되어 있습니다.

새 터미널에서 g++ --version을 실행해 g++ (Rev..., Built by MSYS2 project)로 시작하는 버전 정보가 나오면 성공입니다. 디버거와 make가 필요하면 pacman -S mingw-w64-ucrt-x86_64-gdb mingw-w64-ucrt-x86_64-make로 추가합니다(make는 mingw32-make라는 이름으로 설치됩니다).

MinGW로 빌드한 실행 파일을 다른 폴더나 다른 PC에서 실행하면 libgcc_s_seh-1.dll이나 libstdc++-6.dll을 찾을 수 없다는 에러가 날 수 있습니다. 이 DLL들이 ucrt64\bin에만 있기 때문입니다. 배포할 실행 파일은 -static-libgcc -static-libstdc++(또는 -static)로 런타임을 정적 링크하면 DLL 없이 실행됩니다.

방법 3: WSL에서 Linux 환경 그대로 사용

Windows 10/11에서는 WSL(Windows Subsystem for Linux)로 Ubuntu 같은 Linux 배포판을 설치하고 그 안에서 g++을 쓸 수 있습니다. 관리자 권한 PowerShell에서 다음을 실행하고, 재부팅 후 Ubuntu의 사용자 이름과 비밀번호를 설정합니다.

wsl --install

Ubuntu 터미널에서 빌드 도구를 설치합니다.

sudo apt update
sudo apt install build-essential
g++ --version

이후에는 Linux 환경 구축과 같습니다. VS Code의 WSL 확장을 쓰면 Windows의 VS Code에서 편집하면서 WSL 안에서 빌드하고 디버깅할 수 있습니다.


macOS 환경 구축

macOS에서는 Apple이 제공하는 Clang을 씁니다. 전체 Xcode 없이 컴파일러와 명령줄 도구만 담긴 Command Line Tools를 설치하면 됩니다.

xcode-select --install

설치 창이 뜨면 “설치”를 누르고 라이선스에 동의합니다. 설치가 끝나면 버전을 확인합니다.

clang++ --version

Apple clang version ...으로 시작하는 출력이 나오면 성공입니다. 이때 g++ --version을 실행해도 같은 Apple Clang 정보가 나옵니다. macOS의 /usr/bin/g++은 GCC가 아니라 Clang을 실행하는 대체 명령이기 때문입니다. 대부분의 C++ 코드는 Clang으로 문제없이 컴파일되므로 그대로 쓰면 됩니다.

iOS나 macOS 앱을 만들 계획이면 Mac App Store에서 Xcode 전체를 설치합니다. 설치 후 한 번 실행해 추가 구성 요소를 설치하고, 명령줄 도구가 Xcode를 가리키도록 설정합니다.

sudo xcode-select -s /Applications/Xcode.app/Contents/Developer

GCC가 꼭 필요하면 Homebrew로 설치합니다.

brew install gcc

Homebrew의 GCC는 g++-15처럼 버전이 붙은 이름으로 설치되고, 그냥 g++은 여전히 Apple Clang을 가리킵니다. xcode-select 관련 에러가 나면 sudo xcode-select --reset으로 경로를 초기화해 봅니다.


Linux 환경 구축

Linux에서는 배포판의 패키지 매니저로 GCC를 설치합니다.

Ubuntu / Debian 계열

build-essential 패키지에는 gcc, g++, make, C 표준 라이브러리 개발 파일(libc6-dev)이 들어 있어, 한 번 설치하면 컴파일과 링크에 필요한 기본 도구가 준비됩니다.

sudo apt update
sudo apt install build-essential
g++ --version

디버거와 CMake는 따로 설치합니다.

sudo apt install gdb cmake

배포판 기본 GCC보다 새 버전이 필요하면 버전이 붙은 패키지를 설치하고 update-alternatives로 기본값을 바꿀 수 있습니다. 예를 들어 Ubuntu 22.04의 기본 GCC 11도 C++17은 모두 지원하지만, C++20·C++23의 일부 라이브러리 기능은 더 새 버전이 필요합니다.

sudo apt install g++-12
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 100

Fedora / RHEL 계열

# Fedora 41 이상 (dnf5)
sudo dnf group install development-tools
# RHEL 8/9, 이전 Fedora
sudo dnf groupinstall "Development Tools"

sudo dnf install gcc-c++
g++ --version

Arch Linux

sudo pacman -S base-devel
g++ --version

base-devel에는 gcc, make 등 빌드 도구가 들어 있지만 gdb는 포함되지 않으므로 필요하면 sudo pacman -S gdb로 따로 설치합니다.

openSUSE와 Alpine

# openSUSE
sudo zypper install -t pattern devel_basis
# Alpine (Docker 등 경량 환경)
apk add build-base

첫 프로그램 실행

편집기(메모장, VS Code 등)로 hello.cpp 파일을 만들고 다음 코드를 입력합니다.

#include <iostream>

int main() {
    std::cout << "Hello, C++!" << std::endl;
    return 0;
}

#include <iostream>은 입출력 기능의 선언이 담긴 표준 헤더를 불러옵니다. int main()은 프로그램의 시작점으로, 모든 C++ 프로그램은 main 함수부터 실행됩니다. std::cout은 표준 출력(화면)에 연결된 스트림 객체이며, std는 표준 라이브러리가 들어 있는 네임스페이스 이름입니다. <<는 스트림 삽입 연산자로, 오른쪽의 값을 왼쪽 스트림으로 보냅니다. return 0은 프로그램이 정상적으로 끝났음을 운영체제에 알리는 관례적인 값입니다.

std::endl은 줄바꿈과 함께 출력 버퍼를 비웁니다(flush). 출력이 많은 반복문에서는 매번 버퍼를 비우는 비용이 쌓이므로 '\n'만 쓰는 편이 빠르고, 프로그램이 비정상 종료되기 전에 꼭 화면에 보여야 하는 메시지에는 std::endl이 안전합니다.

컴파일 및 실행

Windows에서 MSVC를 쓴다면 Developer Command Prompt에서 다음을 실행합니다. /EHsc는 표준 C++ 예외 처리를 켜는 옵션으로, 빠뜨리면 iostream을 쓸 때 경고가 납니다.

cl /EHsc hello.cpp
hello.exe

MinGW, macOS, Linux에서는 다음과 같습니다. -o hello는 만들 실행 파일의 이름을 지정합니다.

g++ hello.cpp -o hello
./hello

Windows CMD에서는 hello 또는 .\hello.exe로 실행합니다. Hello, C++!가 출력되면 환경 구축이 끝난 것입니다.

처음에 자주 막히는 지점은 세 가지입니다. Windows 메모장에서 저장할 때 파일 형식을 “모든 파일”로 바꾸지 않으면 hello.cpp.txt로 저장되어 컴파일러가 파일을 찾지 못합니다. C++ 문장 끝의 세미콜론을 빠뜨리면 컴파일 에러가 나는데, 에러가 가리키는 줄이 실제로 세미콜론이 빠진 줄의 다음 줄인 경우가 많습니다. 또 C++은 대소문자를 구분하므로 std::Cout이나 Int main()은 에러입니다.

C++ 표준 지정

컴파일러마다 기본으로 적용하는 C++ 표준이 다르므로, 표준을 명시하는 습관을 들이는 것이 좋습니다. 다음 코드는 C++17의 구조화된 바인딩을 사용합니다.

#include <iostream>
#include <map>
#include <string>

int main() {
    std::map<std::string, int> m = {{"a", 1}, {"b", 2}};
    for (const auto& [key, value] : m) {
        std::cout << key << ": " << value << '\n';
    }
    return 0;
}
g++ -std=c++17 -Wall -Wextra hello.cpp -o hello

MSVC에서는 cl /EHsc /std:c++17 hello.cpp입니다. -Wall -Wextra는 컴파일은 되지만 버그일 가능성이 높은 코드에 경고를 띄우므로, 처음부터 켜 두는 것을 권장합니다.


코드 한 줄씩 이해하기

#include <iostream>

#include는 다른 파일의 내용을 이 위치에 그대로 붙여 넣으라는 전처리기 지시문입니다. <iostream>은 표준 입출력 스트림을 다루는 헤더로, std::cout, std::cin, std::endl이 여기에 선언되어 있습니다. 이 줄을 빼먹으면 컴파일러는 cout이라는 이름을 모른다는 에러를 냅니다.

직접 확인한 값입니다. 4줄짜리 Hello World를 g++ -E(전처리만 수행)로 확장하면 Ubuntu 24.04 aarch64 컨테이너의 g++ 13.3에서는 36,548줄, 이 글을 쓴 macOS의 Apple clang 21에서는 59,476줄이 됐습니다. #include <iostream> 한 줄이 표준 라이브러리 헤더 수만 줄로 바뀐다는 뜻이고, 줄 수는 컴파일러와 운영체제에 따라 다릅니다. 그래서 헤더를 많이 포함할수록 컴파일 시간이 늘어납니다.

이 줄을 빼먹었을 때의 에러 문구도 컴파일러마다 달랐습니다. 같은 소스(#include 없음)를 g++ 13.3은 error: 'cout' is not a member of 'std', clang++ 18과 Apple clang은 error: use of undeclared identifier 'std'로 알려 줬습니다. 앞 문장의 “cout이라는 이름을 모른다”는 표현은 g++ 기준이고, clang에서는 std 자체를 모른다고 나옵니다. 두 메시지는 같은 원인(헤더 누락)을 가리킵니다.

int main()과 반환값

C++ 프로그램은 main 함수에서 실행을 시작합니다. 반환 타입은 표준에 따라 int여야 하고, 반환값은 종료 코드로 운영체제에 전달됩니다. return 0;은 정상 종료를 뜻하는 관례이며, 0이 아닌 값은 실패로 해석됩니다. 터미널에서 실행한 직후 echo $?(Mac/Linux)나 echo %ERRORLEVEL%(CMD)로 이 값을 직접 확인할 수 있습니다. main에 한해서는 return을 생략하면 0을 반환한 것으로 처리되지만, 처음에는 명시적으로 쓰는 편이 읽기 쉽습니다.

std::cout과 <<

std::cout은 표준 출력(보통 터미널 화면)에 연결된 스트림 객체이고, <<는 왼쪽 스트림에 오른쪽 값을 보내는 스트림 삽입 연산자입니다. 연산 결과가 다시 스트림이므로 여러 값을 이어 쓸 수 있습니다.

std::cout << "값: " << 42 << std::endl;

std::endl과 '\n'

std::endl은 줄바꿈 문자를 출력한 뒤 출력 버퍼를 비웁니다(flush). 단순히 줄만 바꾸면 될 때는 '\n'이면 충분하고, 반복문 안에서 매번 endl을 쓰면 줄마다 flush가 일어나 대량 출력이 눈에 띄게 느려질 수 있습니다. 프로그램이 비정상 종료되기 직전의 진행 로그처럼, 그 줄이 반드시 화면에 찍혀야 할 때만 endl의 flush가 의미가 있습니다.

std::cout << "Hello\n";            // 줄바꿈만
std::cout << "Hello" << std::endl; // 줄바꿈 + 버퍼 비우기

using namespace std 논쟁

std::를 매번 쓰기 싫다면 다음처럼 쓸 수 있습니다.

#include <iostream>
using namespace std;

int main() {
    cout << "Hello, World!" << endl;
    return 0;
}

타이핑은 줄지만, std 안의 수많은 이름이 전부 현재 범위로 들어오므로 직접 만든 count, distance 같은 이름과 충돌할 수 있습니다. 짧은 예제나 알고리즘 문제 풀이에서는 흔히 쓰지만, 헤더 파일에 쓰면 그 헤더를 포함하는 모든 파일로 영향이 퍼지므로 헤더에서는 쓰지 않습니다. 큰 프로젝트에서는 using std::cout;처럼 필요한 이름만 가져오거나 std::를 그대로 붙이는 편이 안전합니다. 이 글은 이름이 어디서 왔는지 보이도록 std::를 붙인 형태를 기본으로 씁니다.

소스 코드가 실행 파일이 되기까지

g++ hello.cpp -o hello 한 줄은 사실 네 단계를 한꺼번에 실행합니다. 전처리가 #include <iostream>을 수만 줄짜리 헤더 내용으로 바꿔 붙이고, 컴파일이 그 결과를 어셈블리로 번역하고, 어셈블이 오브젝트 파일(.o)을 만들고, 링크가 표준 라이브러리의 실제 구현과 합쳐 실행 파일을 만듭니다. 이 구분을 알면 에러 메시지의 출처를 짐작할 수 있습니다. expected ';'처럼 문법에 관한 것은 컴파일 단계, undefined reference to ...처럼 무엇을 찾을 수 없다는 것은 링크 단계의 에러입니다. 흔한 예로 g++ 대신 gcc hello.cpp로 빌드하면 컴파일은 되지만 C++ 표준 라이브러리를 링크하지 않아 undefined reference to std::cout 류의 에러가 쏟아집니다. 궁금하다면 g++ -E hello.cpp | wc -l로 전처리 결과가 몇 줄이 되는지 확인해 보세요.

첫 수정 실습

여러 줄 출력과 변수

#include <iostream>

int main() {
    int year = 2026;
    std::cout << "첫 번째 줄\n";
    std::cout << "올해는 " << year << "년입니다.\n";
    return 0;
}

변수와 자료형은 다음 글에서 int, double, std::string 등으로 이어서 다룹니다.

사용자 입력 받기

#include <iostream>

int main() {
    int age = 0;
    std::cout << "나이를 입력하세요: ";
    std::cin >> age;
    std::cout << "입력한 나이는 " << age << "살입니다.\n";
    return 0;
}

std::cin은 표준 입력(키보드)에서 값을 읽고, >>는 공백을 기준으로 다음 토큰을 읽어 변수의 타입으로 변환하는 스트림 추출 연산자입니다.

직접 실행해 보면 두 가지 동작에 놀라게 됩니다. 나이 자리에 abc처럼 숫자가 아닌 값을 입력하면 age에는 0이 들어가고(C++11부터 읽기 실패 시 0을 저장), 이후의 std::cin 읽기는 모두 무시됩니다. 입력 스트림이 실패 상태가 되었기 때문입니다. 또 std::cin >> name으로 이름을 받으면 홍 길동처럼 공백이 들어간 입력에서 홍까지만 읽힙니다. 한 줄 전체를 받으려면 std::getline(std::cin, name)을 쓰며, 이 차이와 실패 처리 방법은 변수와 자료형 글의 입력 검증 예제에서 이어서 다룹니다.

한글이 깨질 때

Windows에서 한글을 출력하면 안녕하세요가 ?덈뀞?섏꽭?? 같은 문자로 보이는 경우가 있습니다. 소스 파일은 UTF-8로 저장했는데 콘솔이 한국어 Windows의 기본 인코딩인 CP949로 해석하기 때문입니다. 소스 파일 인코딩, 컴파일러가 실행 파일에 넣는 문자열 인코딩, 콘솔 출력 인코딩의 세 가지를 맞춰야 합니다.

  • MinGW의 g++는 기본적으로 UTF-8 소스를 UTF-8 문자열로 그대로 넣으므로, 콘솔만 chcp 65001로 UTF-8로 바꾸면 됩니다.
  • MSVC는 BOM 없는 소스를 시스템 코드 페이지로 읽으므로 /utf-8 옵션으로 소스와 실행 문자 집합을 UTF-8로 지정한 뒤, 콘솔도 chcp 65001로 맞춥니다.
  • Mac과 Linux의 터미널은 기본이 UTF-8이라 보통 별도 설정이 필요 없습니다.

인터넷에서 자주 보이는 std::locale::global(std::locale(""))은 이 문제의 해결책이 아닙니다. 이미 만들어진 std::cout의 로케일은 바뀌지 않고, Linux에서 LANG이 설치되지 않은 로케일을 가리키면 locale::facet::_S_create_c_locale name not valid 예외로 프로그램이 시작하자마자 종료되기도 합니다.

편집기 설정 (VS Code, Visual Studio)

컴파일러만으로도 개발할 수 있지만, IDE나 편집기를 쓰면 자동 완성, 에러 표시, 디버깅이 훨씬 편해집니다.

VS Code

VS Code를 설치한 뒤 확장(Extensions) 탭에서 Microsoft의 C/C++ 확장을 설치합니다. C/C++ Extension Pack을 설치하면 CMake Tools 확장도 함께 설치됩니다.

자동 완성(IntelliSense)이 동작하지 않거나 #include에 빨간 밑줄이 생기면 대부분 컴파일러 경로가 지정되지 않은 경우입니다. Ctrl+Shift+P에서 “C/C++: Edit Configurations”를 실행해 compilerPath를 설정합니다. MSYS2라면 C:/msys64/ucrt64/bin/g++.exe와 intelliSenseMode의 windows-gcc-x64를, macOS라면 /usr/bin/clang++를, Linux라면 /usr/bin/g++를 지정합니다. 빌드와 디버깅 설정(tasks.json, launch.json)은 C++ 실전 가이드 #3: VS Code C++ 개발 환경 설정에서 다룹니다.

Visual Studio

Visual Studio에서는 파일 → 새로 만들기 → 프로젝트에서 “콘솔 앱”을 선택하면 바로 C++ 프로젝트가 만들어집니다. 프로젝트 속성의 C/C++ → 언어에서 C++ 언어 표준을 원하는 버전으로 지정하고, Ctrl+Shift+B로 빌드, F5로 디버깅을 시작합니다.

도구장점적합한 경우
VS Code가벼움, 크로스 플랫폼, 확장성Linux/macOS 개발, 터미널 중심 작업
Visual Studio디버거 기능이 가장 풍부함, Windows SDK 통합Windows 전용 앱, 게임 개발
CLionCMake 통합, 리팩토링CMake 기반의 큰 프로젝트

빌드 도구 맛보기 (Make, CMake)

파일이 몇 개일 때는 g++ main.cpp utils.cpp -o app처럼 직접 컴파일해도 되지만, 파일이 늘어나면 바뀐 파일만 다시 컴파일해 주는 빌드 도구가 필요합니다.

Makefile은 무엇을 어떤 명령으로 빌드할지 적어 두는 파일입니다. 명령 줄 앞의 들여쓰기는 반드시 탭이어야 합니다.

CXX = g++
CXXFLAGS = -std=c++17 -Wall -Wextra -g

app: main.cpp utils.cpp
	$(CXX) $(CXXFLAGS) main.cpp utils.cpp -o app

clean:
	rm -f app *.o

make로 빌드하고 make clean으로 정리합니다. 이 예제는 파일 하나만 바뀌어도 전체를 다시 컴파일하므로, 증분 빌드를 하려면 .o 파일을 중간 타겟으로 나눠야 합니다.

CMake는 Windows, macOS, Linux에서 같은 설정 파일로 각 플랫폼의 빌드 파일(Makefile, Ninja, Visual Studio 솔루션 등)을 생성합니다. 팀 프로젝트와 크로스 플랫폼 프로젝트에서는 사실상 표준입니다. 자세한 내용은 C++ 실전 가이드 #4: CMake 입문에서 다룹니다.

cmake_minimum_required(VERSION 3.16)
project(MyApp LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)

add_executable(myapp main.cpp utils.cpp)
cmake -S . -B build
cmake --build build

Visual Studio용 솔루션을 만들려면 -G "Visual Studio 17 2022" -A x64처럼 설치된 Visual Studio 버전에 맞는 제너레이터를 지정하고, 빌드할 때 cmake --build build --config Release로 구성을 고릅니다.


설치 없이 온라인에서 실행하기

로컬에 컴파일러를 설치하기 전에 짧은 코드를 실행해 보고 싶다면 웹 기반 컴파일러를 쓸 수 있습니다.

사이트특징
Compiler Explorer여러 컴파일러·버전의 출력과 어셈블리를 비교
WandboxGCC·Clang 여러 버전으로 실행
OnlineGDB브라우저에서 디버거 사용
cpp.sh가입 없이 바로 실행

표준 기능이 특정 컴파일러 버전에서 지원되는지 확인할 때도 유용하지만, 여러 파일로 된 프로젝트를 개발하려면 로컬 환경이 필요합니다.


자주 겪는 에러와 해결법

“g++을 찾을 수 없음” / “g++: command not found”

컴파일러가 설치되지 않았거나 설치 폴더가 PATH에 없습니다. Windows MSYS2라면 C:\msys64\ucrt64\bin을 PATH에 추가하고 터미널을 새로 엽니다. macOS는 xcode-select --install, Ubuntu/Debian은 sudo apt install build-essential로 설치합니다. 실제로 어떤 g++이 잡히는지는 macOS/Linux에서 which g++, Windows CMD에서 where g++로 확인합니다.

”cl은(는) 내부 또는 외부 명령… 이 아닙니다” (Windows MSVC)

일반 CMD에는 MSVC의 경로와 환경 변수가 설정되어 있지 않습니다. 시작 메뉴의 “Developer Command Prompt for VS”를 쓰거나, 일반 CMD에서 vcvars64.bat을 먼저 실행합니다. 경로는 Visual Studio 버전과 에디션에 따라 다르며, Visual Studio 2022 Community 기준으로는 다음과 같습니다.

"C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
cl /EHsc hello.cpp

”iostream: No such file or directory”

C++ 표준 라이브러리 헤더를 찾지 못한 경우입니다. 흔한 원인은 두 가지입니다. 하나는 파일 확장자가 .c여서 C 코드로 컴파일되는 경우로, C에는 iostream이 없으므로 확장자를 .cpp로 바꿉니다. 다른 하나는 C++ 표준 라이브러리 개발 파일이 없는 경우로, Ubuntu에서 g++ 없이 clang만 설치했을 때 자주 나타납니다. 이때는 build-essential이나 libstdc++-<버전>-dev 패키지를 설치합니다.

참고로 gcc hello.cpp처럼 C++ 파일을 gcc로 빌드하면 헤더는 찾지만 C++ 표준 라이브러리를 링크하지 않아, 컴파일이 아니라 링크 단계에서 undefined reference to std::cout 같은 에러가 납니다. C++ 코드는 항상 g++이나 clang++로 빌드합니다.

”Permission denied” (Linux/macOS)

./hello를 실행했는데 권한 에러가 난다면 실행 파일에 실행 권한이 없는 경우입니다. 컴파일러가 만든 파일에는 보통 실행 권한이 붙지만, 다른 곳에서 복사해 오거나 noexec로 마운트된 위치(일부 USB 드라이브나 공유 폴더)에 있으면 이 에러가 납니다. chmod +x hello로 권한을 주거나 홈 디렉터리 안에서 빌드합니다.

”undefined reference to …”

컴파일은 끝났지만 링크 단계에서 함수나 변수의 정의를 찾지 못한 경우입니다. 여러 파일로 된 프로그램이면 모든 .cpp 파일을 명령에 나열했는지 확인하고, 외부 라이브러리를 쓰면 -l 옵션으로 링크합니다. 스레드를 쓰는 코드는 -lpthread 대신 -pthread 옵션을 쓰는 것이 정확합니다.

g++ main.cpp utils.cpp -o hello
g++ -pthread main.cpp -o hello

헤더에 함수 정의를 넣고 여러 .cpp에서 include하면 반대로 multiple definition of ... 에러가 납니다. 헤더에는 선언만 두고 정의는 .cpp에 두거나, 정의에 inline을 붙입니다.

”cannot find -lstdc++”

주로 Ubuntu에서 Clang을 쓸 때 나타납니다. Clang은 시스템에 설치된 가장 새 GCC 버전의 libstdc++를 찾는데, 해당 버전의 GCC 디렉터리만 있고 libstdc++-<버전>-dev가 없으면 링크에 실패합니다. clang++ -v hello.cpp의 출력에서 “Selected GCC installation” 줄로 어느 버전을 찾는지 확인하고, 그 버전의 libstdc++-<버전>-dev 패키지를 설치합니다.

“‘bits/stdc++.h’ file not found”

bits/stdc++.h는 GCC의 표준 라이브러리(libstdc++)에만 있는 비표준 헤더입니다. 그래서 Linux GCC나 MinGW에서는 컴파일되지만, macOS의 Apple Clang(libc++)이나 MSVC에서는 에러가 납니다. 코딩 테스트용 코드가 아니라면 필요한 표준 헤더를 하나씩 include하는 것이 좋습니다.

#include <iostream>
#include <vector>
#include <algorithm>

Windows에서 한글 출력이 깨짐

소스 파일 인코딩, 컴파일러가 소스를 읽는 인코딩, 콘솔 코드 페이지가 서로 다르면 한글이 깨집니다. 소스를 UTF-8로 저장하고, MSVC라면 /utf-8 옵션으로 컴파일하며(GCC는 기본으로 UTF-8로 읽습니다), 프로그램 시작 시 콘솔 출력 코드 페이지를 UTF-8로 바꿉니다.

#include <iostream>
#ifdef _WIN32
#include <windows.h>
#endif

int main() {
#ifdef _WIN32
    SetConsoleOutputCP(CP_UTF8);
#endif
    std::cout << "한글 출력" << '\n';
    return 0;
}

segmentation fault

컴파일 에러가 아니라 실행 중에 잘못된 메모리에 접근한 것입니다(널 포인터 역참조, 배열 범위 초과 등). g++ -g -O0으로 디버그 정보를 넣어 빌드한 뒤 gdb ./hello에서 run, 크래시가 나면 bt로 호출 스택을 확인합니다. -fsanitize=address를 붙여 빌드하면 범위 초과나 해제 후 사용을 일어난 줄에서 바로 알려 줍니다.


CI와 Docker에서 같은 환경 맞추기

로컬에서는 빌드되는데 CI에서만 실패한다면, 대부분 컴파일러 버전이나 기본 C++ 표준이 다르기 때문입니다. 표준은 -std=c++17처럼 항상 명시하고, 가능하면 CI와 로컬의 컴파일러 버전을 맞춥니다. CMake 프로젝트라면 CXX=g++-12 cmake -S . -B build처럼 컴파일러를 지정해 구성할 수 있습니다.

GitHub Actions의 Ubuntu 러너에는 GCC와 CMake가 미리 설치되어 있어 바로 cmake나 g++을 실행할 수 있습니다. Windows 러너에는 Visual Studio가 설치되어 있고, MinGW가 필요하면 msys2/setup-msys2 액션으로 설치합니다.

빌드 환경을 완전히 고정하려면 Docker 이미지를 씁니다. 빌드용 이미지에서 컴파일하고, 실행용 이미지에는 결과물만 복사하는 멀티 스테이지 빌드가 일반적입니다.

FROM ubuntu:24.04 AS builder
RUN apt-get update && apt-get install -y build-essential cmake
WORKDIR /src
COPY . .
RUN cmake -S . -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build

FROM ubuntu:24.04
COPY --from=builder /src/build/myapp /usr/local/bin/
CMD ["myapp"]

실행용 이미지가 빌드용 이미지와 같은 배포판·버전이어야 실행 파일이 의존하는 libstdc++와 glibc 버전이 맞습니다. 빌드 옵션은 개발 중에는 -g -O0 -Wall -Wextra로 디버깅하기 쉽게, 배포할 때는 -O2 -DNDEBUG(CMake의 Release 빌드 타입은 -O3 -DNDEBUG)로 최적화하고 assert를 끕니다.


같이 보면 좋은 글


다음 글

환경 구축이 끝났다면 다음 글 C++ 실전 가이드 #2: 컴파일러 비교에서 GCC, Clang, MSVC의 특징과 차이를 살펴봅니다.