C 프로그램을 만드는 과정

컴퓨터는 인간이 하고 싶어 하는 일들을 대신 해주는 존재입니다. 인터넷을 자유로이 돌아다니고, 멀리 떨어진 상대와 실시간으로 대화하는 일처럼, 상상 속에만 머물던 생각들을 현실로 끄집어내는 일을 도와줍니다. 어떻게 이런 일이 가능한 걸까요? 정답은 바로 프로그램Program 에 있습니다. 이 글에서는 C를 통해 프로그램을 만드는 과정을 비교적 가까이 들여다 봅니다.


프로그램이란 무엇인가?

우리는 프로그램을 통해 컴퓨터와 소통한다. 그렇다면 프로그램은 무엇이고, 왜 필요한 걸까?

절차로 쪼개기

사용자가 프로그램과 소통하는 창구를 인터페이스Interface 라고 부른다. 스마트폰의 터치스크린이나 전원 버튼, 노트북의 키보드나 터치패드처럼 사용자와 소통할 수 있다면 인터페이스의 형태는 중요하지 않다.

사용자의 관점에서 봤을 때, 인터페이스는 사용자와 프로그램을 연결하고, 프로그램은 인터페이스를 통해 들어온 입력을 인식하고 이에 맞추어 적절한 결과를 내놓는다. 어떤 입력을 어떻게 처리해서 어떤 결괏값을 돌려줄지는 프로그램이 어떻게 구성되어 있는가에 따라 달라진다. 프로그램이란, 사용자가 원하는 일을 처리할 수 있도록 프로그래밍 언어를 사용하여 올바른 수행 절차를 표현해 놓은 명령어들의 집합이다. 명령어들을 조합해 어떤 작업을 소화하는 절차를 기록한 것이 바로 프로그램이며, 그 기록은 작업을 위해 필요한 데이터와 함께 SSD(Solid-State Drive) 와 같은 기억 장치에 저장되어 있다.

프로그램이 필요한 이유

엄밀히 말하자면, 프로그램은 반드시 있어야 하는, 그래서 없으면 컴퓨터와 소통하지 못하는 그런 개념은 아니다. 물론 프로그램 없이 소통하려면, 원하는 일을 수많은 명령으로 아주 잘게 쪼개서 컴퓨터가 소화할 수 있게 바꾼 다음, 1과 0만으로 이루어진 기계어를 통해 각 명령을 컴퓨터에 전달하면 된다. 생각만 해도 어지럽기 그지없다. 이걸 실제로 해내려면 우리가 컴퓨터에 대해 모르는 것이 없는 지경에 도달해야 할뿐더러, 매우 비효율적이다.

프로그램은 이 절차를 아주 간단하게 만들어 주기 때문에 오늘날 없어서는 안 되는 것이다. 인터페이스가 우리와 프로그램을 연결하는 것처럼 프로그램은 우리가 원하는 일을 하기 위한 명령과 기계어를 연결한다. 이것을 다른 말로 고수준high-level 언어와 저수준low-level 언어를 연결한다고 하기도 한다. 고수준은 왜 고수준이고, 저수준은 왜 저수준일까? 이어지는 글에서는 수준level 이라는 개념을 조금 더 가까이 들여다 볼 것이다.

Level

classification-of-programming-language.png

프로그래밍에서 말하는 수준level 은 주로 언어에 적용되는 개념이다. 프로그래밍 언어는 키보드, 디스플레이, CPU와 같은 컴퓨터의 물리적 구성요소인 하드웨어Hardware 를 다루기 위해 제작되었다. 하드웨어를 어떻게 다룰 것인지에 맞게 다양한 프로그래밍 언어가 존재하고, 사람과 하드웨어 중 어느 쪽에 더 맞춰져 있는지에 따라 이들을 하나의 스펙트럼으로 묶을 수 있다. 어떤 프로그래밍 언어가 사람에 더 맞춰져 있다면, 사람의 언어와 소통 방식에 가깝게 구성되어 있을 것이고, 하드웨어에 더 맞춰져 있다면, 하드웨어의 언어와 소통 방식에 더 가깝게 구성되어 있을 것이다. 이때, 하드웨어 쪽에 더 가까운 언어를 저수준 언어low-level language 라 부르고, 인간 쪽에 더 가까운 언어를 고수준 언어high-level language 라 부른다.

이 글에서는 컴퓨터와 인간의 언어를 위의 스펙트럼을 기준으로 살펴볼 것이다.

기계어 코드/기계 수준 명령

컴퓨터의 중심에는 중앙 처리 장치Central Processing Unit 가 있다. 그래픽 처리 장치Graphic Processing Unit 와 함께 컴퓨터가 필요로 하는 계산을 책임지는 핵심 부품이다. 그런데 CPU와 GPU는 오직 이진수 (0, 1) 만 읽고 처리할 수 있어서, 컴퓨터가 어떤 일을 하도록 명령을 내리려면 결국 이진수로 명령을 작성해야 한다. 이렇게 이진수로 작성된 명령을 기계어 코드Machine Code 또는 기계 수준 명령Machine-level Instruction 이라고 한다.

