GNU Compiler Collection(GCC)은 C 컴파일러가 아니다. 전처리기, C컴파일러, 어셈블러, 링커를 각각 호출하는 역할을 할 뿐이다. 전처리기를 cpp0, 컴파일러를 cc1, 어셈블러를 as, 링커를 ld라고 할 때, sample.c 라는 파일의 컴파일 과정은 아래와 같다.
sample.c —(cpp0)—> sample.i —(cc1)—> smaple.S —(as)—> sample.o —(ld)—> a.out
gcc 설치 방법은 다음과 같다. 먼저 sudo apt-get install gcc 하여 gcc자체를 인터넷에서 받아와 설치한다. 다음으로 sudo apt-get install build-essential를 입력하여 gcc를 제대로 사용하기 위해 관련 라이브러리를 설치하면 된다.
다음은 gcc command line 옵션에 대한 설명이다.
-E : 전처리 과정의 결과를 화면에 보이는 옵션이다.
-S : cc1으로 컴파일까지만 수행한다. 따라서 .S 파일이 최종 결과물이 된다.
-c : as로 어셈블러까지만 수행한다. 따라서 .o파일이 최종 결과물이 된다.
-v : 컴파일 과정을 화면에 출력한다.
–save-temps : 컴파일 과정에서 생성되는 전처리 파일과 어셈블리 파일을 현재 디렉토리에 저장한다.
-o : 컴파일된 파일명을 지정한다.
cpp0 관련 옵션은 아래와 같다.
-I : 전처리 과정에서 헤더 파일을 탐색하는 기본 디렉토리(/usr/local/include, /usr/lib/gcc-lib/리눅스머신/리눅스버전/include, /usr/include)를 추가한다.
-D : 매크로를 외부에서 정의할 때 이용하는 옵션이다.
-M : make를 위한 소스파일의 모든 종속항목을 출력한다.
-MM : make를 위한 소스 파일에서 기본 include 디렉토리에 있는 헤더 파일은 빼고 종속 항목을 출력한다.
-C : -E옵션과 함께 사용하며 전처리 과정에서 주석을 제거하지 않는다.
cc1 경고메시지 관련 옵션은 다음과 같다.
-Wall : 모든 모호한 문법에 대한 경고메시지를 출력한다.
-W : 합법적이지만 모호한 코딩에 대해서 부가적인 정보를 제공한다.
-w : 모든 경고 메시지를 제거한다.
cc1 최적화 관련 옵션은 다음과 같다.
-00 : 최적화를 수행하지 않으며 인라인 함수가 사용되었더라도 확장되지 않는다.
-01 : 1-level의 최적화를 수행한다.
-02 : 가장 많이 사용하는 최적화 옵션으로 거의 대부분의 최적화를 수행한다.
-03 : 가장 높은 레벨의 최적화 를 수행하기 때문에 모든 함수를 인라인 함수와 같이 취급해 버린다.
cc1 디버깅 관련 옵션은 아래와 같다.
-g : gdb에게 제공하는 정보를 바이너리에 삽입한다. 주로 디버깅 심볼이라 하여 c프로그램 소스를 바이너리 안에 삽입한다.
-g도 -0과 같은 레벨이 있는데, -g0은 디버깅 정보를 삽입하지 않고 –g3은 디버깅 정보를 가장 많이 제공한다. 기본적으로는 –g2와 동일하다.
-pg 프로파일을 위한 코드를 삽입한다. –pg 옵션을 사용하여 컴파일한 프로그램이 종료하게 되면 프로파일 정보가out 파일에 저장된다. 이후 gprof에 의해 gmon.out 파일의 내용을 분석하여 어떤 함수가 얼마나 호출되었고 시간이 얼마나 걸렸는지 확인할 수 있다.
Ld 관련 옵션은 다음과 같다.
-L : 라이브러리를 찾을 디렉토리를 지정한다. 기본적으로는 다음의 디렉토리에 위치한 라이브러리 파일(/usb/lib, /usr/lib/gcc-lib/리눅스종류/리눅스버전)만을 참고한다.
-l : 같이 링크할 라이브러리를 지정한다.
-shared : 공유 라이브러리와 정적 라이브러리가 같이 있을 경우 공유 라이브러리를 우선하여 링크한다.
-static : 정적 라이브러리를 우선하여 링크한다.
-nostdlib : 링크시에 표준 c 라이브러리를 사용하지 않는다.
-Wl,[링크옵션] : gcc를 거치지 않고 바로 링크에게 옵션을 준다.
-s : 실행 파일에서 심볼 테이블을 제거한다.
-x : 출력 파일에 로컬 심볼을 제거한다.
-n : 텍스트 영역을 읽기전용으로 만든다.
-r : 추후 링크가 가능하게 오브젝트를 만든다.
-e [name] : 시작 심볼을 name 심볼로 사용한다.
-M : 심볼들의 정보를 자세히 출력한다.
개발환경요소에는 gcc와 같은 개발 환경, 헤더 파일, 정적/동적 라이브러리가 있다. gcc를 이용하면 Source code를 아래와 같이 컴파일 가능하다.
cc –o Test Test.c
이때 error: stdi o.h: No such file or directory 과 같은 에러가 발생하면 sudo apt-get install build-essential를 하여 필요한 라이브러리를 설치해준다. 컴파일 후에 생성된 파일을 ls 명령어를 통해 확인해보자. ls 명령어 옵션은 아래와 같다.
– Dos명령어인 dir과 유사한 명령어이다.
– ls –al : 모든파일을 나열한다.
– ls –c : 최근에 생성순으로 나열한다.
컴파일 후 생성된 파일 실행을 하기 위해서는 “./TEST” 를 command line에 입력하면 된다. 이때 “./” 는 현재 디렉토리가 되며, “../”는 상위 디렉토리가 된다. 즉, 현재 디렉토리내에 있는 Test라는 프로그램을 실행하겠다는 뜻이다.
헤더파일은 상수 정의문, 시스템과 라이브러리 함수 호출을 위한 선언을 제공한다. 사용자가 작성한 함수의 경우 컴파일러에게 미리 알려줘야 한다. 헤더파일로부터 정보를 받은 컴파일러(X윈도우 시스템, C++ etc.)는 실제 함수임을 인지할 수 있다. C와 관련한 대부분의 헤더 파일 위치는 /usr/include이다. 그 외에도 /usr/include/sys가 있다. gcc 컴파일시 지정된 표준 위치가 아닌 다른 디렉토리에 위치한 헤더파일을 지정할 때 아래와 같이 옵션을 줘야 한다.
– Ex) 표준 디렉토리와 다른 디렉토리 내의 헤더파일 포함할 경우: gcc –I/beginningLinuxProgramming/include Test.c
grep 명령어는 파일에서 주어진 문자를 포함하고 있는 줄을 찾을 때 사용한다. 예를들어 cat /usr/include/*.h | grep EXIT 입력시 /usr/include내에 있는 모든 h파일 들 중에서 EXIT내용을 출력한다.
라이브러리는 미리 컴파일된 오브젝트(object)들의 집합이다. Object들은 자주 사용하는 함수의 소스코드를 컴파일하여 만들 수 있다. 표준 시스템 라이브러리는 /lib 와 /usr/lib에 위치하며 라이브러리의 이름은 대개 lib로 시작한다. library를 사용하는 이유는 자주 사용하는 함수들을 쉽게 사용할 수 있고, 미리 컴파일된 라이브러리를 링크(link)만 하면 사용 가능할 수 있으므로 컴파일 시간을 크게 단축시킬 수 있기 때문이다. 라이브러리의 종류에는 정적(static) 라이브러리(.a)와 공유(shared) 라이브러리(.so, .sa)가 있다. Shared Library는 컴파일 시에 라이브러리 함수가 사용된 곳에 공유 라이브러리를 사용한다는 표시만해 놓고 바이너리(binary)가 실행되는 순간 동적 링크(ld-linux.so.2)에 의해 링크되는 라이브러리이다. 즉 컴파일 할 때가 아닌 실제로 프로그램이 실행될 때 동적으로 링크한다. shared 라이브러리는 메모리를 효율적으로 사용 할 수 있다는 장점이 있다.- 컴파일러와 링커는 프로그램 코드와 라이브러리를 하나의 실행 가능한 프로그램으로 혼합한다. static library는 속도가 빠르고 바이너리를 배포할 때 제한이 없지만 많은 프로그램이 같은 라이브러리 함수를 사용할 때 메모리 낭비와 사이즈가 크다. 반면 shared library는 사이즈가 작다는 장점을 가지지만, 실행 속도가 약간 느리고 바이너리를 배포할 때 컴파일 할 당시에 사용한 공유 라이브러리와 같은 메이저(major) 버전을 가지는 공유 라이브러리가 있는 시스템에서만 동작한다. 또한 프로그램이 함수 코드 자체를 가지는 것이 아니라 실행시 유효한 공유 코드에 대한 참조를 가지도록 링크 되어야 한다.
Static library 실습
Static Library는 컴파일 시 링크에 의해 라이브러리의 오브젝트 코드가 만들고자 하는 바이너리에 추가되는 형태로 사용하는 라이브러리이다. 컴파일 할 때 -l 옵션을 사용하여 추가로 필요한 라이브러리를 지정해야한다. ar(archive) 명령을 이용하여 여러 개의 파일을 하나의 파일로 묶을 수 있다. 두 개의 함수를 포함하고 있는 라이브러리를 만든 다음, 이 함수를 사용하는 예제를 만들어보자. 제일 먼저 작업을 진행할 디렉토리를 하나 생성한다.
[ungc00@zeus ~]mkdir ch1
[ungc00@zeus ~]cd ch1
다음으로 fred.c 와 bill.c 두 개의 파일을 vi 편집기를 이용하여 작성해보자.
/*Filename :fred.c */
#include <stdio.h>
void fred(int arg)
{
printf(“fred: you passed %d\n”, arg);
}
/* Filename :bill.c */
#include <stdio.h>
void bill(char* arg)
{
printf(“bill:you passed %s\n”, arg);
exit(0);
}
다음 단계는 작성한 두 개의 소스 파일을 gcc –c 옵션을 이용하여 object 파일을 생성하는 것이다.
[ungc00@zeus ch1]$gcc –c bill.c fred.c
[ungc00@zeus ch1]$ls –l -> bill.o 와 fred.o 가 생성되었는지 확인
다음은 bill 함수를 호출하는 프로그램 작성이다. 준비작업으로 라이브러리를 위한 헤더 파일(“lib.h ”)을 생성한다.
/* Filename :lib.h */
void bill(char *);
void fred(int);
/* File:program.c */
#include “lib.h”
int main()
{
bill(“Hello World”);
exit(0);
}
마지막으로 프로그램을 컴파일 하고 테스트 해보자.
[ungc00@zeus ch1]$gcc –c program.c
[ungc00@zeus ch1]$gcc –o program program.o bill.o
[ungc00@zeus ch1]$ ./program
bill : you passed Hello World
추가적으로 정적 라이브러리를 생성하여 사용해보자.
[ungc00@zeus ch1]$ ar crv libfoo.a bill.o fred.o
a – bill.o
a – fred.o
[ungc00@zeus ch1]$ ls –l
[ungc00@zeus ch1]$ gcc –o ex_program program.o libfoo.a
[ungc00@zeus ch1]$ ./program
bill : you passed Hello World
참고로 ar t libfoo.a 와 같이 t 옵션을 이용하여 아카이브에 있는 파일 리스트를 출력하여 확인할 수 있다.
Shared library 실습
shared library 생성을 위한 소스 코드를 컴파일하자.
[ungc00@zeus ch1]$gcc –fPIC –c bill.c fred.c
[ungc00@zeus ch1]$gcc –shared –WI,-soname,libfoo.so.0 –o libfoo.so.0 bill.o fred.o
… 중략
… 이 후 과정은 root 권한을 요구하고, 범위를 넘어가기 때문에 생략합니다.
[ungc00@zeus ch1]$ldd program
>>> ldd 명령어를 이용하여 바이너리 파일이 요구하는 공유 라이브러리 목록을 확인할 수 있다.
homework#1
library 작성
– convert.c : char *a 에 저장된 알파벳을 소문자 > 대문자, 대문자 > 소문자 로 convert 시키는 함수
– 이 함수를 library로 저장
main 함수에서 사용자 입력을 getchar로 받아서 convert.c를 사용
실제 program에서 library 링크된 a.out 작성