티스토리 뷰

Application/C/C++

프로그래밍 연습

알 수 없는 사용자 2008. 2. 20. 21:51

I. C 컴파일러

1. C 컴파일러의 종류

Unix환경에서 사용되는 C compiler에는 4가지 정도가 있다.

cc: 표준 C compiler

CC: 표준 C++ compiler

gcc: GNU판 C compiler

g++: GNU판 C++ compiler

2. 컴파일 과정

Array



- 전처리(preprocessing) : #define, #include, #if와 같은 지시자를 처리한다.

- 컴파일(compile) : 입력파일을 어셈블리 파일로 만든다.

- 어셈블리(assembly) : .o 확장자를 가진 오브젝트 파일로 만든다.

- 링크(linking) : 오브젝트 파일과 라이브러리 함수를 실행 파일의 적절한 곳에 배치한다.

3. 컴파일 예제

% gcc sourcefile

compile된 결과는 기본적으로 a.out 이라는 파일 이름을 갖는다.

[Useful options on gcc]

-o : 결과로 생기는 실행파일의 이름지정 한다.

-Wall : Warning all의 약자로 warning까지 표시해 준다.

-g : compiler시 debugging정보를 집어 넣는다.

-c : compile만 하고 link를 수행하지 않는다.

-v : 실행 전과정(compiling, linking등)의 진행상황을 화면으로 보여준다.

-ansi : 순수 ansi C code를 compile 한다.

-llibrary : 해당 library를 link 시킨다.

-Idir : include file을 찾을 directory를 추가

-Ldir : -l option에서 지정한 library를 찾을 directory 추가

-O : code optimization을 수행한다. (참조: -O2, -O3, -O0)

-s : 실행파일 만들 때 심볼 테이블을 제거해 준다

예를 들어

$ gcc -o test -Wall -g test.c -lm

이라 치면 test.c를 컴파일 해서 test라는 이름의 실행파일을 생성하고, 이때 error뿐 아니라 warning정보까지 모두 보여 주고, debugging정보를 함께 집어 넣게 되며, 수학함수를 사용하기 위한 library를 link시키게 된다.

-l 옵션 뒤에 오는 library이름들은 man page를 사용하여 알아내면 된다.

예를 들어 sin() 함수를 사용해야 할 경우,

$ man 3 sin

하면, include 해야 할 header file이름과 library파일의 이름 또는 컴파일 할 때 필요한 option에 관한 정보를 얻을 수 있다. 이때 만일 해당 library 파일의 이름이 ‘libm.a’일 경우 ‘lib’와 ‘.a’를 제외하고 ‘m’만 사용하여 ?lm이라고 option을 주면 된다.

사용자가 만든 header파일을 compiler가 찾지 못하거나 header파일이 다른 directory에 있을 경우, compiler에게 header파일을 찾을 경로를 ?I option을 사용하여 추가 지정할 수 있다.

예를 들어 test.c에서 include한 num.h라는 header파일이 ./num/num.h 에 있을 경우 다음과 같이 하면 compiler가 num.h파일을 찾을 수 있게 된다.

$ gcc test.c ?I./num

II. Make 이론

1. make란? & make의 목적

- make: 복잡한 컴파일 작업을 자동화하는 프로그래밍 언어이다.

- 목적 : make를 적절히 활용하면 불필요한 컴파일 작업을 줄여므로 프로그램 개발 시간을 크게 절약할 수 있다.

- 기본 아이디어 : 프로그램이 여러 개의 소스 파일로 이루어졌다면, 모든 소스 파일을 컴파일 하기보다는 컴파일을 마지막으로 한 시점으로부터 변경된 소스 파일만 컴파일한다. 컴파일 여부는 소스 파일과 오브젝트 파일의 갱신시간을 비교하여 결정한다.

2. make 문법

Makefile은 기본적으로 아래와 같이 target, dependency, command의 세개로 이루어진 기본적인 규칙(rule)들이 계속적으로 나열되어 있다. make가 지능적으로 파일을 갱신하는 것도 모두 이 간단한 규칙에 의하기 때문이다.

targetList : dependencyList

commandList

여기서 target 부분은 command가 수행이 되어서 나온 결과 파일을 지정한다. 당연히 목적 파일(object file)이나 실행 파일이 될 것이다.

command부분에 정의된 명령들 depenency부분에 정의된 파일의 내용이 바뀌었거나, 목표 부분에 해당하는 파일이 없을 때 이곳에 정의된 것들이 차례대로 실행이 된다. 일반적으로 쉘에서 쓸 수 있는 모든 명령어들을 사용할 수가 있으며 bash에 기반한 쉘 스크립트도 지원한다.

명령 부분은 꼭 TAB 글자로 시작해야 한다. 그냥 빈칸 등을 사용하면 make 실행 중에 에러가 난다. make가 명령어인지 아닌지를 TAB 가지고 구별하기 때문이다.

예제를 통해 make의 기본 문법을 익혀나가자.

또 Makefile의 주석은 #으로 한다.

3. Make의 실행

% make [-f makeFileName]

보통 default로 Makefile이라는 이름을 쓴다.

III. make 실습

1. Make 예제 1

main.c는 f1.c와 f2.c.의 함수를 사용하고 모든 .c파일은 io.h를 include하는 프로그램이 있다고 하자. (/afs/p/class/cse/cs103/lab/example/make 에서 이 파일들을 얻을 수 있다.)

% cat io.h

#ifndef __IO_H__

#define __IO_H__

#include <stdio.h>

#endif /* __IO_H__ */

% cat main.c

#include “io.h”

int main(void)

{

printf("main start..n");

f1();

f2();

printf("main end..n");

}

% cat f1.c

