02. 알아두면 정말 유용한 C소스 컴파일 과정(1)

Language/C 2011. 9. 8. 12:14

|| C 소스의 전체 컴파일 과정


gcc는 GNU에서 만든 C컴파일러라고 말하지만 사실 컴파일러 드라이버이다.

gcc는 실제 컴파일 과정을 담당하지 않고 전처리기와 C 컴파일러, 어셈블러, 링커를 각각 호출해 주는 역할만 한다.

이렇게 각각의 컴파일 과정에 필요한 명령을 순차적으로 호출해 주는 것을 컴파일러 드라이버라고 한다.


그럼 진짜 컴파일러는 어디에 있을까?

[oneone@study study]$ /usr/libexec/gcc/i686-redhat-linux/4.4.4/

cc1       cc1plus   collect2  f951


cc1: C compiler, -E 옵션을 붙이면 C전처리기로 동작.
cc1plus : C++ compiler, -E 옵션을 붙이면 C++ 전처리기로 동작
collect2 : 링커(내부에서 ld 호출)
f951 : 포트란 컴파일러


/usr/bin 디렉토리에 가보면 다음과 같은 파일들이 있는데, 이를 컴파일러 드라이버들이다.
cc : C컴파일러 드라이버, gcc와 동일
gcc : C컴파일러 드라이버
c++ : C++ 컴파이러 드라이버, g++ 와 동일
g++ : C++ 컴파일러 드라이버
gcj : 자바 컴파일러 드라이버(페도라 미 설치)

[gcc 컴파일 과정]

like.c소스 → [cpp0 or cc1 -E 전처리기] → like.i 전처리 결과 → [cc1 C컴파일러] → like.s 어셈블리 파일 →

→ [as 어셈블러] → like.o 오브젝트 파일 → [ld or collect2] → like 실행파일 


like.c 파일의 컴파일 과정을 알아보기 위해 다음 명령으로 컴파일 한다.

[oneone@study study]$ gcc -v -save-temps -o like like.c

 [oneone@study study]$ gcc -v -save-temps -o like like.c

Using built-in specs.

Target: i686-redhat-linux

Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --disable-dssi --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-1.5.0.0/jre --enable-libgcj-multifile --enable-java-maintainer-mode --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --disable-libjava-multilib --with-ppl --with-cloog --with-tune=generic --with-arch=i686 --build=i686-redhat-linux

Thread model: posix

gcc version 4.4.4 20100503 (Red Hat 4.4.4-2) (GCC)

COLLECT_GCC_OPTIONS='-v' '-save-temps' '-o' 'like' '-mtune=generic' '-march=i686'

 /usr/libexec/gcc/i686-redhat-linux/4.4.4/cc1 -E -quiet -v like.c -mtune=generic -march=i686 -fpch-preprocess -o like.i

ignoring nonexistent directory "/usr/lib/gcc/i686-redhat-linux/4.4.4/include-fixed"

ignoring nonexistent directory "/usr/lib/gcc/i686-redhat-linux/4.4.4/../../../../i686-redhat-linux/include"

#include "..." search starts here:

#include <...> search starts here:

 /usr/local/include

 /usr/lib/gcc/i686-redhat-linux/4.4.4/include

 /usr/include

End of search list.

COLLECT_GCC_OPTIONS='-v' '-save-temps' '-o' 'like' '-mtune=generic' '-march=i686'

 /usr/libexec/gcc/i686-redhat-linux/4.4.4/cc1 -fpreprocessed like.i -quiet -dumpbase like.c -mtune=generic -march=i686 -auxbase like -version -o like.s

GNU C (GCC) version 4.4.4 20100503 (Red Hat 4.4.4-2) (i686-redhat-linux)

        compiled by GNU C version 4.4.4 20100503 (Red Hat 4.4.4-2), GMP version 4.3.1, MPFR version 2.4.2.

GGC heuristics: --param ggc-min-expand=42 --param ggc-min-heapsize=24314

Compiler executable checksum: 72dc052d4f2936b9a3bac52ec96d8f03

COLLECT_GCC_OPTIONS='-v' '-save-temps' '-o' 'like' '-mtune=generic' '-march=i686'

 as -V -Qy -o like.o like.s

GNU assembler version 2.20.51.0.2 (i686-redhat-linux) using BFD version version 2.20.51.0.2-15.fc13 20091009

COMPILER_PATH=/usr/libexec/gcc/i686-redhat-linux/4.4.4/:/usr/libexec/gcc/i686-redhat-linux/4.4.4/:/usr/libexec/gcc/i686-redhat-linux/:/usr/lib/gcc/i686-redhat-linux/4.4.4/:/usr/lib/gcc/i686-redhat-linux/:/usr/libexec/gcc/i686-redhat-linux/4.4.4/:/usr/libexec/gcc/i686-redhat-linux/:/usr/lib/gcc/i686-redhat-linux/4.4.4/:/usr/lib/gcc/i686-redhat-linux/

