Menu

C 프로그램 컴파일하고 실행하는 방법 (gcc 단계별 정리)

.c 파일을 실행 가능한 프로그램으로 바꾸는 법: gcc 명령, 전처리·컴파일·링크가 실제로 하는 일, 첫날부터 써야 할 옵션들, 그리고 실패했을 때 오류를 읽는 법.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

C는 컴파일 언어입니다. 컴파일러가 소스를 여러분의 CPU와 운영체제에 맞는 기계어로 번역하기 전까지는 아무것도 실행되지 않습니다. 이 단계는 명령 하나로 끝나지만, 그 명령이 무엇을 하는지 알면 알쏭달쏭하던 오류 메시지 대부분이 뻔해집니다.

두 개의 명령

다음을 hello.c로 저장하세요.

#include <stdio.h>

int main(void) {
    printf("Hello, C!\n");
    return 0;
}

그리고 같은 폴더에서 이렇게 실행합니다.

gcc hello.c -o hello
./hello

첫 번째 명령이 컴파일하고, 두 번째가 그 결과를 실행합니다. 윈도우에서는 명령 프롬프트에서 hello, PowerShell에서 .\hello로 실행합니다. ./ 접두사가 없는데, 윈도우는 기본적으로 현재 디렉터리를 검색하고 유닉스 셸은 의도적으로 그러지 않기 때문입니다.

-o hello를 빼면 gcc는 출력 이름을 a.out(또는 a.exe)으로 정합니다. 오래된 튜토리얼이 ./a.out으로 끝나는 이유죠. 출력 이름은 항상 지정하세요. 네 글자를 더 쓰고 혼란을 덜 수 있습니다.

같은 프로그램을 브라우저 편집기로 옮긴 것이 아래에 있습니다. 두 단계를 대신 처리해 줍니다.

"컴파일"이 실제로 하는 일

gcc hello.c -o hello는 한 단계처럼 보입니다. 실제로는 네 단계이고, 각 단계마다 고유한 종류의 오류로 실패할 수 있습니다.

1. 전처리. C가 컴파일되기 전에, 전처리기가 #으로 시작하는 모든 줄을 처리합니다. #include <stdio.h>는 말 그대로 그 헤더 파일의 내용으로 대체되고, #define 매크로는 확장되며, #ifdef 블록은 남거나 삭제됩니다. 결과물은 # 줄이 하나도 남지 않은 거대한 C 파일 하나입니다. 직접 확인해 볼 수 있습니다.

gcc -E hello.c

수백 줄이 출력되는데, 거의 전부가 붙여넣어진 stdio.h입니다.

2. 컴파일. 전처리된 C가 파싱되고, 타입 검사를 거쳐, 여러분의 CPU용 어셈블리로 번역됩니다. 문법 오류, 타입 오류, 경고가 나오는 곳이 여기입니다.

3. 어셈블. 어셈블리가 오브젝트 파일이 됩니다. 다른 파일의 함수를 호출하는 자리는 빈칸으로 남겨 둔 기계어입니다.

gcc -c hello.c   # hello.o를 만들고 링크 전에 멈춤

4. 링크. 오브젝트 파일들이 C 표준 라이브러리와 함께 꿰어지고, 모든 빈칸이 실제 주소로 채워지며, 결과로 실행 파일이 나옵니다. "undefined reference" 오류가 나오는 곳이 여기입니다.

실용적인 교훈은 이렇습니다. 컴파일 오류는 소스의 특정 줄을 가리키지만 링크 오류는 그러지 않습니다. 링크는 모든 줄이 이미 받아들여진 뒤에 일어나기 때문입니다.

첫날부터 쓸 만한 옵션들

옵션 없이 쓰는 gcc file.c -o prog는 위험한 코드를 아무 말 없이 많이 받아들입니다. 네 가지 옵션이 그걸 바꿉니다.

gcc -std=c17 -Wall -Wextra -g hello.c -o hello
  • **-Wall**은 흔한 경고들을 켭니다. 이름과 달리 "모든" 경고는 아니고, 합리적인 묶음입니다.
  • **-Wextra**는 쓰이지 않는 매개변수나 일부 비교 실수를 포함해 경고를 더 추가합니다.
  • **-std=c17**은 언어 표준을 고정해서 어떤 머신에서든 코드가 같은 의미를 갖게 합니다. 오래된 자료를 따라 하고 있다면 -std=c99를 쓰세요.
  • **-g**는 디버깅 정보를 남겨서, 뭔가 크래시가 났을 때 gdblldb가 실제 소스 줄을 보여 줄 수 있게 합니다.

결국 필요해질 두 가지가 더 있습니다.

  • **-O2**는 릴리즈 빌드용 최적화를 켭니다. 배우는 동안에는 꺼 두세요. 최적화된 코드는 디버깅하기 어렵고 경고도 달라질 때가 있습니다.
  • -fsanitize=address,undefined(gcc와 clang)는 범위를 벗어난 읽기나 미정의 동작이 일어나는 순간 명확한 메시지와 함께 프로그램을 중단시킵니다. C를 배울 때 가장 유용한 옵션 하나를 꼽으라면 이것입니다.

경고를 직접 겪어 보세요. 아래 프로그램은 컴파일도 되고 실행도 되지만 진짜 버그가 두 개 있습니다.