어셈블리 코드

기계어 코드는 컴퓨터가 직접 이해할 수 있기 때문에 아주 빠르지만, 오직 이진수로만 구성되기 때문에 기계어 코드가 어떤 명령인지 해석하기 어렵다는 단점이 존재한다. 이를 보완하기 위해 등장한 것이 바로 어셈블리 코드Assembly Code 이다. 어셈블리 코드는 기계어 코드를 보다 인간의 언어에 가깝게 번역한 결과물로 명령이 더 잘 보이고, 매크로, 레지스터 변수, 주석 등 추가적인 기능을 지원하기도 한다.

프로그래밍 언어

인간의 생각을 컴퓨터에 전달하기 위해 사용하는 가장 일반적인 언어로, 기계어 코드와 동일한 구조를 가지는 어셈블리 코드보다 더욱 인간의 사고방식에 가까운 추상적인 구조로 되어 있다. 무엇보다 기계어나 어셈블리 코드와 달리 특정 하드웨어에서만 실행되지 않는다. 프로그래밍 언어는 하드웨어와 독립적이며, 이와 같은 범용성은 프로그래밍 언어가 널리 쓰이는 주된 이유 중 하나다.

자연어

자연어는 인간이 서로 간의 의사소통을 위해 사용하는 인간의 언어를 의미한다. 언어 하면 우리 대다수의 머릿속에 가장 먼저 떠오르는 그것이다.

C부터 기계어까지, C 프로그램의 컴파일 과정

오늘날 모든 프로그래밍 언어는 실행되기 위해서 기계어로 변환되어야 합니다. 이 과정 전체를 컴파일compile 이라 부릅니다. C는 여기에 앞서 컴파일러가 소화하기 좋게 소스 코드를 변형시키는 전처리preprocessing 과정이 포함되어, 이를 통해 전처리 > 컴파일 > 어셈블 > 링킹이라는 C 프로그램의 컴파일 파이프라인이 완성됩니다. 이어지는 글에서는 각 과정을 간략하게 살펴보겠습니다.

전처리

전처리Preprocessing 는 C의 강력한 구성요소이며, C를 다른 프로그래밍 언어와 구분시키는 수많은 특징 중 하나다. C로 작성된 소스 코드에서 전처리기 지시자Preprocessor Directive 가 바로 전처리 과정에서 다루는 부분이다. 전처리는 전처리기Preprocessor 가 담당하고, 전처리 과정을 거치면 소스 코드에서 #으로 시작하는 코드들이 전부 처리되어 컴파일러가 직접 컴파일할 수 있는 컴파일 단위Compilation Unit / 변환 단위Translation Unit 가 만들어진다.

컴파일

컴파일Compile 의 의미는 세 가지 범위를 가지고 있다. 넓게는 소스 코드를 통해 최종 결과물을 만들어내는 과정 전체를 빌드Building 대신 컴파일이라 부르거나, 링킹을 제외한 나머지 과정 전체를 컴파일이라 부르기도 하고, 좁게는 컴파일 단위/변환 단위를 어셈블리 코드로 변환하는 과정을 컴파일이라 한다. 이 단계는 좁은 의미의 컴파일로, 컴파일러Compiler 가 이 단계를 맡아 컴파일 단위 / 변환 단위를 구문 분석하고 이를 코드가 실행될 하드웨어나 CPU에 맞는 어셈블리 코드로 변환한다.

어셈블리

어셈블리Assembly 단계에서는 어셈블리 코드를 기계어 코드/기계 수준 명령으로 변환한다. 이때부터는 코드 자체가 하드웨어에 의존적이기 때문에 각 아키텍처에 맞는 고유의 어셈블러Assembler 가 존재하고, 이 어셈블러가 아키텍처에 맞는 어셈블리 코드를 해당 아키텍처의 기계어로 변환하는 과정을 담당한다. 이렇게 기계어로 변환된 파일을 재배치 가능한 목적 파일Relocatable Object File 혹은 중간 목적 파일Intermediate Object File 이라고 한다.

링크

기계어로 된 파일이 나왔지만, 아직 이 파일은 실행 가능한 파일이 아니다. 최종 결과물을 얻으려면, 실행을 위해 필요한 목적 파일들을 연결해서 실행 가능한 목적 파일Executable Object File 을 만들어야 한다. 이 단계를 링크Linking 단계 라고 한다. 어셈블리 단계를 통해 만들어진 목적 파일 이외에 이 단계에서 필요한 목적 파일들은 자주 사용하기 때문에 이미 만들어져 있는 목적 파일들인 경우가 대부분이며, 만약 파일이 여러 개인 프로젝트를 빌드한다면 각 파일의 소스 코드에서 산출된 목적 파일들 또한 이 단계에서 연결한다.


참고 자료 & 더보기

참고 자료