2017년 1월 16일 월요일

[ Linux ] linux 환경 C 컴파일 과정 & make 의 이해





linux 컴파일 과정 & make의 이해




Name
Author
박은탁
Current Status
1.1
Date
2014-02-27














Version
Updates
Author
Date
1.0
초안
박은탁
2014-02-26
1.1
3.3.4 확장자 규칙 추가
3.3.4 와일드 카드 매칭 기법과 대입 참조 기법 추가

2014-02-27























1.1.              컴파일이란?

흔히 말하기를 컴파일은 인간이 이해할 수 있는 형식언어로 작성된 소스 코드를 CPU가 이해할 수 있는 기계어로 번역하는 과정. 결국, 컴파일이라는 것은 결국 컴파일러를 이용해 인간이 만든 코드를 CPU가 이해할 수 있는 코드로 변경하는 과정이다.

1.1.1.            기계어

기계어를 다른  말로 머신 인스트럭션(machine instruction) 이라고 한다.
이런 인스트럭션은 01010101 같은 이진수로 이뤄진 숫자로 cpu종류마다 고유하며, cpu가 특정 행동을 취하게 하기 위한 코드를 말한다.
각각의 인스트럭션은 어셈블리 코드와 1:1 대응하므로 숫자로 이뤄진 인스트럭션을 역어셈블해 어셈블리언어로 표현할 수 있고, 어셈블리 언어를 어셈블해 인스트럭션으로 표현할 수 있다..

1.1.2.            용어

*전처리기
원시파일 내의 모든 전처리기 지시자를 컴파일 할 수 있는 C의 문장으로 전개하는 작업을 담당.
*어셈블러
어셈블러는 기본 컴퓨터 명령어들을, 컴퓨터 프로세서가 기본 연산을 수행하는데 사용할 수 있는 비트 패턴으로 변환시키는 프로그램이다.
*링크(Linking)
오브젝트 파일을 필요한 라이브러리와 연결시켜(링크) 컴퓨터가 알 수 있는 기계어로 바꾼다. 그러면 실제로 실행 가능한 파일을 생성하게 된다

2.    C 소스 Complie 과정

2.1.              gcc 컴파일러

gcc GNU Compiler Collection C컴파일러인 gcc C++, 자바등의 컴파일러들을 포함하는 컴파일러의 묶음을 의미한다.
소문자 gcc GNU C Compiler를 의미하고 대문자 GCC GNU Compiler Collection의 약자로 C컴파일러인 gcc C++, 자바 등의 컴파일러들을 포함하는 컴파일러의 묶음을 의미한다.
usr/bin/gcc는 내부적으로 전처리기인 cpp0(C++ 전처리기)또는 cc1 –E(C 전처리기)명령을 호출해 전처리 과정을 수행하고, 진짜 C컴파일러인 cc1(C언어 컴파일러)또는 cc1plus(C++ 컴파일러)를 호출해 컴파일한 후 어셈블러인 as를 호출해 목적 코드를 만들고, 마지막으로 링커를 호출하는 collect2를 호출하여 collect2가 내부적으로 링커인 id를 호출해 라이브러리와 링크해 실행 파일로 만들어낸다.
gcc는 실제 컴파일 과정을 담당하지 않고, 전처리기와 C컴파일러, 어셈블러, 링커를 각각 호출해 주는 역할만 담당한다. 이렇게 각각의 컴파일 과정에 필요한 명령을 순차적으로 호출해 주는 것을 컴파일러 드라이버라고 부른다.
Gcc 컴파일러 드라이버 내에 컴파일러 들
cc1
C 컴파일러, -E 옵션 을 붙이면 C 전처리기로 동작
Cc1plus
C++ 컴파일러, -E 옵션 을 붙이면 C++ 전처리기로 동작
Collect2
링커(내부에서 링커인 id를 호출해 링크함)


2.2.              gcc compile 과정


