makefile
-
[입문자 튜토리얼] - C언어 소스 코드 파일 분리 2편 (feat. 헤더파일)2021.12.19
-
Libft 과제 시 유의점 (테스터기, protected, Makefile)2021.05.31
[입문자 튜토리얼] - C언어 소스 코드 파일 분리 2편 (feat. 헤더파일)
지난 시간에는...
지난 시간에는 어떤 게 알아보기 어려운 코드인지, 그 문제점을 알아보았다. 또 그걸 해결하기 위한 "코드 깔끔하게 짜는 법"을 알아보았다.
"한 파일안에 코드 줄 수가 너무 많다고요? 그러면 나더러 어떻게 하란거에요. 뭐 다른 방법이 있나요?"
오늘은 이 질문에 대한 답변을 하는 시간을 갖도록 한다.
자, 저번에서 그 한 파일에 있던 코드들을 1. 배열을 이용한 함수, 2. 연결리스트를 이용한 함수, 3. 메인함수와 그 외의 함수라는 총 3가지의 분류로 나누어, 각각을 따로 파일에 담았다하자. 그럼에도 아직 문제가 남아있다.
"파일을 여러 개로 나눠요? 그러면 main.c 파일에서 어떻게 다른 파일의 함수를 읽어오나요? 게다가 컴파일은 어떻게 해요?"
사용자 정의 헤더파일
우선, 코드를 길게짜는 사람이라면, 함수의 원형을 main문 위쪽에 선언해주는 방법을 아마 알 것이다.
#include <stdio.h>
void hello(); // 반환값이 없는 hello 함수 원형 선언
int main()
{
hello(); // hello 함수 호출
return 0;
}
void hello() // 반환값이 없는 hello 함수 정의
{
printf("Hello, world!\n"); // Hello, world! 출력
}
main문에서 위쪽에 함수를 선언해주면, 어느 위치에 함수가 오더라도 메인에서 이 함수를 읽어들여 프로그램을 돌릴 수 있다.
파일을 여러개로 나눌 경우에도 마찬가지다. 함수 파일마다 내 전체함수들의 프로토타입과 사용되는 구조체를 전부 상단에 넣어주면 될 것 같다. 근데 그러면 깔끔하지가 않다... 방법이 없을까?
이 경우 쓰게 되는 게 사용자 정의 헤더파일이다.
C언어 기초 - 헤더파일 만드는 방법과 사용하는 방법
본문 목표 프로젝트가 복잡하고 규모가 클 수록, 코드가 많이 길어지게 된다. 코드가 길어지면 가독성이 떨어지게 된다. 가독성이 떨어지면, 코드 개발이 복잡해지고, 오류가 발생할 확률이 높
diyver.tistory.com
위 블로그를 참고하면 좋다.
헤더 파일 예시
#ifndef _EXAMPLE_H_
#define _EXAMPLE_H_
/*
* include할 헤더 목록 적기
*/
typedef struct 구조체 명
{
...
} 구조체;
int 함수1();
int 함수2();
void 함수3();
void 함수4();
#endif
다음과 같은 방식으로 헤더파일에 모든 include할 헤더목록, 구조체명, 내가 사용한 모든 함수의 프로토타입을 적어준다.
해당 파일의 이름을 example.h라고 저장했다고 해보자.
그럼 내가 가진 모든 C 소스 파일의 위쪽에 모두 #include "example.h" 라는 선언을 한줄만 추가해주면 된다.
그럼 모든 파일의 상단에 내가 사용할 구조체&함수들을 전부 선언해놓은 것과 동일한 효과가 난다!
여기에서 #include <example.h>가 아닌, "example.h"로 따옴표를 사용해줘야만 한다. 사용자가 정의한 이런 헤더파일은 원칙적으로 따옴표를 써야하기 때문이다.
그럼 이제, 어떻게 main 파일에서 다른 파일의 함수들을 읽어오냐는 질문에 대한 답변은 끝이 났다.
그러면 이제 남은 질문은?
그렇게 여러 개로 나눠진 파일들을 어떻게 묶어서 컴파일 해요?
이거다.
다음 포스팅에서 이어서 이것을 설명하도록 하겠다.
다음 포스팅에서는 컴파일의 대략적인 과정과, 컴파일러, 컴파일 명령어 그리고 makefile에 대해 배운다.
'IT > 튜토리얼 및 가이드' 카테고리의 다른 글
[개인적인 팁] 영어 논문 쉽게 읽는 법 (5) | 2022.03.01 |
---|---|
[번역] C에서 시그널을 보내고 처리하는 방법 (kill, signal, sigaction) (0) | 2022.01.06 |
[입문자 튜토리얼] - C언어 소스 코드 파일 분리 1편 (feat. 코드를 깔끔하게) (0) | 2021.12.19 |
[Github] 깃허브 왕초보 사용법 - 기본편3 (0) | 2021.12.19 |
[Github] 깃허브 왕초보 사용법 - 기본편2 (0) | 2021.12.19 |
Libft 과제 시 유의점 (테스터기, protected, Makefile)
libft나 다른 하위 서클 함수들은 주로 어떤 정해진 것을 구현하는 문제인 경우가 많다. 따라서 깃허브에 libft 과제를 뮬리넷(42채점 서버)처럼 테스트 할 수 있는 테스터기가 있다. 이 테스터기들을 통해 미처 하지 못한 예외처리나 필수사항의 구현을 체크하여 코드를 보완할 수 있다. 아주 중요하다.
* 테스터기
- 내가 주로 쓴 테스터기는 4가지가 있다.
1) Libftest
2) libft-unit-test
3) libft-war-machine
4) libftester
깃허브에 쳐보면 이 외의 더 많은 테스터기들도 다 나온다.
기본적인 사용법은 이렇다. 일단 해당 깃 레포지터리를 깃클론 받는다. 그다음 libft-unit-test와 libftester에서는 그 안의 Makefile에 있는 평가할 libft 과제 디렉토리의 상대경로를 입력한다.
Libftest와 libft-war-machine에서는 ./grademe.sh 를 실행한 다음, 생성된 my_config.sh에서 libft 과제 디렉토리의 상대경로를 변경한다.
이런 식으로 채점될 파일의 경로를 다 설정해준 후에는, 해당 테스터들의 README.md 파일을 보면 알 수 있듯이, Libftest와 libft-war-machine에서는 ./grademe.sh를 실행하고, libft-unit-test에서는 make f, libftester에서는 make a 를 실행한다.
이러면 makefile의 구성요소가 잘 있는 지도 점검해주고, 보너스 함수까지 전체 파일을 채점해볼 수 있다. 보통은 3개의 테스트기만 돌려도 된다 하는 경우도 있는데, 4개까지 돌리기를 권장한다. (그렇지만 보통 libftester나 libft-unit-test가 제일 깐깐한 것 같다)
의외로 libftester 테스트기에서, 다른 테스트기가 빠뜨린 예외처리들이나 segfault를 몇 개 잡아주는 경우가 있다.
일단 테스트기에서 문제가 틀렸다고 나오면, (vs코드로 테스트기 폴더를 열어) 해당 테스트기의 main 함수를 확인하면서 디버깅하면 큰 도움이 된다.
아무래도 과제에서 함수를 40개 넘게 구현하는 것도 있고, 인터넷의 정보들도 완벽하지 않기 때문에 혼자서만 머리를 짜내서는 예외처리를 찾기 힘들 수 있다. 이때, 해당 테스트기의 main 함수나 그에 따른 정답으로 나오는 정상출력 결과값, 그리고 내 출력 결과값을 확인하며 디버깅하는 것이 과제 진행 속도를 올려준다.
* protected / unprotected
- 매개변수로 NULL이 들어왔을 때의 예외처리 여부
주로 libft-unit-test 테스트기를 돌려볼 때, 방패모양과 터지는 모양의 그림이 나오면서 이게 뭔지에 대한 궁금증이 생긴다.
방패 모양은 함수가 protected 되었다는 뜻이고, 터지는 그림은 함수가 unprotected 되었다는 뜻이다.
매개변수로 주어진 변수에 NULL 값이 들어올 경우의 예외처리를 하면 protected 된 함수, 안하면 unprotected 된 함수다.
어떤 방식을 택하든 뮬리넷의 채점 결과에서는 별 상관없지만, 동료평가에서는 그렇게 구현한 이유에 대해서 적당한 근거를 들어 설명하여 디팬스 해야한다.
나는 우선 unprotected 된 함수로 구현하였고, 내 초반의 논리는 이러했다.
일단 기존의 C 라이브러리 함수가 그렇게 구현되있기 때문이다. 특히 실제 개발자로 일하게 된다면 내 구현 함수보다는 최적화된 시중의 C 라이브러리를 사용할 텐데, 실제 함수를 사용할 때와 다른 결과값을 낼 수 있게 만들기보다는, 실제 함수의 동작에 익숙해지기 위해 unprotected로 구현하는 것을 택했다. 또한 protected 처리를 할 경우, 0을 리턴할 때 그 원인이 함수의 정상적인 결과값인지, malloc 실패로 인한 NULL 반환인지, 매개변수로 인한 protected 처리인지 원인을 파악하기 애매해지는 경우가 생길 수 있으며, 애초에 null을 매개변수에 넣을 수 있게 하는 것이 좋지 않은 습관이라고 생각한 점도 한 이유가 되었다.
그러나, 동료평가를 하면서 함수에 protected 처리를 해주는 게 더 바람직하다는 것을 알게되었다.
무엇보다 현직 개발직에서는 함수를 protected 로 만드는 것을 굉장히 중요시 여긴다고 한다. 어떤 상황에서도 프로그램이 segfault 등의 예기치 못한 오류로 종료되지 않고, 일단 어떻게든 동작을 이어가게끔 만들어야 하기 때문이다. 또한 고객이 함수의 매개변수에 null 값을 넣게 되는 경우도 빠짐 없이 처리해주어야하기 때문에, protected 된 함수를 만들 수 밖에 없다.
그래서, 만일 내가 다시 함수를 구현하게 되거나 후에 다시 libft.a 라이브러리를 사용하게 된다면, protected 처리를 더해 함수를 수정/보완할 계획이 있다.
* Makefile
- relink 방지가 중요 !
- relink : 변경된 object 파일 없이 make를 실행하였을 때, 굳이 필요없는데도 다시 링킹이 되는 현상
-> 변경된 object 파일이 없어도 다시 라이브러리 (libft.a)를 생성하게 된다.
- 원래 make는 파일의 수정 시간을 확인하여, 수정된 파일들만 다시 컴파일 하기 때문에, 불필요한 relink를 없애 효율적인 컴파일-링킹이 이뤄질 수 있게한다.
보통 흔하게 짜는 방식(깃허브 참고)으로 Makefile을 짜면, make 를 2번 실행 시, "libft.a가 최신이다(up-to-date)" 라는 안내문과 함께 리링크가 이루어지지 않는다. 그러나 보너스 함수까지 라이브러리에 같이 아카이브하는 make bonus를 2번 실행할 경우, libft.a로 보너스파일까지 링킹하여 다시 아카이브 되는 과정이 반복되서 이루어지는 것을 볼 수 있다. 즉, 리링크가 일어나는 것이다.
원래는 동료평가 시에, 보너스 파일의 리링크까지는 어쩔 수 없다고 보고 잡지 않는 사람들이 많았다.
하지만, 최근에 리링크 방지를 위해 조건문 + 매크로를 정의하여 bonus 리링크까지 방지하는 코드가 슬랙을 통해 퍼졌다.
그 후로, 깐깐한 카뎃들은 동료평가에서 이 부분까지 고려하여 fail을 주기도 한다. 따라서 나 또한 친구가 fail을 받은 것을 본 입장에서, 만일을 위해 리트라이 때 makefile을 저런 방식으로 수정하였다. 저 Makefile을 사용하는 경우에, github의 테스터기에서는 보너스파일이 존재하지 않는다며 채점되지 않는 경우도 생기지만, 뮬리넷 채점서버에서는 잘 동작하니 걱정하지 않아도 된다.
이 코드를 사용하면 make bonus를 2번 실행 시,
make WITH_BONUS=1 all
make[1]: Nothing to be done for `all'.
라는 문구와 함께 리링크가 되지 않는다.
다음으로는 코드 예시를 소개하겠다.
- Makefile 예시 코드 (relink 방지):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
SRCS = ft_atoi.c ft_bzero.c ft_calloc.c ft_isalnum.c ft_isascii.c\
ft_isalpha.c ft_isdigit.c ft_isprint.c ft_itoa.c ft_memccpy.c\
ft_memchr.c ft_memcmp.c ft_memcpy.c ft_memmove.c ft_memset.c\
ft_putchar_fd.c ft_putendl_fd.c ft_putnbr_fd.c ft_putstr_fd.c\
ft_split.c ft_strchr.c ft_strdup.c ft_strjoin.c ft_strlcat.c\
ft_strlcpy.c ft_strlen.c ft_strmapi.c ft_strncmp.c ft_strnstr.c\
ft_strrchr.c ft_strtrim.c ft_substr.c ft_tolower.c ft_toupper.c
SRCS_BONUS = ft_lstadd_back.c ft_lstadd_front.c\
ft_lstclear.c ft_lstdelone.c ft_lstiter.c\
ft_lstlast.c ft_lstmap.c ft_lstnew.c\
ft_lstsize.c
OBJS = $(SRCS:.c=.o)
OBJS_BONUS = $(SRCS_BONUS:.c=.o)
NAME = libft.a
ifdef WITH_BONUS
OBJ_FILES = $(OBJS) $(OBJS_BONUS)
else
OBJ_FILES = $(OBJS)
endif
CC = cc
RM = rm -f
FLAGS = -Wall -Werror -Wextra
%.o : %.c
$(CC) $(FLAGS) -c -o $@ $<
$(NAME): $(OBJ_FILES)
ar cr $@ $^
bonus:
make WITH_BONUS=1 all
all: $(NAME)
clean:
$(RM) $(OBJS) $(OBJS_BONUS)
fclean: clean
$(RM) $(NAME)
re: fclean all
.PHONY: bonus all clean fclean re
|
cs |
- ifdef ~ else ~ endif 문을 사용하여, 매크로가 정의되었는지 되지 않았는지에 따라 컴파일 될 object 파일들의 목록을 다르게 구성하였다.
예)
all :
ifdef CC
@echo "CC 매크로는 정의되어 있습니다."
else
@echo "CC 매크로는 정의되지 않았습니다."
endif
- ar rcs libft.a -> 정적 라이브러리를 만드는 명령어
- 참고) 정적 라이브러리 vs 동적 라이브러리
정적링크라이브러리 (Static Link Libarary - .lib
컴파일 시에 함수가 실행파일에 연결된다. 실행 파일에 함수의 코드가 복사되기 때문에, 실행파일의 크기가 커지는 단점이 있지만
실행파일은 완전한 단독 실행파일이 된다. 실행 파일에 함수의 코드가 포함되어 있기 때문에, 컴파일이 끝나면 lib 파일이 없어도 프로그램을 실행할 수 있다.
동적링크라이브러리 (Dynamic Link Library - .dll
정적라이브러리처럼 컴파일 시에 함수가 연결되는 방식이 아닌 런타임 시에 함수가 실행파일에 연결된다. 실행파일에는 호출할 함수의 정보만 포함되고, 실제 함수 코드는 복사되지 않으므로 실행파일의 크기가 작아진다. 하지만 실행 파일은 함수에 대한 정보만 가지고 있을 뿐 실제 코드를 가지고 있지 않기 때문에 프로그램 실행 시에는 dll 파일이 항상 존재해야 한다.
- 참고) 라이브러리 사용 이유
라이브러리는 다른 프로그램들과 링크되기 위하여 존재하는, 하나 이상의 서브루틴이나 함수들의 집합파일을 말하는데, 함께 링크될 수 있도록 보통 컴파일된 형태인 목적코드 형태로 존재한다.
라이브러리는 코드 재사용을 위해 조직화된 오래된 기법 중의 하나이며, 많은 프로그램 들에서 사용할 수 있도록 운영체계나 소프트웨어 개발 환경 제공자들에 의해 제공되는 경우가 많다.
참고자료 출처 :
https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=muri1004&logNo=220027346833
Makefile - 조건부, 함수
▣ 조건부 make에서 조건문은 ifeq ~ else ~endif 이다. 예) all : ifeq ($(CC), gcc) @echo "C 컴파일...
blog.naver.com
https://yunslee-42seoul.tistory.com/3
Makefile 정복하기
1. Makefile 구성요소 Target(만들려는 녀석), Dependency(만들기위한 재료), Command(명령어) ,Macro(작성의 편리성) 해당설명은 이 블로그를 참고하자!![1] Makefile에서 반복되는 구조인 Rule block의 구조는..
yunslee-42seoul.tistory.com
'IT > 42Seoul' 카테고리의 다른 글
[GET_NEXT_LINE] 구현 예시 (0) | 2021.06.11 |
---|---|
[GET_NEXT_LINE] 구현 준비 (0) | 2021.06.04 |
malloc/calloc 관련 정리 (0) | 2021.05.30 |
[Libft] C 언어 라이브러리 구현_BONUS_보너스 함수 구현2 (0) | 2021.05.30 |
[Libft] C 언어 라이브러리 구현_BONUS_보너스 함수 구현1 (0) | 2021.05.30 |