C 언어(20) - 전처리와 라이브러리 함수

C 언어 강의, 전처리, 라이브러리 함수

Featured image

🔚 짧게 하는 복습

✅ 1. 메모리 구조의 영역을 안다.

✅ 2. 함수 호출과 종료에 따른 스택 영역의 변화를 안다.

✅ 3. 포인터 매개변수가 있는 함수는 왜 다른 스택 프레임 사이 값의 영향을 줄 수 있는지 안다.

혹시 기억이 안 난다면, 다시 돌아가자


저번 강의를 통해서 여러분들은 더 깊은 컴퓨터 과학 지식을 얻었다.

메모리에 변수가 할당되고, 함수가 호출되고 여러 일이 일어나도 여러분들은 어떠한 내부 과정이 일어나는지 알 수 있게 된 것이다.

오늘은 조금 더 나아가 main함수가 실행되기 전, 즉 본격적인 코드 실행 전에 일어나는 일에 대해 알아보겠다.


프로그램의 실행 순서

분명 우리가 작성한 것은 c라는 소스 코드인데, 어떻게 여기서 프로그램이 실행되는 걸까?

우리가 프로그램을 실행하면 전처리, 컴파일, 링킹, 로딩, 실행이라는 과정을 거친다.

전처리라는 과정은 전처리기를 통해 소스 코드의 일부가 변경되는 과정이다.

예를 들어, #include 지시문을 사용하여 다른 파일의 내용을 현재 파일에 포함하거나, 매크로를 확장하는 등의 작업이 이루어집니다. (아직은 무슨 말인지 몰라도 된다.)

컴파일이란 컴파일러를 통해 전처리된 소스 코드를 기계어나 중간 코드로 변환하는 과정이다. 이 단계에서는 소스 코드의 구문과 의미를 기반으로 기계어 코드를 생성한다.

기계어라는 것은 컴퓨터가 이해할 수 있게 0과 1로만 되어있는 코드이다. 이 기계어 코드는 컴퓨터의 시스템마다 다르다.

이 과정에서 몇몇 언어는 어셈블리어라는 기계와 소스 코드의 사이 언어로 바꾸는 어셈블리라는 과정을 추가로 겪기도 한다. (읽어볼 거리 참고)

링킹이란 링커를 통해 여러 개의 컴파일된 오브젝트 파일과 라이브러리를 하나의 실행 가능한 파일로 결합하는 과정이다.

이 과정에서는 사용되지 않는 코드나 라이브러리의 함수, 변수 등을 제거하고, 모든 외부 참조를 적절히 해결하여 하나의 실행 파일을 생성한다.

로딩이란 운영 체제(우리가 익히 아는 윈도우, 리눅스 등이다!)는 실행 가능한 파일을 메모리로 올리는 과정이다.

이 과정에서 저번 시간에 다루었던 프로그램의 코드, 변수, 상수 등이 텍스트, 데이터, 스택, 힙 메모리에 할당된다.

실행은 로딩이 완료되면, 운영 체제는 프로그램의 시작 지점(대부분은 main 함수)으로 제어를 전달한다.

프로그램은 이후에 메모리에서 실행되며, 사용자의 입력이나 시스템 호출 등에 따라 다양한 작업을 수행한다.


전처리란?

우리는 그 중, 전처리에 대해서 자세하게 살펴볼 것이다.

전처리에서 하는 일로는 대표적으로 include, 매크로, 함수 원형 정의, typdef 등이 있다.


include란

#include<>
#include ""

로 작성되는 코드를 말한다. 아래의 include는 생소할텐데 위의 include는 라이브러리 헤더 파일을, 아래의 include는 직접 제작한 헤더 파일을 포함시키는 것을 의미한다.

우선 헤더 파일이라는 것은 다양한 함수나 상수를 미리 저장해 놓은 파일을 말한다.

예를 들어, 우리가 가장 처음 작성한 코드를 기억해보자.

#include<stdio.h>

int main(){
    printf("Hello world!");

    return 0;
}

저기서 printf라는 것, 사실은 함수이다. 그런데 우리가 저 함수에 대해 작성한 기억은 하나도 없다.

그런데 왜 오류가 나지 않을까? 그 이유는 include라는 전처리에 있다.

printf라는 것은 stdio.h라는 헤더 파일에 저장이 되어있고, stdio는 표준 입출력에 관한 헤더 파일이다.

여기에는 printf 뿐만 아니라 scanf도 포함되어 있고, 이는 C언어 개발자가 미리 만들어 놓은 헤더 파일이라 특별하게 라이브러리 헤더 파일이라고 부른다.

또한, 그 헤더 파일에 포함된 모든 함수들을 라이브러리 함수라고 한다.

라이브러리 함수에는 편리한 함수들이 대거 포함되어 있고, 이를 얼마나 잘 사용하는가도 프로그래머의 역량이다.

반면에, 정의되어 있지 않은 함수도 우리가 함수를 헤더 파일에 작성하여 포함할 수도 있는데, 아래와 같이 하면 된다.

// hello.h라는 파일에 저장했다고 하자
#include<stdio.h>

void hello(){
    printf("hello world!");
}
// project.c라는 파일에 저장했다고 하자
#include<stdio.h>
#include "hello.h" //직접 만든 헤더 파일이라 ""로 include해준다.

int main(){
    hello();

    return 0;
}

이렇게 하면 project.c의 전처리 과정에서, hello.h의 파일이 모두 include되고 링킹 과정에서 정리되어 실행된다.

그렇기에 project.c에는 hello라는 함수를 찾아볼 수 없지만 문제없이 실행된다.

중요한 점은 두 파일을 같은 폴더에 넣어놔야 작동한다는 점이다.


매크로란

매크로란 우리가 전처리 과정에서 상수나, 특정한 함수를 정의하는 것이다.

#define MAX 10000 //MAX라는 값이 10000으로 사용된다.
#define SQUARE(X) ((X) * (X)) //SQUARE(값)을 넣으면 값 * 값이 반환된다.
#define add(x, y) ((x) + (y))

다만 함수 매크로는 추천하지 않는다. 이런 기괴한 오류가 있기 때문이다.

int value = SQUARE(3 + 2);
// value는 5*5인 25가 나오는 것이 아니라,
// 3 + 2 * 3 + 2인 11이 나온다.

typedef란?

typedef는 자료형의 이름을 원하는 값으로 바꾸는 기능을 의미한다.

이는 자료형이 복잡해지거나, 가독성을 높이기 위해서 쓰이는데

#include <stdio.h>

typedef int** pointer_of_2d;

int main() {
    int arr[2][3] = {0, };

    pointer_of_2d value = arr;

    return 0;
}

이런 식으로 사용된다. 하지만 이는 모든 사람이 typedef로 변한 이름을 숙지하고 있는 것이 아니라면 오히려 가독성을 낮출 수 있다.


📖 오늘의 핵심(다 알기 전까지는 넘어가지 말자❗)

✅ 1. 프로그램의 실행 과정을 안다.

✅ 2. 전처리 과정에 대해 자세히 안다.

✅ 3. 라이브러리 함수와 헤더 파일을 다룰 줄 안다.(과제를 통해 이해하자)

⚠️ 함수 매크로은 사용을 자제하자.

⚠️ typedef는 함부로 사용하지 말자.

💣 과제, 모두 검색해서 함수를 사용하는 방법을 찾아보자

  1. math.h를 이용해 반지름을 입력받고 각각 원의 넓이, 둘레를 구하는 함수를 작성해보자(난이도 下)

  2. stdlib.h의 qsort를 이용해 배열을 정렬해보자 (난이도 下)

  3. stdlib.h의 rand()를 통해서 난수를 만들어보자 (난이도 下)

  4. stdlib.h와 time.h의 rand(), time()으로 더욱 발전한 난수 제작을 해보자 (난이도 下)

  5. 자신이 윈도우를 쓰고 있다면 windows.h의 system(“cls”), 리눅스를 쓰고 있다면 stdlib.h의 system(“clear”)를 사용해서, 1에서 100까지 더하는 동안 합을 보여주다가 10의 자리가 바뀔 때마다 화면을 지우는 프로그램을 만들어보자 (난이도 中)

//실행화면
1
3
6
10
15
21
28
36
45
//화면 지움
55
...
5050
//화면 지움

🔜 더 공부해보기,

  1. 읽어볼 거리(1) - 어셈블리란? (매우 중요함)