1. gcc
cc1 –E를 호출해 전처리 과정을 거쳐 like.c 파일을 like.i 파일로 만든다. 
2. like.i 파일은 C 컴파일러인 cc1에 의해 어셈블리 코드인 like.s로 컴파일된다.
3. like.s as 어셈블러에 의해 어셈블 과정을 거쳐 like.o 오브젝트 파일로 만들어진다.
4. like.o 파일은 다시 collec2가 링커인 id를 호출한 후 표준 C라이브러리와 링크함.
5. 최종적으로 실행파일 like 가 만들어진다.

2.2.1.             gcc compile test - like.c 파일 컴파일 수행

like.c 소스 파일 내용
#include <stdio.h>
int main()
{
        printf("I like you!\n");
        return 0;
}

[euntark@devweb ~]$ gcc -v -save-temps -o like like.c
-v : 컴파일 과정을 화면에 출력
 -save-temps : 컴파일 과정에서 발생되는 중간 파일을 지우지 않고 저장
 -o : 컴파일된 파일명을 like로 하라는 의미
Using built-in specs.
Target: i386-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-libgcj-multifile --enable-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --disable-dssi --disable-plugin --with-java-home=/usr/lib/jvm/java-1.4.2-gcj-1.4.2.0/jre --with-cpu=generic --host=i386-redhat-linux
Thread model: posix
gcc version 4.1.2 20080704 (Red Hat 4.1.2-50)
 /usr/libexec/gcc/i386-redhat-linux/4.1.2/cc1 -E -quiet -v like.c -mtune=generic -fpch-preprocess -o like.i
ignoring nonexistent directory "/usr/lib/gcc/i386-redhat-linux/4.1.2/../../../../i386-redhat-linux/include"
#include "..." search starts here:
#include <...> search starts here:
 /usr/local/include
 /usr/lib/gcc/i386-redhat-linux/4.1.2/include
 /usr/include
End of search list.
 /usr/libexec/gcc/i386-redhat-linux/4.1.2/cc1 -fpreprocessed like.i -quiet -dumpbase like.c -mtune=generic -auxbase like -version -o like.s
GNU C version 4.1.2 20080704 (Red Hat 4.1.2-50) (i386-redhat-linux)
        compiled by GNU C version 4.1.2 20080704 (Red Hat 4.1.2-50).
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: d8d95095eb3c93dae4bed2137d559f95
 as -V -Qy -o like.o like.s
GNU assembler version 2.17.50.0.6-14.el5 (i386-redhat-linux) using BFD version 2.17.50.0.6-14.el5 20061020
 /usr/libexec/gcc/i386-redhat-linux/4.1.2/collect2 --eh-frame-hdr -m elf_i386 --hash-style=gnu -dynamic-linker /lib/ld-linux.so.2 -o like /usr/lib/gcc/i386-redhat-linux/4.1.2/../../../crt1.o /usr/lib/gcc/i386-redhat-linux/4.1.2/../../../crti.o /usr/lib/gcc/i386-redhat-linux/4.1.2/crtbegin.o -L/usr/lib/gcc/i386-redhat-linux/4.1.2 -L/usr/lib/gcc/i386-redhat-linux/4.1.2 -L/usr/lib/gcc/i386-redhat-linux/4.1.2/../../.. like.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/i386-redhat-linux/4.1.2/crtend.o /usr/lib/gcc/i386-redhat-linux/4.1.2/../../../crtn.o

생성 확인
ASP50  asp40         hello_dir     like    like.i  like.s    ppurio50  vhosts.conf
asp20  hello_cp.txt  hello_mv.txt  like.c  like.o  ppurio30  test1


3.     Make

3.1.              Make의 이해

make는 파일 관리 유틸리티다. make는 각 파일 간의 종속 관계를 파악해 기술 파일(make file)에 기술된 대로 컴파일 명령이나 shell명령을 순차적으로 내린다. make는 각 파일에 대한 반복적 명령을 자동화시켜 개발자의 수고를 덜고, 시간을 절약 할 수 있다.
make의 동작 방식은 매우 단순하다. makefile 기술 파일을 작성하면 make가 기술 파일에 적힌 대로 명령을 수행한다. 결국 기술 파일을 작성하는 법과 make에 대한 몇 가지 특징만 이해하면 make를 프로젝트에 도입할 수 있다.

3.2.              예제-Make 기본 동작 이해

3.2.1.            예제 소스