LIBRARY_PATH=/usr/lib/gcc/i686-redhat-linux/4.4.4/:/usr/lib/gcc/i686-redhat-linux/4.4.4/:/usr/lib/gcc/i686-redhat-linux/4.4.4/../../../:/lib/:/usr/lib/

COLLECT_GCC_OPTIONS='-v' '-save-temps' '-o' 'like' '-mtune=generic' '-march=i686'

 /usr/libexec/gcc/i686-redhat-linux/4.4.4/collect2 --no-add-needed --eh-frame-hdr --build-id -m elf_i386 --hash-style=gnu -dynamic-linker /lib/ld-linux.so.2 -o like /usr/lib/gcc/i686-redhat-linux/4.4.4/../../../crt1.o /usr/lib/gcc/i686-redhat-linux/4.4.4/../../../crti.o /usr/lib/gcc/i686-redhat-linux/4.4.4/crtbegin.o -L/usr/lib/gcc/i686-redhat-linux/4.4.4 -L/usr/lib/gcc/i686-redhat-linux/4.4.4 -L/usr/lib/gcc/i686-redhat-linux/4.4.4/../../.. like.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/i686-redhat-linux/4.4.4/crtend.o /usr/lib/gcc/i686-redhat-linux/4.4.4/../../../crtn.o



위의 출력에서 확인 할 수 있듯이 gcc는 내부적으로 cc1 -E, cc1, as, collect2를 각각 호출함을 알 수 있다.
컴파일시 옵션 -save-temps 옵션에 의해 컴파일 중간 과정에서 생긴 like.i파일과 like.s파일이 보존되었음을 확인 할 수 있다.
[oneone@study study]$ ls
like  like.c  like.i  like.o  like.s

like.i와 like.s를 각각 열어보면

like.i 

.... 생략 

extern char *ctermid (char *__s) __attribute__ ((__nothrow__));

# 908 "/usr/include/stdio.h" 3 4

extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__));




extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__)) ;



extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__));

# 938 "/usr/include/stdio.h" 3 4


# 2 "like.c" 2


int main()

{


 printf("I like you!\n");

 return 0;

}




 like.s

         .file   "like.c"

        .section        .rodata

.LC0:

        .string "I like you!"

        .text

.globl main

        .type   main, @function

main:

        pushl   %ebp

        movl    %esp, %ebp

        andl    $-16, %esp

        subl    $16, %esp

        movl    $.LC0, (%esp)

        call    puts

        movl    $0, %eax

        leave

        ret

        .size   main, .-main

        .ident  "GCC: (GNU) 4.4.4 20100503 (Red Hat 4.4.4-2)"

        .section        .note.GNU-stack,"",@progbits



이런 like.i파일과 like.s파일은 컴파일 중간 과정의 산물로서 컴파일러 오류로 인한 문제나 전처리 과정에서의

오류로 인한 문제가 발생 했을 때 유용하게 사용할 수 있다.


|| cc1 -E에 의한 전처리 과정


첫번째 과정은 헤더파일 삽입, 두 번째 과정은 매크로 치환 및 적용이다.

like.c 

 #include <stdio.h>


void myFunc(void);


int main()

{


        myFunc();

        return 0;

}


void myFunc(void)

{

        printf("I like you\n");

}


main() 함수 내에서 myFunc() 함수를 사용할려면 myFunc() 함수 사용 이전에 함수의 원형을 void myFunc();과 같은 형식으로 선언해야한다.

printf()함수는 include한 stdio.h파일에 선형이 되어있다. 헤더파일을 C 소스내에 include하면 전처리기가 include한 자리에 헤더파일의 내용을 붙인다. 헤더파일은 설정된 디렉토리 순으로 파일을 찾는다. include할 디렉토리를 추가하기 위해선 gcc 옵션으로 -I[디렉토리] 라고 추가하면 된다.


 osCode.c

 #include <stdio.h>

#include <stdlib.h>


void main(){


#ifdef __oneone__

        printf("oneone\n");

#elif __babo__

        printf("babo\n");

#endif


}



[oneone@study study]$ gcc -D__babo__ -g -o osCode osCode.c

[oneone@study study]$ ./osCode

babo


-D옵션은 C 소스 파일의 제일 첫부분에 #define한 것과 의미가 같다.
-D 옵션 인자들은 주로 운영체제별로 코드를 달리해야 될 때 이용한다.
현재 gcc 버전에서는 자동으로 운영체제따라 -D 인자값을 설정한다(리눅스 __linux__ , 윈도 __WINDOWS__)
소스상에서 define되지 않았지만 전처리기가 임의로 define 하는 것을 predefine 매크로 한다.

출처 : 

http://www.oneone.kr/?mid=UnixPUtil&page=1&document_srl=5653 

'Language > C' 카테고리의 다른 글

02. 알아두면 정말 유용한 C소스 컴파일 과정(3)  (0) 2011.09.08
02. 알아두면 정말 유용한 C소스 컴파일 과정(2)  (0) 2011.09.08
01. 컴파일의 의미  (0) 2011.09.08
링킹/로더  (0) 2011.09.08
모듈과 라이브러리  (0) 2011.09.08
: