개요
이번에는 컴파일러가 컴파일을 진행하는 과정을 공부해봤다.
컴파일러
C, python, Java 등 인간이 읽고 이해하고, 쓸 수 있는 언어로 만들어진 프로그래밍 언어인 고수준 프로그래밍 언어를 컴퓨터가 읽고 실행할 수 있는 저수준언어(기계어)로 변환하는 프로그램이다.
컴파일러는 코드 변환과정에서 오류를 검출하고, 최적화를 수행해 성능을 개선하는 역할도 한다.
기능
컴파일러의 주요 기능으로는 코드변환, 오류검출, 최적화가 있다.
- 코드변환 : 사람이 이해하기 쉬운 고수준 언어를 컴퓨터가 이해할 수 있는 기계어로 변환
- 오류검출 : 문법, 데이터 타입, 범위 등과 관련된 오류를 확인
- 최적화 : 실행 성능을 높이거나 메모리 사용량을 줄이는 방식으로 코드 개선
컴파일 과정
컴파일은 크게 전처리, 컴파일, 어셈블, 링킹의 4단계로 이루어진다.
오늘은 이 과정에 대해 실습과 함께 공부했다.
gcc 컴파일러를 통해 각 단계별로 파일이 어떻게 변하는지 확인해봤다.
#include<stdio.h>
#define Calculate(x) ((x)*(x))
int main(){
int num =5;
printf("result : %d",Calculate(num));
return 0;
}
1. 전처리
- 역할
- 코드 내의 지시문(Preprocessor Directivies)를 처리한다.
- #include, #define, #ifdef 등
- 헤더 파일을 포함하고 매크로를 확장하여 소스코드에 헤더파일의 내용을 포함시키고, 소스코드에서 매크로를 찾아 해당 값을 치환한다.
- 결과 : 전처리 결과로 전처리가 완료된 소스 파일이 생성된다.
- 생성 파일 : test.i
# 1 "test.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "test.c"
# 1 "/usr/include/stdio.h" 1 3 4
# 27 "/usr/include/stdio.h" 3 4
# 1 "/usr/include/x86_64-linux-gnu/bits/libc-header-start.h" 1 3 4
# 33 "/usr/include/x86_64-linux-gnu/bits/libc-header-start.h" 3 4
typedef long int __off64_t;
typedef int __pid_t;
typedef struct { int __val[2]; } __fsid_t;
typedef struct _G_fpos_t
{
__off_t __pos;
__mbstate_t __state;
} __fpos_t;
# 40 "/usr/include/stdio.h" 2 3 4
# 1 "/usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h" 1 3 4
# 10 "/usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h" 3 4
typedef struct _G_fpos64_t
{
__off64_t __pos;
__mbstate_t __state;
} __fpos64_t;
# 41 "/usr/include/stdio.h" 2 3 4
# 1 "/usr/include/x86_64-linux-gnu/bits/types/__FILE.h" 1 3 4
typedef struct _IO_FILE FILE;
# 43 "/usr/include/stdio.h" 2 3 4
# 1 "/usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h" 1 3 4
# 35 "/usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h" 3 4
struct _IO_FILE;
struct _IO_marker;
struct _IO_codecvt;
struct _IO_wide_data;
extern void setbuffer (FILE *__restrict __stream, char *__restrict __buf,
size_t __size) __attribute__ ((__nothrow__ , __leaf__));
extern int scanf (const char *__restrict __format, ...) ;
extern int sscanf (const char *__restrict __s,
const char *__restrict __format, ...) __attribute__ ((__nothrow__ , __leaf__));
extern int vscanf (const char *__restrict __format, __gnuc_va_list __arg)
__attribute__ ((__format__ (__scanf__, 1, 0))) ;
extern int vsscanf (const char *__restrict __s,
const char *__restrict __format, __gnuc_va_list __arg)
__attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__format__ (__scanf__, 2, 0)));
__attribute__ ((__format__ (__scanf__, 2, 0)));
# 485 "/usr/include/stdio.h" 3 4
extern int fgetc (FILE *__stream);
extern int getc (FILE *__stream);
extern int fputs (const char *__restrict __s, FILE *__restrict __stream);
extern int puts (const char *__s);
extern int ungetc (int __c, FILE *__stream);
extern size_t fread (void *__restrict __ptr, size_t __size,
size_t __n, FILE *__restrict __stream) ;
extern size_t fwrite (const void *__restrict __ptr, size_t __size,
size_t __n, FILE *__restrict __s);
# 673 "/usr/include/stdio.h" 3 4
extern size_t fread_unlocked (void *__restrict __ptr, size_t __size,
size_t __n, FILE *__restrict __stream) ;
extern size_t fwrite_unlocked (const void *__restrict __ptr, size_t __size,
size_t __n, FILE *__restrict __stream);
# 2 "test.c" 2
# 4 "test.c"
int main(){
int num =5;
printf("result : %d",((num)*(num)));
return 0;
}
2. 컴파일
- 역할
- 고수준 언어를 어셈블리 코드로 변환한다.
- 주로 구문 분석과 의미 분석을 포함
- 과정
- 어휘분석 : 소스코드를 키워드, 변수 이름, 연산자 등으로 구분 및 분해해서 분석한다.
- 구문분석 : 문법 구조를 확인해 문장이 올바른 지 확인한다.
- 의미분석 : 데이터 타입, 변수 선언, 함수 호출의 적합성을 검사
- 어셈블리 코드로 변환
- 생성파일 : test.s
.file "test.c"
.text
.section .rodata
.LC0:
.string "result : %d"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl $5, -4(%rbp)
movl -4(%rbp), %eax
imull %eax, %eax
movl %eax, %esi
leaq .LC0(%rip), %rdi
movl $0, %eax
call printf@PLT
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
3. 어셈블
- 역할 : 어셈블러를 사용해 어셈블리 코드를 기계어로 변환
- 결과 : 목적파일(Object File) 생성, Object File은 실행 가능한 파일이 아니며, 링킹단계에서 추가적으로 처리해야 한다.
- 생성파일 : test.o
7f45 4c46 0201 0100 0000 0000 0000 0000
0100 3e00 0100 0000 0000 0000 0000 0000
0000 0000 0000 0000 3003 0000 0000 0000
0000 0000 4000 0000 0000 4000 0e00 0d00
f30f 1efa 5548 89e5 4883 ec10 c745 fc05
0000 008b 45fc 0faf c089 c648 8d3d 0000
0000 b800 0000 00e8 0000 0000 b800 0000
00c9 c372 6573 756c 7420 3a20 2564 0000
4743 433a 2028 5562 756e 7475 2039 2e34
2e30 2d31 7562 756e 7475 317e 3230 2e30
342e 3229 2039 2e34 2e30 0000 0000 0000
0400 0000 1000 0000 0500 0000 474e 5500
0200 00c0 0400 0000 0300 0000 0000 0000
1400 0000 0000 0000 017a 5200 0178 1001
1b0c 0708 9001 0000 1c00 0000 1c00 0000
... ...
4. 링킹(Linking)
- 역할
- 프로그램에서 사용하는 여러 목적파일과 라이브러리를 결합해 실행파일을 생성
- 정적라이브러리(.lib, .a) 또는 동적라이브러리(.dll, .so)를 연결한다.
- 결과 : 완전한 실행 파일 생성
요약
단계 | 입력 | 출력 | 주요작업 |
---|---|---|---|
전처리 | 소스코드 | 전처리된 코드 | 헤더 파일 포함, 매크로 확장 |
컴파일 | 전처리된 코드 | 어셈블리 코드 | 어휘, 구문, 의미 분석 후 중간 코드 생성 |
어셈블 | 어셈블리 코드 | 목적파일 | 어셈블러를 통해 기계어로 변환 |
링킹 | 목적파일 | 실행파일 | 여러 파일 및 라이브러리를 결합 |
'STUDY' 카테고리의 다른 글
[3주차 TIL] KnockOn Bootcamp Pre.Rev : 메모리 구조와 매핑 (0) | 2024.12.21 |
---|---|
[3주차 TIL] KnockOn Bootcamp Pre.Rev : 링커와 로더 (0) | 2024.12.21 |
[3주차 TIL] KnockOn Bootcamp Pre.Rev : 아키텍쳐 (0) | 2024.12.21 |
[2주차 TIL] KnockOn Bootcamp Pre.Rev : 탐색 알고리즘 (1) | 2024.12.15 |
[2주차 TIL] KnockOn Bootcamp Pre.Rev : 정렬 알고리즘 (1) | 2024.12.14 |