예제 소스 파일 내용
diary.h
#include<stdio.h>
int main();
int calendar();
memo.c
#include "diary.h"
int memo()
{
        printf("function memo.\n");
        return 0;
}
calendar.c
#include "diary.h"
int calendar()
{
        printf("function calendar.\n");
        return 0;
}
main.c
#include "diary.h"
int main()
{
        memo();
        calendar();
        return 0;
}




예제 소스 파일 구조도

3.2.2.            예제-make 파일 생성

Makefile의 내용
all : diary


TAB
diary : memo.o calendar.o main.o
        gcc -W -Wall -o diary memo.o calendar.o main.o
memo.o : memo.c
        gcc -W -Wall -c -o memo.o memo.c
calendar.o : calendar.c
        gcc -W -Wall -c -o calendar.o calendar.c
main.o : main.c
        gcc -W -Wall -c -o main.o main.c

clean :
        rm -rf *.o diary
-o : 출력 파일명을 지정할 때 사용합니다.
-c : 링킹 과정을 진행 하지 않고 .o 파일인 오브젝트 파일까지만 생성 하게 됩니다.
-Wall : 모든 모호한 코딩에 대해서 경고를 보내는 옵션
-W : 합법적이지만 모호한 코딩에 대해서 경고를 보내는 옵션
-W -Wall : 아주 사소한 모호성에 대해서도 경고

3.2.3.            make명령으로 컴파일 수행

Make 명령을 내리면 각 오브젝트에 대한 컴파일 명령을 수행한 후 최종적으로 diary 실행 파일을 생성한다.
[euntark@devweb maketest]$ make
gcc -W -Wall -c -o memo.o memo.c
gcc -W -Wall -c -o calendar.o calendar.c
gcc -W -Wall -c -o main.o main.c
gcc -W -Wall -o diary memo.o calendar.o main.o


3.2.4.            생성된 파일 목록

생성한  파일 목록
calendar.c  diary.h  main.c  makefile  memo.c

3.2.5.            컴파일 후 최종 생성된 파일 목록

컴파일 후 생성된 파일 목록
calendar.c  calendar.o  diary  diary.h  main.c  main.o  makefile  memo.c  memo.o

3.2.1.              예제-make기본동작 수행 순서

make의 기본동작순서는
1)make 명령을 내리면 make는 먼저 현재 디렉토리 내에서 기술 파일을 찾는다. 기술 파일은 makefile이다.
2)makefile 내에서 제일 처음 오는 타겟을 찾는다.(위의 예제 all 타겟)
3)해당 타겟을 만들기 위한 종속 항목을 보고(예제 diary)파일이 현재 디렉토리내에 없다는 것을 확인하다.
4)diary를 생성하기 위한 룰을 찾기 위해 diary타겟의 종속 항목을 접하고 첫 종속항목(memo.o)이 아직 만들어져 있지 안았음을 확인하다.
5)memo.o를 생성하기 위한 룰을 찾기 위해 memo.o 항목을 읽고 종속 항목 memo.c 가 현재 디렉토리 내에 있는 것을 확인하면 make memo.o를 만들기 위해 아래 명령으로 memo.o를 컴파일 해 만들어낸다. 만약 memo.o가 미리 만들어져 있다면 make는 다시 만들 필요가 있는지 검토한다. 만들어져 있는 memo.o보다 memo.c의 수정시간이 최근이라면 memo.o가 만들어진 후 memo가 수정되었기 때문에 다시 만들 필요가 없다. 다시 만들어야 할 필요가 있으면 make는 아래 명령을 수행하고 그렇지 않으면 명령을 건너 뛰고 다음 과정을 수행한다.
6)다시 diary 타겟의 종속항목으로가 memo.o는 만들어 졌지만, calendar.o는 아직 생성 되지 않음을 확인하다.
7)calendar.o main.o 5) ~6) 반복
8)이제는 diary를 생성하기 위한 종속 항목임 3개의 파일이 다 만들어진 것을 확인하고 diary를 생성하기 위한 명령을 수행해 dirary를 생성한다.
9)all 타겟으로 가서 생성하기 위한 diary가 만들어진 것을 확인한 후 현재 all 타켓을 생성하기 위한 특별한 명령이 없으므로 make all타겟이 생성 되었다고 간주하고 종료.

