본문 바로가기
Books

[나는 리뷰어다] 전문가를 위한 C

by hyeyoo 2022. 10. 30.
※ 이 블로그의 글은 글쓴이가 공부하면서 정리하여 쓴 글입니다.
※ 최대한 내용을 검토하면서 글을 쓰지만 틀린 내용이 있을 수 있습니다.
※ 만약 틀린 부분이 있다면 댓글로 알려주세요.

이 글은 한빛미디어 <나는 리뷰어다> 활동을 위해서 책을 제공받아 작성된 서평입니다.

전문가를 위한 C

개요

나는 처음 프로그래밍을 배울 때 나중에 어떤 언어를 배우든 C언어가 가장 기초가 된다는 이야기를 많이 들었던 것 같다. 물론 새로운 언어는 계속 생겨나고있고 첫 번째 언어에 정답이 있다고 생각하지는 않는다. 다만 나는 C언어가 프로그래밍 언어의 발전의 중심에 있었으며, 다른 언어에 비해 하드웨어, 운영체제와 가까운 만큼 배울 점이 많은 언어라고 생각한다. 그리고 C언어는 만들어진지 이제 50년이 되었지만 아직도 많은 프로젝트에서 사용된다.

"전문가를 위한 C"라는 제목처럼 이 책은 초심자를 위한 책은 아니다. 처음 C 언어를 배운다면 쉬우면서 사람들이 많이 읽은 책을 읽고, 그 다음 더 깊이있는 책을 읽어야 한다고 생각한다. 이 책은 두 번째나 세 번째 C언어 책으로 좋은 것 같다. 다른 책으로는 이 책도 추천한다. (다만 이건 추천일 뿐이니 참고만 하자.)

이 책은 크게 컴파일 과정, 목적 파일의 형식, 메모리 레이아웃, 객체지향, 유닉스, 최신 C, 동시성, 소켓, 프로세스간 통신 등 C언어와 관련된 다양한 주제를 깊이있게 다룬다. 아래에는 그 중 일부 내용을 정리해보았다. 읽어보고 흥미가 생긴다면 일독을 권한다.

프로젝트 빌드

이 챕터에서는 C언어 소스와 헤더파일이 실행 파일이 되기까지의 과정 다룬다. 다만 단순히 "전처리기 -> 컴파일러 -> 어셈블러 -> 링커를 거쳐서 실행 파일이 된다"고 끝내지 않고, 조금 자세하게 다룬다. 

소스코드 전처리기를 거쳐 변환 단위 (또는 컴파일 단위)가 되며, 이는 컴파일러 프론트엔드가 렉서파서를 거쳐 추상 구문 트리 (AST)라는 구조로 변환한다. 그 다음 컴파일러는 이를 어셈블리와 유사하지만 하드웨어에 독립적인 중간 표현 (LLVM IR)으로 변환한 후, 하드웨어에 맞는 어셈블리로 번역한다. 어셈블리는 하드웨어 벤더사에서 제공하는 (또는 오픈소스인) 어셈블러를 거쳐 프로세서가 해석할 수 있는 기계어를 포함하는 재배치 가능한 목적 파일이 된다.

우리의 목표는 프로젝트 전체를 빌드하는 것이지만 컴파일러가 하는 일은 하나의 변환 단위재배치 가능한 목적 파일로 만드는 것이다. 재배치 가능한 목적 파일은 그 자체로 실행하기에는 부족한 정보가 많기 때문에 대체로 최종 결과물이 아니라 중간 단계이다.

예를 들어 우리가 foo.c에 foo()라는 함수를 정의하고 main.c에서 foo()를 호출한다고 해보자. 전처리기를 거쳐 foo.c와 main.c는 서로 다른 변환 단위가 되며, 변환 단위 바깥에 정의된 함수의 위치는 컴파일 시점에 알지 못한다. 따라서 컴파일러는 main.o에 foo()를 호출하는 명령어는 삽입하지만, 호출되는 함수등 심볼의 실제 주소는 링커가 목적 파일들을 합치면서 결정한다. 링커는 목적 파일들을 엮어서 최종적으로 실행 가능한 목적 파일 구성한다.

목적 파일

소프트웨어는 언어에만 국한되지 않는다. C언어를 잘 다루려면 C언어가 컴파일되는 과정에 대해서도 잘 알아야 한다. 이 챕터에서는 그 중 목적 파일의 형식에 관해 자세히 다룬다.

ABI와 API

API (Application Programming Interface)는 많이 들어본 말인데 라이브러리 등에서 프로그램의 개발을 위해 제공하는 프로그래밍 인터페이스이다. 라이브러리를 사용하는 쪽에서는 인터페이스를 정해진 규칙에 따라서 사용해야 하며, 사용자는 인터페이스 뒤에서 무슨 일이 일어나는지는 몰라도 된다. 대신 정해진 규칙만 잘 따른다면 문제 없이 라이브러리에서 제공하는 기능을 사용할 수 있다.

이와 유사하게 서로 다른 환경에서 빌드된 목적 파일들(예를 들어 libc와 내가 작성한 프로그램)을 함께 사용하려면 규칙이 필요하다. 이러한 규칙을 ABI (Application Binary Interface)라고 한다.

ABI는 보통 명령어 집합, 함수 호출 규약, 시스템 호출 방식, 목적 파일의 형식 등을 정의한다. 리눅스나 BSD 등의 유닉스 계열 시스템에서는 System V ABI를 사용한다. ELF (Executable and Linking Format)는 System V ABI에서 사용되는 표준 목적 파일 형식이다.

목적 파일의 형식

프로세서가 사용하는 명령어 셋에 따라서 목적 파일에 사용되는 명령어들은 매우 다르지만, 목적 파일의 구조 자체는 프로세서의 아키텍처와는 독립적이다.

초기의 유닉스 운영체제는 a.out이라는 단순한 목적 파일 형식을 사용했지만, 이후 COFF (Common Object File Format)으로 대체되었으며 현재 우리가 사용하는 운영체제들의 목적 파일들은 a.out와 COFF를 기반으로 한다. 많이 쓰이는 목적 파일 형식에는 Unix/Linux의 ELF, OS X의 Mach-O, 윈도우의 PE 등이 있다.

재배치 가능한 목적 파일과 실행 가능한 목적 파일

재배치 가능한 목적 파일은 최종 위에서 설명했듯 변환 단위가 컴파일러와 어셈블러를 거친 이후의 결과물이다. 재배치 가능한 목적 파일은 변환 단위에서 선언된 함수와 전역변수, 심벌에 대한 정보를 포함한다.

그런데 목적 파일이면 목적 파일이지 재배치 가능한 목적 파일은 뭐고, 실행 가능한 목적 파일은 어떻게 다른가? 재배치 가능한 이라는 말은 말 그대로 명령어의 주소가 결정되지 않고 목적 파일들을 합치는 순서에 따라서 바뀔 수 있음을 의미한다. 주소가 아직 결정되지 않았기 때문에 실행이 불가능하다. (다만 실행이 불가능할 뿐 파일 형식은 같다.)

링커는 재배치 가능한 목적 파일들을 합치면서 심볼들의 주소를 결정하고 실행 가능한 목적 파일을 만들어낸다.

댓글