count에는 값이 한 번도 들어가지 않으므로, 프로그램은 그 자리에 있던 바이트를 그대로 출력합니다. -Wall을 켜면 컴파일러가 그렇다고 말해 줍니다. 'count' is used uninitialized. C에서 경고는 거의 절대 잡음이 아닙니다. 아직 터지지 않은 오류로 여기세요.

파일을 여러 개 컴파일하기

실제 프로그램은 여러 파일로 나뉩니다. 전부 gcc에 넘기세요.

gcc -std=c17 -Wall main.c utils.c -o myprog

또는 각각 따로 컴파일하고 마지막에 링크할 수도 있습니다. 파일 하나가 바뀌었다고 전부 다시 빌드하지 않도록 빌드 시스템이 하는 방식이 이것입니다.

gcc -c main.c      # -> main.o
gcc -c utils.c     # -> utils.o
gcc main.o utils.o -o myprog

어떤 라이브러리는 명시적인 링크 옵션이 필요합니다. 초보자가 반드시 만나는 것이 수학 라이브러리입니다.

gcc calc.c -o calc -lm

-lm 없이 math.hsqrt를 쓰면 컴파일은 멀쩡히 되고, 링크 단계에서 *undefined reference to sqrt*로 실패합니다. 선언은 헤더에 있었지만 실제 코드는 아무도 요청하지 않은 라이브러리 안에 있었던 것이죠.

오류 읽기

C의 오류는 무뚝뚝하지만 일관성이 있습니다. 세 가지 예시면 초반에 보게 될 대부분을 설명할 수 있습니다.

세미콜론 누락은 컴파일러가 계속 읽어 나가기 때문에 다음 줄에서 보고됩니다.

hello.c:5:5: error: expected ';' before 'return'

고쳐야 할 곳은 5번 줄이 아니라 4번 줄입니다. 오류가 멀쩡해 보이는 줄을 가리킬 때마다 그 앞 줄을 확인하세요.

헤더 누락은 분명 철자를 맞게 쓴 함수에 대한 수수께끼처럼 보입니다.

hello.c:4:5: warning: implicit declaration of function 'printf'

컴파일러가 printf의 선언을 본 적이 없다는 뜻이니, #include <stdio.h>를 빠뜨린 것입니다. C99 이후로 이것은 단순한 경고가 아니라 오류입니다.

링크 실패에는 줄 번호가 아예 없습니다.

/usr/bin/ld: main.o: in function `main':
main.c:(.text+0x1a): undefined reference to `helper'

컴파일러는 helper가 어딘가에 있다는 여러분의 말을 믿었고, 링커가 찾아봤더니 없었던 겁니다. 정의를 아예 쓰지 않았거나, 이름 철자가 다르거나, 그 정의가 들어 있는 파일을 gcc에 넘기지 않은 것입니다.

항상 첫 번째 오류를 고치세요. C의 오류는 연쇄됩니다. 잘못된 줄 하나가 메시지 스무 개를 만들어 내고, 첫 번째를 고치면 나머지 열아홉 개가 사라집니다.

종료 코드

mainreturn 0은 장식이 아닙니다. 프로그램의 종료 상태이며, 셸이 그 값을 읽을 수 있습니다.

실행한 뒤 macOS/리눅스에서는 echo $?, 윈도우에서는 echo %errorlevel%을 치면 그 숫자가 출력됩니다. 스크립트와 빌드 도구는 이 값을 보고 계속 진행할지 결정합니다. 규약은 절대적입니다. 0은 성공이고, 0이 아닌 값은 여러분이 정하는 오류 코드입니다. 이름을 쓰는 쪽이 좋다면 stdlib.hEXIT_SUCCESSEXIT_FAILURE를 정의해 둡니다.

C99부터는 mainreturn 없이 끝까지 흘러 나가면 return 0으로 취급됩니다. 하지만 직접 써 주는 쪽이 더 분명하고, 다른 모든 함수에서는 반드시 필요합니다.

자주 묻는 질문

C 프로그램은 어떻게 컴파일하고 실행하나요?

코드를 hello.c로 저장한 뒤 gcc hello.c -o hello로 빌드하고 ./hello로 실행합니다(윈도우에서는 그냥 hello). gcc를 찾을 수 없다면 먼저 컴파일러를 설치해야 합니다.

gcc의 -o 옵션은 무슨 일을 하나요?

-o는 출력 파일의 이름을 지정합니다. gcc hello.c -o hellohello라는 실행 파일을 만듭니다. -o가 없으면 gcc는 a.out(윈도우에서는 a.exe)에 결과를 씁니다. 오래된 튜토리얼이 ./a.out을 실행하는 이유가 바로 이것입니다.

gcc 옵션 중에 항상 써야 하는 건 무엇인가요?

gcc -std=c17 -Wall -Wextra -g yourfile.c -o yourprog입니다. -Wall -Wextra는 실제 버그를 잡아내는 경고를 켜고, -std=c17은 언어 버전을 고정하며, -g는 디버거가 원본 소스를 보여줄 수 있도록 디버그 심볼을 남깁니다. 릴리즈 빌드에서 속도가 필요하면 -O2를 추가하세요.

C에서 "undefined reference to"는 무슨 뜻인가요?

링커 오류입니다. 컴파일러는 그 함수 호출을 받아들였지만, 그 함수의 정의를 어디에서도 찾지 못했다는 뜻입니다. 흔한 원인은 이름 오타, 두 번째 .c 파일을 컴파일하지 않은 경우, 또는 수학 라이브러리를 링크하지 않고(-lm) 수학 함수를 쓴 경우입니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기