[CS:APP] 1-2~1-3) 컴파일 시스템
2023. 3. 6. 22:15ㆍComputer Science
728x90
반응형
프로그램은 다른 프로그램에 의해 다른 형태로 번역된다
아래의 hello.c
프로그램이 시스템에서 실행되는 과정을 알아보자!
그 중에서 소스 파일이 번역되는 과정을 알아보자!
hello.c
#include <stdio.h>
int main()
{
printf("hello, world\n");
return 0;
}
뭘 번역한다는 거야?
hello.c
를 시스템에서 실행시키려면,
각 C 문장들은 다른 프로그램들에 의해 저급 기계어 인스트럭션들로 번역되어야 한다.- 이 인스트럭션들은 실행 가능 목적 프로그램( = 실행가능 목적 파일) 이라는 형태로 합쳐져서 바이너리 디스크 파일로 저장된다.
- 컴파일러 드라이버는 유닉스 시스템에서 아래와 같이 소스파일에서 오브젝트 파일로 번역한다.
👇🏻 GCC 컴파일러 드라이버는 소스파일
hello.c
를 읽어서 실행파일인hello
로 번역한다.
linux> gcc -o hello hello.c
- 번역은 4개의 단계를 거쳐서 실행되는데,
이 네 단계를 실행하는 프로그램들을 합쳐서 컴파일 시스템이라고 부른다.
다음으로, 컴파일 시스템을 알아봅시다~!
컴파일 시스템
1. Pre-processor : 전처리 단계
과정
- 전처리기(cpp)는 본래의 C 프로그램을 #문자로 시작하는 디렉티브(directive)에 따라 수정한다.
- 예를 들어
hello.c
파일 첫 줄의#include<stdio.h>
는 전처리기에게 시스템 헤더파일인stdio.h
를 프로그램 문장에 직접 삽입하라고 지시한다.
결과
- 그 결과 일반적으로
.i
로 끝나는 새로운 C 프로그램이 생성된다.
2. Compiler : 컴파일러
과정
- 컴파일러(ccl)는 텍스트 파일
hello.i
를 텍스트 파일인hello.s
로 번역하며, 이 파일에는 어셈블리어 프로그램이 저장된다. - 이 프로그램은 다음과 같은 main 함수의 정의를 포함한다.
아래 코드의 2~7줄에서는 한 개의 저수준 기계어 명령어를 텍스트 형태로 나타내고 있다.
main:
subq $8, %rsp
movl $.LCO, %edi
call puts
movl $0, %eax
addq $8, %rsp
ret
- 어셈블리어는 여러 상위수준 언어의 컴파일러들을 위한 공통의 출력언어를 제공하기 때문에 유용하다.
(예를 들어, C와 Fortran 컴파일러는 둘 다 동일한 어셈블리어로 출력 파일을 생성한다.)
결과
- 어셈블리어 프로그램이 저장된 텍스트 파일
hello.s
3. Assembler : 어셈블리 단계
과정
- 어셈블러(as)가
hello.s
를 기계어 인스트럭션으로 번역하고,
이들을 재배치가능 목적프로그램의 형태로 묶어서hello.o
라는 목적파일에 그 결과를 저장한다. - 이 파일은 main 함수의 인스트럭션들을 인코딩하기 위한 17바이트를 포함하는 바이너리 파일이다.
결과
- 기계어 인스트럭션으로 번역한 바이너리 파일
hello.o
4. Linker : 링크 단계
과정
- 위에서 작성한 hello 프로그램은 C 컴파일러에서 제공하는 표준 C 라이브러리에 들어있는
printf
함수를 호출하고 있다. printf
함수는 이미 컴파일된 별도의 목적파일인printf.o
에 들어있다.printf.o
파일은hello.o
파일과 어떤 형태로든 결합되어야 한다.- 링커 프로그램(ld)이 이 통합작업을 수행한다.
결과
- 실행가능 목적파일( = 실행파일)로 메모리에 적재되어 시스템에 의해 실행될
hello
파일
컴파일 시스템의 동작을 이해해야 하는 이유
hello.c
처럼 간단한 프로그램은,
컴파일 시스템이 정확하고 효율적인 기계어 코드를 만들어 줄 거라고 기대할 수 있다.
하지만, 프로그래머들이 어떻게 컴파일 시스템이 동작하는지 이해해야 하는 중요한 이유가 있다!
1. 프로그램 성능 최적화하기
- 최신 컴파일러들은 복잡한 도구로 대개 우수한 코드를 생성하므로,
프로그래머로서 효율적인 코드 작성을 위해 컴파일러의 내부 동작을 알 필요는 없다. - 그렇지만, C 프로그램 작성 시 올바른 판단을 하기 위해서는
기계어 수준 코드에 대한 기본적인 이해를 할 필요가 있다. - 컴파일러가 어떻게 C 문장들을 기계어 코드로 번역하는지 알 필요가 있다.
# 효율적인 코드 작성을 위한 판단 예시
* switch 문은 if-else 문을 연속해서 사용하는 것보다 언제나 더 효율적일까?
* 함수 호출 시 발생하는 오버헤드는 얼마나 되는가?
* while 루프는 for 루프보다 더 효율적일까?
* 포인터 참조가 배열 인덱스보다 더 효율적인가?
* 합계를 지역 변수에 저장하면 참조형태로 넘겨받은 인자를 사용하는 것보다 왜 루프가 더 빨리 실행되는가?
* 수식 연산시 괄호를 단순히 재배치하기만 해도 함수가 더 빨리 실행되는 이유는 무엇인가?
2. 링크 에러 이해하기
- 프로그래밍 에러 중, 시스템을 빌드하려는데 링커의 동작 에러가 발생하는 경우가 있다.
# 링크 관련 이슈들
* 예를 들어 링커가 어떤 참조를 풀어낼 수 없다고 할 때, 무엇을 의미하는지?
* 정적변수와 전역변수의 차이는 무엇인가?
* 만일 각기 다른 파일에 동일한 이름이 두 개의 전역변수를 정의한다면 무슨 일이 일어나는가?
* 정적 라이브러리와 동적 라이브러리의 차이는 무엇인가?
* 컴파일 명령을 쉘에서 입력할 때 명령어 라인의 라이브러리들의 순서는 무슨 의미가 있는가?
* 왜 링커와 관련된 에러들은 실행하기 전까지는 나타나지 않는 걸까?
3. 보안 약점 피하기 (security hole)
- 오랫동안 버퍼 오버플로우(buffer overflow) 취약성이 인터넷과 네트워크상의 보안 약점의 주요 원인으로 설명되었다.
- 이 취약성은 프로그래머들이 신뢰할 수 없는 곳에서 획득한 데이터의 양과 형태를 주의 깊게 제한해야 할 필요를 거의 인식하지 못하기 때문에 생겨난다.
- 안전한 프로그래밍을 배우는 첫 단계는 프로그램 스택에 데이터와 제어 정보가 저장되는 방식 때문에 생겨나는 영향을 이해하는 것이다.
728x90
반응형
'Computer Science' 카테고리의 다른 글
[CS:APP] 1-4) 프로세서의 작동 원리 (0) | 2023.03.06 |
---|---|
[CS:APP] 1-1) 비트와 컨텍스트 (0) | 2023.03.06 |
메모리 누수: Garbage Collection, 전역 변수, 브라우저에서 메모리 확인하기 (0) | 2023.02.15 |