[Linux] C소스 컴파일과 링킹 과정

알린·2023년 10월 25일
0

Linux

목록 보기
5/14

컴파일과 링킹

컴파일의 의미

기계어(=머신 인스트럭션, 기계 명령어)

  • 이진수로 이루어져 있는 숫자
  • CPU의 종류마다 고유 => CPU에게 특정한 행동을 취하게 하기 위한 코드
  • 어셈블리 코드와 1대1 대응 => 어셈블러 언어를 어셈블해 인스트럭션으로 표현 가능

C소스 컴파일 과정

gcc

  • GNU에서 만든 C 컴파일러
  • 전처리기, C 컴파일러, 어셈블러, 링커 호출

/usr/bin/gcc는 내부적으로 전처리기(cpp0) 호출해 전처리 과정 수행 → C컴파일러(cc1)호출해 컴파일 → 어셈블러(as) 호출해 오브젝트 코드 만듦 → 링커(ld, collect2) 호출해 오브젝트 코드 링크 → 실행파일

--save-temps => 중간에 생성되는 파일을 저장

#include<stdio.h>
int main() {
        int n = 0;
        int min = 1000000;
        int max = -1000000;
        scanf("%d", &n);
        int arr[n];
        for(int i = 0; i < n; i++) {
                scanf("%d", &arr[i]);
        }
        for(int i = 0; i < n; i++) {
                if(arr[i] < min) {
                        min = arr[i];
                }
                if(arr[i] > max) {
                        max = arr[i];
                }
        }
        printf("%d %d", min, max);
        return 0;
}

cpp0에 의한 전처리 과정

  • 헤더 파일의 삽입
    => C 소스 내의 헤더 파일을 include함
    ex) #include <stdio.h>를 만나게 되면 /usr/include 에서 stdio.h 파일이 있는지 찾음(Unix 계열의 경우) → 찾으면 현재 소스 파일의 #include <stdio.h>라고 적힌 부분에 stdio.h 파일의 내용을 읽어서 붙여넣음

  • 매크로 치환 및 적용
    => #define된 부분은 cpp0의 심볼 테이블에 저장 → 심볼 테이블에 들어 있는 문자열과 같은 문자열을 만나게 되면 #define된 내용으로 치환이 일어남

  • 다음 명령어 실행 시 /usr/include에서 헤더파일을 찾는 과정을 볼 수 있음

gcc -E main.c -v -o main.i

cc1에 의한 어셈블리 소스 파일로 컴파일

컴파일 과정: 어휘 분석 → 구문 분석 → 의미 분석 → 중간 언어 생성 → 최적화 → 목적 코드 생성

  • 어휘 분석
    .i 소스가 읽히며 소스를 문법적으로 의미있는 가장 최소 단위(Token)로 나눔 →파서에게 전달

  • 구문 분석
    파서가 문법적 오류가 있는지 검사 → 파서 트리(GIMPLE 트리) 만듦

  • 의미 분석
    문법상 오류는 없지만 의미상 오류가 있는 것 검사
    ex) 선언되지 않은 변수의 사용, 자료형 불일치 등

  • 중간 언어 생성
    RTL(Register Transfer Language, 고급언어와 어셈블리 언어의 중간 형태) 생성

  • 최적화
    프로그램의 기능은 같지만 크기가 작고 빠르며, 메모리를 작게 사용하게 코드를 변경

    중간 코드 최적화: 지역 최적화, 전역 최적화, 루프 최적화
    목적 코드 최적화: 메모리보다 레지스트를 사용하게 함, 효율적인 인스트럭션 선택

  • 목적 코드 생성
    되도록 재배치가 가능한 어셈블리 언어 생성

  • 컴파일러 실행하기 위해 다음 명령어 실행

gcc -S main.i -v -o main.s

vim main.s

as에 의한 기계어 코드 생성

ELF 헤더(엔디안, 운영체제, CPU의 정보,바이너리 내부의 각 구역의 시작 위치와 크기 정보) + .s를 어셈블하여 만들어진 인스트럭션 + .s의 데이터들 + .c를 컴파일한 gcc 컴파일러의 버전 문자열 + 기타 섹션들

Relocation 섹션(재배치 섹션): 재배치 해야하는 심볼의 정보가 들어 있는 위치

gcc -c main.s -v -o main.o