3.3.              기술 파일(make file) 작성

3.3.1.            기술 파일의 기본 구조

매크로 정의
CC=gcc
1
Target1 :dependency1 dependeny2 …
명령1
[ TAB ] command1
[ TAB ] command2
…..
N
TargetN :dependencyN dependenyN+1 …
명령2
[ TAB ] commandN
[ TAB ] commandN+1
….

3.3.2.            기술 파일의 구문 작성 기본 규칙

1. 명령의 시작은 반드시 TAB으로 시작되어야 한다.
2. 비어 있는 행은 무시된다.
3. ‘#’을 만나면 개행 문자를 만날 때까지 무시한다.
4. 기술 행이 길어지면 백슬레쉬를 사용해서 이을 수 있다.
5. ‘;’는 명령 라인을 나눌 때 사용 한다.
6. 종속 항목이 없는 타겟도 사용 가능하다.
7. 명령 부분에는 어떤 명령이 와도 상관없다.

3.3.3.            매크로 사용

기술 파일 내에 매크로는 C와 마찬가지로 사용자 정의 변수에 특정한 문자열을 정의하고 표현하는 것을 의미한다.
CC=gcc
           CC매크로를 정의했다면 CC는 기술 파일 내부에서 $(CC)로 표기함으로 써 gcc로 치환될 수 있다.

3.3.3.1.         매크로 기본 규칙

1.     매크로의 정의는 ‘=’를 포함하는 하나의 문장이다.
‘=’의 좌측에는 매크로 이름이 오고 우측에는 정의 문자열이 온다.
2.     ‘#’은 주석문의 시작이다.
3.     여러 행을 사용할 때는 백슬레쉬를 사용한다.
4.     매크로를 참조할 때는 괄호나 중괄호를 둘러싸고 앞에 ’$’를 붙인다.
5.     정의 되지 않는 매크로를 참조할 때는 null 문자열로 치환된다.
6.     중복된 정의는 최후에 정의된 값을 사용한다.
7.     매크로 정의 시 이전에 정의된 매크로를 참조해 정의할 수 있다.
8.     여러 대입 기법을 사용할 수 있다.
NAME1 =string #재귀적 확장 매크로
NAME2 :=string #단순 확장 매크로
NAME2 +=string #기존의 매크로에 공백을 두고 현재의 문자열을 덧붙인다.
NAME3 ?=string #현재 정의하는 매크로가 정의되어 있지 않았다면 치환 하고 이전에 매크로가 정의 되어 있으면 이 치환은 무시한다.

3.3.3.2.         매크로 사용시 주의 사항.

1. 구분을 위해 문자열에 따옴표를 넣으면 따옴표 또한 문자열의 일부로 인식한다.
2. 매크로의 이름에는 ‘:’ , ’=’ , ‘#’ 이 들어 가서는 안되고 TAB으로 시작해서도 안 된다.  (TAB으로 시작하는 라인은 명령라인으로 인식하기 때문에)
              3. 매크로는 반드시 치환될 위치보다 먼저 정의 되어야 한다

3.3.3.3.         매크로 규칙 적용 makefile

매크로 규칙에 유통적으로 직관적이게 고친 예제 makefile
CC = gcc
CFLAGS = -W –Wall
TARGET = diary
all : $(TARGET)

TAB
 
$(TARGET) : memo.o calendar.o main.o
        $(CC) $(CFLAGS) -o $(TARGET) memo.o calendar.o main.o
memo.o : memo.c
        $(CC) $(CFLAGS) -c -o memo.o memo.c
calendar.o : calendar.c
        $(CC) $(CFLAGS) -c -o calendar.o calendar.c
main.o : main.c
        $(CC) $(CFLAGS) -c -o main.o main.c

clean :
        rm -rf *.o diary


3.3.3.4.         내부적으로 정의 되어 있는 매크로

make –p 명령을 쉘에서 내리면 make 내부에 정의된 매크로 리스트들이 나열된다.
매크로 이름
설명
기본값
AR
아카이브 관리 프로그램
ar
AS
어셈블러
as
CC
C 컴파일러
cc
CXX
C++컴파일러
g++
CO
RCS checkout 프로그램
co
CPP
C 전처리기
cc E
FC
포트란 컴파일러
f77
GET
SCCS 관련 프로그램
get
LD
링크
ld
LEX
스캐너 코드 생성기
lex
PC
파스칼 컴파일러
pc
YACC
파서 코드 생성기
yacc
MAKEINFO
Texinfo 파일을 Info 파일로 변환
makeinfo
TEX
TeX 문서로부터 TeX DVI 생성
tex
TEXI2DVI
Texinfo 파일을 dvi 파일로 변환 프로그램
texi2dvi
WEAVE
Web TeX로 변환
weave
CWEAVE
C Web TeX로 변환
cweave
TANGLE
Web을 파스칼로 변환
tangle
CTANGLE
C Web C로 변환
ctangle
RM
파일을 지우는 명령
rm f
ARFLAGS
ar 플래그
rv
ASFLAGS
어셈블러 플래그

CFLAGS
C 컴파일러 플래그

CXXFLAGS
C++ 컴파일러 플래그

COFLAGS
RCS co 플래그

CPPFLAGS
C 전처리기 플래그

FFLAGS
포트란 컴파일러 플래그

GFLAGS
SCCS get 플래그

LDFLAGS
링크 플래그

LFLAGS
Lex 플래그

PFLAGS
파스칼 컴파일러 플래그

YFLAGS
Yacc플래그

3.3.3.5.         자동 매크로

$?
-현재의 타겟보다 최근에 변경된 종속 항목 리스트
 (확장자 규칙에서는 사용 불가)
$^
- 현재 타겟의 종속 항목 리스트
 (확장자 규칙에서는 사용 불가)
$@
-현재 타겟의 이름
$<
-현재 타겟보다 최근에 변경된 종속 항목리스트
 (확장자 규칙에서만 사용 가능)
$*
-현재 타겟보다 최근에 변경된 현재 종속 항목의 이름
 (확장자제외)
 (확장자 규칙에서만 사용 가능)
$%
-현재의 타깃이 라이브러리 모듈일 때 .o 파일에 대응되는 이름

3.3.3.6.         자동매크로를 적용한 makefile.


자동 매크로를 이용한 makefile

CC = gcc
CFLAGS = -W -Wall
TARGET = diary

all : $(TARGET)

$(TARGET) : memo.o calendar.o main.o
        $(CC) $(CFLAGS) -o $@ $^
memo.o : memo.c
        $(CC) $(CFLAGS) -c -o $@ $^
calendar.o : calendar.c
        $(CC) $(CFLAGS) -c -o $@ $^
main.o : main.c
        $(CC) $(CFLAGS) -c -o $@ $^

clean :
        rm -rf *.o diary

3.3.4.            확장자 규칙

컴파일러는 확장자를 보고 어떤 프로그래밍 소스파일인지 인식한다.

3.3.4.1.         내부 정의 확장자 규칙

%.o: %.c
 #  실행할 명령어 (내장) :
                 $(COMPILE.c) $(OUTPUT_OPTION) $<

 %.o: %.f
 #  실행할 명령어 (내장) :
                 $(COMPILE.f) $(OUTPUT_OPTION) $<

 %.o: %.S
 #  실행할 명령어 (내장) :
                 $(COMPILE.S) -o $@ $<

- %는 일치하는 확장자를 제외한 파일명을 의미한다.
- gcc
컴파일러는 *.c 파일이 있을 때 확장자 .c를 보고 그 파일이 C 프로그래밍 소스 파일인 것을 인식한다.

- COMPILE.xx : 컴파일러 및 컴파일 옵션을 지정
- OUTPUT_OPTION : -o $< 로 지정
*.c 파일은 C컴파일러를 이용해 *.o 파일로 만들 수 있고, *.f 파일은 포트란 컴파일러를 이용해 *.o 파일을 만들 수 있다.
이런 확장자가 가지는 규칙에 기초해 make는 사용자가 내리는 명령을 알아서 해석해 컴파일 과정을 자동화할 수 있다.



