지금까지는 아무것도 모른채 마구잡이로 쓴 코드가 잘 돌아갔다면 이제부터는 연습과 응용을 통해 동작 원리를 이해할 수 있을 것입니다.
우선 첫 수업에 봤던 예제를 다시 살펴보며 지금 사용하는 방법이 그때 우리가 사용한 방법과 어떻게 다른지 알아봅시다.
첫 수업에 봤던 C코드를 다시 봐보겠습니다.
- stdio.h 라이브러리 이용
- int main(void){} : 시작하기라는 의미의 함수 정의
- prinf("문자열") : 출력하기 + 만약 변수를 쓰고 싶으면 ("%i,s 등"(int,string 등),변수이름)
printf를 이용할 때 #include <stdio.h> 필요
: 헤더파일, C언어로 작성, printf의 프로토타입 소유 -> clang -> printf에 대한 정보 전달 (머신코드)
코드를 clang hello.c로 컴파일하고 ./a.out 명령으로 프로그램을 실행할 때 이 과정은 컴퓨터가 이해하는 0과 1로 가득찬 파일 a.out을 생성하여 실행 가능하게 합니다.
$clang hello.c(clang이라는 컴파일러, hello.c는 c로 작성된 파일) -> a.out이라는 파일로 컴파일된다 (머신코드) -> ./a.out 명령어로 실행하면 컴퓨터가 실행
만약 a.out과 다른 이름(hello)으로 컴파일을 하고 싶다면?
: 아래와 같이 명령행 인자를 추가해야줘야 합니다.
clang -o hello hello.c
우리는 또한 CS50 라이브러리를 사용해 보았습니다.
이 처럼 CS50 라이브러리를 사용한 프로그램을 컴파일 할때는 clang에 또 하나의 프로그램(-lcs50)이 필요했습니다.
그래야 clang이 실행되었습니다.
clang -o hello hello.c -lcs50
이는 clang에게 CS50 라이브러리에 있는 모든 0과 1들을 여기에 연결하라는 의미입니다.
더 간단히는, 이 전에 배웠듯이 make 프로그램을 이용하면 이 모든 컴파일 과정을 자동으로 처리할 수 있습니다.
make나 clang을 사용해서 프로그램을 실행할 때 아래 네 개의 단계를 거칩니다.
- 전처리
- 컴파일링
- 어셈블링
- 링킹
우리가 명령어를 실행할 때 정확히 어떤 일이 일어나는지 알아보도록 하겠습니다.
컴파일의 전체 과정은 네 단계로 나누어볼 수 있습니다. 그 중 첫 번째 단계는 전처리인데, 전처리기에 의해 수행됩니다.# 으로 시작되는 C 소스 코드는 전처리기에게 실질적인 컴파일이 이루어지기 전에 무언가를 실행하라고 알려줍니다.
#으로 시작되는 C 소스코드
전처리기에게 실질적인 컴파일이 이루어지기 전에 실행할 것을 알려준다
예를 들어,
#include : 전처리기에게 다른 파일의 내용을 포함시키라고 알려줍니다. 프로그램의 소스 코드에 #include 와 같은 줄을 포함하면, 전처리기는 새로운 파일을 생성하는데 이 파일은 여전히 C 소스 코드 형태이며 stdio.h 파일의 내용이 #include 부분에 포함됩니다.
전처리기가 전처리한 소스 코드를 생성하고 나면 그 다음 단계는 컴파일입니다. 컴파일러라고 불리는 프로그램 : C 코드를 어셈블리어라는 저수준 프로그래밍 언어로 컴파일합니다.
C코드 -> 컴파일러 -> 어셈블리어(저수준 프로그래밍 언어)
어셈블리
컴파일
소스 코드가 어셈블리 코드로 변환되면, 다음 단계인 어셈블 단계로 어셈블리 코드를 오브젝트 코드로 변환시키는 것입니다. 컴퓨터의 중앙처리장치가 프로그램을 어떻게 수행해야 하는지 알 수 있는 명령어 형태인 연속된 0과 1들로 바꿔주는 작업이죠. 이 변환작업은 어셈블러라는 프로그램이 수행합니다.
어셈브릴 코드 -> 어셈블러 -> 오브젝트 코드(CPU가 읽을 수 있는 0,1의 형태로)
만약 프로그램이 (math.h나 cs50.h와 같은 라이브러리를 포함해) 여러 개의 파일로 이루어져 있어 하나의 오브젝트 파일로 합쳐져야 한다면 링크라는 컴파일의 마지막 단계가 필요합니다.
링커는 여러 개의 다른 오브젝트 코드 파일을 실행 가능한 하나의 오브젝트 코드 파일로 합쳐줍니다.
예를 들어, 컴파일을 하는 동안에 CS50 라이브러리를 링크하면 오브젝트 코드는 GetInt()나 GetString() 같은 함수를 어떻게 실행할 지 알 수 있게 됩니다.
이 네 단계를 거치면 최종적으로 실행 가능한 파일이 완성됩니다.
이 과정 전체를 컴파일이라고도 한다
컴파일은 구체적으로는 아래의 컴파일 단계를 의미함
전처리: #코드 (컴파일 전 실행) -> 컴파일: 소스코드를 어셈블리어로(저수준 프로그래밍 언어) -> 어셈블: 어셈블리 언어를 오브젝트 코드로 -> (링크): 파일이 여러개라면 합쳐준다