vim main.o


main.o 파일의 ELF 헤더 정보, 심볼테이블, Relocation 섹션 정보는 다음 명령어를 통해 확인할 수 있음

readelf -a main.o
  • ELF 헤더 정보

  • 심볼테이블

  • Relocation 섹션 정보

collect2에 의한 링킹 과정

표준 C 라이브러리와 링크

  • 정적 라이브러리
    함수들이 오브젝트 파일로 컴파일되어 하나의 파일로 묶여 있는 형태
    형식: lib[라이브러리 이름].a
    단점: 각각의 실행 파일에 같은 오브젝트 파일이 중복되어 링크되기 때문에 실행 파일의 크기가 커져 하드디스크의 공간을 낭비할 수 있음, 실행할 때 메모리의 공간 낭비 있을 수 있음

<gcc 옵션>
-static : libc.a와 링크

  • 동적 라이브러리(=공유 라이브러리)
    실제로 오브젝트와 링크하는 것이 아니라 공유 라이브러리의 함수를 사용한다는 표시만 해둠
    공유 라이브러리를 사용하는 프로세스가 하나라도 있다면 메모리에 로딩되고 공유 라이브러리를 사용하는 모든 프로세스들은 이미 메모리에 로드되어 있는 하나의 공유 라이브러리를 공유해서 사용
    장점: 메모리 절약 가능, 하드디스크 공간 절약 가능
    단점: 약간의 실행 속도 감소
gcc main.o -o main.exe
./main.exe

gcc를 사용해 원하는 파일 컴파일

gcc 옵션

옵션기능
-vgcc가 컴파일을 어떻게 수행하는지 보임
-E / --save-temps전처리 과정의 결과를 화면에 보임 / .i과 .s 파일을 현재 디렉토리에 저장
-Scc1으로 전처리된 파일을 어셈블리 파일로 컴파일까지만 수행하고 멈춤
-c어셈블까지만 수행하고 멈춤(.o 파일 만들어짐)

cpp0 옵션

옵션기능
-I헤더 파일을 탐색하는 기본 디렉토리를 추가
-include [헤더파일경로]헤더파일을 소스 내에 추가
-D[매크로] / -U [매크로]매크로를 외부에서 define / 소스 파일 내에 #undef [매크로] 옵션 추가
-D[매크로]=[매크로값]소스 내에 #define [매크로][매크로값] 옵션 추가
-M / -MMmake를 위한 소스 파일의 모든 종속 항목 출력 / 기본 include 디렉토리에 있는 헤더 파일은 빼고 종속 항목 출력
-nostdinc-I 옵션으로 추가한 디렉토리에서만 헤더 파일 찾음
-C-E 옵션과 함께 사용하며 전처리 과정에서 주석을 제거하지 않음

cc1 옵션

C언어 옵션

C 언어 종류와 관련된 옵션

옵션기능
-std=[C 표준들]기타 다른 표준을 지정

경고 옵션

경고 수위 조절

옵션기능
-W / -Wall / -W -Wall합법적이지만 모호한 코딩에 대해 경고 / 모든 모호한 코딩에 대해 경고 / 아주 사소한 모호성에 대해서 경고
-w모든 경고메세지 제거
-Werror모든 경고를 컴파일을 중단하는 오류로 취급

최적화 옵션

옵션기능
-O0최적화 수행하지 않음
-O2최적화 레벨2, 거의 대부분의 최적화 수행
-Os사이즈 최적화

디버깅 옵션

옵션기능
-ggdb에게 제공하는 정보를 바이너리에 삽입 => 디버깅 시에 소스를 보며 디버깅 가능
-pg프로파일을 위한 코드 삽입

as 옵션

-Wa,[as 옵션들] 형식으로 사용

옵션기능
-al어셈블된 인스트럭션 보임
-as정의된 심볼 보임
-I[패스]include 디렉토리 지정

collect2 또는 ld 옵션

옵션기능
-L[라이브러리 디렉토리]라이브러리를 찾을 디렉토리 저장
-I [라이브러리 이름]같이 링크할 라이브러리 지정
-shared공유라이브러리를 우선하여 링크
-static정적라이브러리를 우선하여 링크
profile
Android 짱이 되고싶은 개발 기록 (+ ios도 조금씩,,👩🏻‍💻)

0개의 댓글