3.3.4.2.         내부 정의 확장자 규칙 적용 makefile & 설명

내부 정의 확장자 규칙 이용한 makefile
OBJECTS = memo.o calendar.o main.o

all : diary

diary : $(OBJECTS)
        $(CC)  -o $@ $^
clean :
                rm -rf *.o diary

내부 정의 확장자 규칙 이용한 makefile 컴파일 화면
[euntark@devweb maketest]$ make
cc    -c -o memo.o memo.c
cc    -c -o calendar.o calendar.c
cc    -c -o main.o main.c
        cc  -o diary memo.o calendar.o main.o

위와 같은 기술 파일을 make는 다음 과같이 해석해 명령을 수행한다.
1.     기술 파일에서 diary를 생성하기 위해 make는 종속 항목을 살펴보고 종속 항목을 각각 타겟으로 설정한다.
2.     diary memo.o에 의존하고 memo.o는 만들어지지 않았으며 memo.o를 생성하기 위한 룰도 기술 파일에는 정의되어 있지 않다.
3.     make는 확장자가 .o인 내부 확장자 규칙을 이용해 다음과 같은 기준으로 현재 디렉토리에서 memo.o를 생성할 파일을 찾는다.
-확장자를 제외하고 memo.o와 같은 이름이어야 한다.
4.     내부 정의 확장자 규칙의 기술된 명령을 수행하여 컴파일을 수행한다.

3.3.4.2.1.    make 유틸리티의 디폴트 처리 방식
- 타겟과 종속성은 있지만 함께 기술했던 명령어가 없이도 make 유틸리티가 스스로 알고 있다.
- make 유틸리티의 디폴트 처리 방식 : ${CC} ${CFLAGS} -c -o $@ $*.c
- make 유틸리티는 종속성에 의해 .o를 확장자로 하는 타겟을 만날 때마다 이 명령어에 대입한다.

3.3.5.            와일드 카드 매칭 기법과 대입 참조 기법

와일드 카드 매칭 기법과 대입 참조 기법을 적용한 makefile
SRCS =$(wildcard *.c)  # memo.c calendar.c main.c
OBJECTS=$(SRCS: .c=.o)  # memo.c calendar.c main.c


all : diary

diary : $(OBJECTS)
        $(CC)  -o $@ $^
clean :
                rm -rf *.o diary

SRCS 매크로에서 $(wildcard *.c)는 현재 디렉토리에서 *.c 와 파일명이 일치하는 파일을 찾아 공백을 구분 문자로 SRCS 매크로에 정의한다.
결국 SRCS에는 현재 디렉토리에 있는 memo.c  calendar.c  main.c로 정의된다.
정의된 SRCS는 다시 OBJECTS 매크로에 정의 되는데, $(SRCS: .c=.o)와 같은 대입 참조 기법을 통해 확장자가 .c .o로 바뀐다. 그래서 memo.c  calendar.c  main.c로 정의된다.
위 기술 파일에서 SRCS=*.c 라고 하지 않은 이유는 매크로를 정의할 때는 와일드 카드 확장이 되지 않기 때문이다.
*.c 식의 와일드카드 확장은 타겟 정의절과 명령절에서만 일어난다.
그래서 SRCS 매크로는 memo.c  calendar.c  main.c가 정의되는 게 아니라 *.c가 정의 되기 때문에 OBJECTS 역시 *.o로 정의된다.
$(wildcard *.c)와 같이 사용하는 것을 make로 함수의 사용이라고 한다.
또 다른 make함수 patsubst를 사용하여 한 라인 으로 정리 할 수도 있다.
OBJECTS=$(patsubst %.c %.o $(wildcard *.c))
세 번째 인자로 오는 $(wildcard)의 결과를 공백으로 구분된 문자열들 각각에 대해 첫 번째 인자인 %.c와 매칭되는 것을 %.o로 바꾼다.

3.3.5.1.         참고 echo $?

셸에서 바로 직전 명령 수행 후 리턴 값을 보려면 echo $? 명령으로 확인 할 수 있다. 0이면 정상적으로 수행 O,  0이 아니면 정상적으로 수행 X


댓글 없음:

댓글 쓰기

HOME