컴파일
어떤 언어의 코드를 다른 언어로 바꾸어주는 과정. 예를 들어 사람이 읽을 수 있는 c언어 코드를 기계어로 바꾸는 것을 컴파일이라고 한다. 컴파일러는 어떤 프로그래밍 언어로 쓰인 소스 파일을 다른 언어로 번역해주는 번역기이다.
c vs python
파이썬을 주로 사용하다 c를 사용하니, 파이썬은 따로 실행 파일이 생성되지 않는데 c는 생성되어 그 차이가 궁금했다.
python | c | |
실행시 | 스크립트 실행시 파이썬 인터프리터가 한 줄 한 줄 실시간으로 읽는다. 따라서 실행 파일을 생성할 필요가 없다. | 반드시 컴파일하여 빌드 후 생성되는 실행파일을 실행해야 함. |
언어 타입 | 인터프리터 언어 | 컴파일 언어 |
컴파일 | 파이썬 인터프리터만 있다면 .py 파일을 어떤 시스템에서든지간에 돌릴 수 있으며 컴파일이 따로 필요 없음 | 반드시 기계어로 돌리는 과정이 필요하므로 컴파일 필수 |
GCC
GNU 컴파일러 모음의 약자로 널리 쓰이고 있는 컴파일러.
GCC명령어
-o [파일명] [*.c] :지정한 파일명으로 실행 파일을 저장
# gcc -o hello hello.c : hello.c파일을 hello라는 실행파일을 만들어 저장한다.
#아래 각 단계별 예시 파일을 보고 싶다면 사용할 명령어
-E :전처리 단계 수행 후 멈춤
-S :컴파일 단계 수행 후 멈춤
-c : 컴파일/어셈블 하며 링크 하지 않음. (링크를 하지 않고 컴파일만 진행) 이 옵션 생략 시 main함수를 찾을 수 없다는 오류가 출력
자주 사용되는 컴파일 옵션
### 자주 사용되는 컴파일 옵션
-g : gdb 디버깅 정보 포함
컴파일 명령어 예제
# - gcc -g -o [실행파일명] [소스파일명]
-gcc -g -o hello hello.c
- 실행파일의 확장자는 각 os마다 다르다. ubuntu는 .out, window는 .exe
소스 코드가 실행 파일이 되는 과정
과정명 | 담당기계 | 설명/하는 일 | 파일 | 파일의 내용물 타입 |
source program | hello.c | text | ||
전처리단계 | 전처리기 preprocessor(cpp) |
전처리기 지시자를 처리한다. | ||
확장 소스 파일(hello.i) / modified source program | hello.i | text | ||
컴파일단계 | 컴파일러 ccl |
어샘블리어로 된 파일로 바꾼다. | ||
어샘블리 프로그램 / assembly program | hello.s | text | ||
어셈블러 단계 | 어셈블러 as |
어셈블리어 파일을 오브젝트 파일로 변환한다. | ||
재배치 가능한 오브젝트 프로그램 / Relocatable Object program | hello.o | binary | ||
링크 단계 | 링커 ld |
작성한 프로그램이 사용하는 다른 프로그램/ 라이브러리를 가져와 연결→ 실행 가능한 파일 생성한다. |
||
실행 가능한 파일 / Executable object program(실행파일) | hello (hello.exe(window) / hello.out(ubuntu)) |
binary |
소스 코드: source program
// ex.c
#include <stdio.h>
int add(int x, int y); // Forward declaration
int main(void){
int x = 0;
x = add (1, 2);
printf("%d", x);
return 0;
}
int add(int x, int y){
return x+y;
}
전처리 단계 #ex.i
- 전처리기 지시자를 처리한다!
- 전처리기 지시자: #으로 시작하고 세미콜론 없이 개행문자(/n)로 종료되는 라인.
- include는 지정된 특정 파일의 내용을 해당 지시자가 있는 위치에 삽입한다. 즉, stdio.h라는 파일의 내용을 전처리 단계에서 이것이 헤더에 선언된 파일에 넣는다.
- define은 매크로 함수/상수 정의에 사용한다. 코드 내 상수를 정의한 문자열로 대체한다.
#소스 코드 -> 전처리기 지시자가 처리된 코드
gcc -E hello.c -o hello.i
#전처리기 지시자 예시
#include <stdio.h>
// ex.c => ex.i
// 환경: window 11, vscode
# 1 "ex.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "ex.c"
# 1 "C:/mingw64/x86_64-w64-mingw32/include/stdio.h" 1 3
# 9 "C:/mingw64/x86_64-w64-mingw32/include/stdio.h" 3
# 1 "C:/mingw64/x86_64-w64-mingw32/include/crtdefs.h" 1 3
# 10 "C:/mingw64/x86_64-w64-mingw32/include/crtdefs.h" 3
# 1 "C:/mingw64/x86_64-w64-mingw32/include/_mingw.h" 1 3
# 12 "C:/mingw64/x86_64-w64-mingw32/include/_mingw.h" 3
# 1 "C:/mingw64/x86_64-w64-mingw32/include/_mingw_mac.h" 1 3
# 98 "C:/mingw64/x86_64-w64-mingw32/include/_mingw_mac.h" 3
# 107 "C:/mingw64/x86_64-w64-mingw32/include/_mingw_mac.h" 3
# 13 "C:/mingw64/x86_64-w64-mingw32/include/_mingw.h" 2 3
# 1 "C:/mingw64/x86_64-w64-mingw32/include/_mingw_secapi.h" 1 3
# 14 "C:/mingw64/x86_64-w64-mingw32/include/_mingw.h" 2 3
# 282 "C:/mingw64/x86_64-w64-mingw32/include/_mingw.h" 3
# 1 "C:/mingw64/x86_64-w64-mingw32/include/vadefs.h" 1 3
# 9 "C:/mingw64/x86_64-w64-mingw32/include/vadefs.h" 3
# 1 "C:/mingw64/x86_64-w64-mingw32/include/_mingw.h" 1 3
# 578 "C:/mingw64/x86_64-w64-mingw32/include/_mingw.h" 3
# 1 "C:/mingw64/x86_64-w64-mingw32/include/sdks/_mingw_directx.h" 1 3
# 579 "C:/mingw64/x86_64-w64-mingw32/include/_mingw.h" 2 3
# 1 "C:/mingw64/x86_64-w64-mingw32/include/sdks/_mingw_ddk.h" 1 3
# 580 "C:/mingw64/x86_64-w64-mingw32/include/_mingw.h" 2 3
# 10 "C:/mingw64/x86_64-w64-mingw32/include/vadefs.h" 2 3
//(생략)
컴파일 단계
#전처리된 코드 -> 어셈블리어
gcc -S hello.c -o hello.s
//ex.i => ex.s
.file "ex.c"
.text
.def __main; .scl 2; .type 32; .endef
.section .rdata,"dr"
.LC0:
.ascii "%d\\0"
.text
.globl main
.def main; .scl 2; .type 32; .endef
.seh_proc main
main:
pushq %rbp
.seh_pushreg %rbp
movq %rsp, %rbp
.seh_setframe %rbp, 0
subq $48, %rsp
.seh_stackalloc 48
.seh_endprologue
call __main
movl $0, -4(%rbp)
movl $2, %edx
movl $1, %ecx
call add
movl %eax, -4(%rbp)
movl -4(%rbp), %eax
movl %eax, %edx
leaq .LC0(%rip), %rcx
call printf
movl $0, %eax
addq $48, %rsp
popq %rbp
ret
.seh_endproc
.globl add
.def add; .scl 2; .type 32; .endef
.seh_proc add
add:
pushq %rbp
.seh_pushreg %rbp
movq %rsp, %rbp
.seh_setframe %rbp, 0
.seh_endprologue
movl %ecx, 16(%rbp)
movl %edx, 24(%rbp)
movl 16(%rbp), %edx
movl 24(%rbp), %eax
addl %edx, %eax
popq %rbp
ret
.seh_endproc
.ident "GCC: (x86_64-win32-seh-rev0, Built by MinGW-W64 project) 7.3.0"
.def printf; .scl 2; .type 32; .endef
어셈블 단계
# 어셈블리어 -> 2진수 코드
gcc -C hello.c -o hello.o
링크 단계
#2진수 코드 -> 2진수 코드 (실행 가능한 파일 생성)
gcc -o hello hello.c
디버깅
디버깅 테크닉
- 버그 타입 이해하기
- syntax error: 코드의 구조 문제. semicolon 실종이나 괄호 오류 등..
- logic error: 컴파일은 성공했는데 기대한 값이 나오지 않음. 부정확한 알고리즘이나 함수나 변수의 부정확한 사용으로 발생함.
- runtime error: 프로그램을 돌릴 때 발생함.
- complier의 debugging support를 사용하기.
- gcc에서는 ‘-g’ 를 함께 입력하면 디버깅 정보를 생성한다. ex: gcc -g my_program.c -o my_program
- gdb(GNU debugger) 사용
- breakpoint, 변수 출력 등을 등 사용할 수 있다.
- call stack 을 살펴보기 (backtracke/bt)
- printf()를 사용
- assertion을 사용
- 여기까지 해서 못 찾으면 알고리즘/로직 체크
assert
#include <stdio.h>
#include <assert.h>
int main(void){
int a = 5;
assert(a==10);
printf("this line will not be reached\\n");
return 0;
}
- assert 괄호 안에 넣은 선언이 fales가 뜬다면 프로그램이 실행을 멈추고 assert statement가 실패했다는 내용의 에러 메시지를 띄운다.
- 프로그램이 멈추었기 때문에 이 뒤의 printf는 실행되지 않는다.
#컴파일
$ gcc -g -o assert_example ass.c
#생성된 실행 파일 실행
$ ./assert_example
#결과
Assertion failed!
Program: (프로그램 위치)\\assert_example.exe
File: ass.c, Line 8
Expression: a==10
- 디버깅에만 사용하고, 실제 성능 향상을 위해선 실제 빌드할 때는 assertion는 사라져야 함. 아래처럼 <assert.h> 앞에 NDEBUG를 선언해두면 releas mode로 컴파일할떈 assert가 사라진다.
#define NDEBUG
#include <assert.h>
Make
make 기능은 소스 코드들의 컴파일 작업과 링크 작업을 관리하는 빌드 자동화 툴이다. 이건 Makefile이라는 파일을 읽어서 프로그램을 어떻게 빌드하고 어떤 파일들을 다시 컴파일할지 정한다.
- makefile/Makefile 을 소스 코드와 같은 디렉토리에 생성한다. 이 파일은 프로그램을 어떻게 빌드하는지에 대한 make에 대한 명령어를 포함한다.
- makefile 파일에서 타겟을 명시한다. 각각의 target은 결과물 파일(output file, 실행파일이나 오브젝트 파일)을 나타내는데, 그것을 빌드하는데 필요한 의존성(dependency)들의 목록을 적어둔다.
target: dependencies
command
#example
my_program: main.c functions.c
gcc -o my_program main.c functions.c
#In this example, my_program is the target, and it depends on main.c and functions.c. The command to build my_program is gcc -o my_program main.c functions.c.
- target: The name of the output file or the task you want to perform.
- dependencies: The files or resources that the target depends on. These are the files that need to be present or updated for the target to be built.
- command: The command(s) to execute to build the target. These are usually shell commands.
- makefile과 소스코드가 있는 디렉토리에서 make 커맨드를 프롬포트에 입력한다. make will read the Makefile, figure out which targets need to be rebuilt based on the timestamps of files, and execute the associated commands. If you want to build a specific target, you can specify it like this: make target_name
- Check Output:If there are any errors, make will display them in the terminal along with the relevant file and line number.
- If everything goes well, make will compile the necessary files and produce the output specified in the target.
- Clean Up (Optional):
You can then run make clean to execute the clean target.bashCopy code clean: rm -f my_program *.o
- You can include a clean target in your Makefile to remove any generated files, such as object files and executables. This is useful for starting a fresh build. Here's an example:
'공부기록 > C' 카테고리의 다른 글
[TIL][Sat] 구조체 / 포인터 / 동적 메모리 할당 (0) | 2023.11.04 |
---|