#include “io.h”

void f1(void)

{

printf("f1 start..n");

printf("f1 end..n");

}

% cat f2.c

#include “io.h”

void f2(void)

{

printf("f2 start..n");

printf("f2 end..n");

}

이 파일들을 gcc로 컴파일 해보자..

% gcc -c main.c

% gcc -c f1.c

% gcc -c f2.c

% gcc -o test main.o f1.o f2.o

이제 이 파일들을 make로 컴파일 해보자.

% vi Makefile

test : main.o f1.o f2.o

gcc -o test main.o f1.o f2.o

main.o : io.h main.c

gcc -c main.c

f1.o : io.h f1.c

gcc -c f1.c

f2.o: io.h f2.c

gcc -c f2.c

make는 재귀적(recursive)으로 동작하므로 타겟을 만드는데 필요한 모든 파일이 갱신될 때까지 의존성을 계속 체크한다. 위의 Makefile는 make에게 다음과 같은 단계를 거치도록 지시한다.

1. 오브젝트 파일 main.o, f1.o, f2.o 그 자체가 타겟에 속하는 지 판단한다.

2. 세 파일 모두 타겟이므로, 각 파일이 해당하는 소스에 대하여 갱신되어 있는지 확인한다.

3. main.o 가 io.h나 main.c보다 더 오래되거나 존재하지 않으면 새로운 버전을 만든다.

4. f1.o 가 io.h나 f1.c보다 더 오래되거나 존재하지 않으면 새로운 버전을 만든다.

5. f2.o 가 io.h나 f2.c보다 더 오래되거나 존재하지 않으면 새로운 버전을 만든다.

6. 새로운 버전의main.o, f1.o, f2.o 과 test의 수정 시간을 비교한다.

7. test가 더 오래되었거나 존재하지 않으면 오브젝트 파일 main.o, f1.o, f2.o를 링크하여 test의 새로운 버전을 생성한다.

% make

gcc -c main.c

gcc -c f1.c

gcc -c f2.c

gcc -o test main.o f1.o f2.o

%./test

main start..

f1 start..

f1 end..

f2 start..

f2 end..

main end..

2. Make 예제 2 : 매크로 사용

OBJECTS = main.o f1.o f2.o

test : $(OBJECTS)

gcc -o test $(OBJECTS)

main.o : io.h main.c

gcc -c main.c

f1.o : io.h f1.c

gcc -c f1.c

f2.o: io.h f2.c

gcc -c f2.c

3. 레이블 사용

OBJECTS = main.o f1.o f2.o

test : $(OBJECTS)

gcc -o test $(OBJECTS)

main.o : io.h main.c

gcc -c main.c

f1.o : io.h f1.c

gcc -c f1.c

f2.o: io.h f2.c

gcc -c f2.c

clean :

rm $(OBECTS)

% make

% make clean

4. 미리 정해져 있는 매크로

CC = cc

CFLAGS

CXX

CPPFLAGS

% make ?p // make 안에서 쓰는 모든 변수를 볼 수 있다.

5. 미리 정해져 있는 매크로 이용 예제

OBJECTS = main.o f1.o f2.o

SRCS = main.c f1.c f2.c

CC = gcc

CFLAGS = -g

TARGET = test

$(TARGET) : $(OBJECTS)

$(CC) -o $(TARGET) $(OBJECTS)

main.o : io.h main.c

f1.o : io.h f1.c

f2.o: io.h f2.c

// CFLAGS를 인식하므로gcc ?c main.c를 사용하지 않아도 됨을 주의 하자.


6. implicit rule

.c.o :

gcc -o $* $<

clean :

rm *.o $@

$< : 현재의 목표 파일보다 더 최근에 갱신된 파일 이름이다.

$* : 확장자가 없는 현재의 목표 파일(Target)

$@ : 현재의 목표 파일(Target)

% make main.o

% ls

7. multiple target

.SUFFIXES : .c .o

CC = gcc

CFLAGS = -O2 -g

OBJS1 = main.o test1.o

OBJS2 = main.o test2.o

OBJS3 = main.o test3.o

SRCS = $(OBJS1:.o=.c) $(OBJS2:.o=.c) $(OBJS3:.o=.c)

all : test1 test2 test3

test1 : $(OBJS1)

$(CC) -o test1 $(OBJS1)

test2 : $(OBJS2)

$(CC) -o test2 $(OBJS2)

test3 : $(OBJS3)

$(CC) -o test3 $(OBJS3)

clean:

rm *.o

8. 프로그램 제작에 쓰일 수 있는 Makefile

이 예제는 ../디렉토리에 있는 io.h와 libsnmp.a를 사용하는 예제이다.

.SUFFIXES : .c .o

CC = gcc

INC = -I../ # include 되는 헤더 파일의 패스를 추가한다.

LIBS = -lsnmp # 링크할 때 필요한 라이브러리를 추가한다.

LIBDIR = -L../ # 링크할 라이브러리의 패스를 적어준다.

CFLAGS = -g $(INC) # 컴파일에 필요한 각종 옵션을 추가한다.

OBJS = main.o read.o write.o # 목적 파일의 이름을 적는다.

SRCS = $(OBJS:.o=.c) # 소스 파일의 이름을 적는다.

TARGET = myapp # 링크 후에 생성될 실행 파일의 이름을 적는다.

all : $(TARGET)

$(TARGET) : $(OBJS)

$(CC) -o $@ $(OBJS) $(LIBS) $(LIBDIR)

clean :

rm -rf $(OBJS) $(TARGET)

oclean :

rm -rf $(OBJS)

dep :

gccmakedep $(INC) $(SRCS) #리눅스에 사용되는 gccmakedep이다.