이번에는 C++ 코드를 작성하고 make를 통해 g++ 컴파일러로 컴파일해보는 시간을 가져보자.
C++ 같이 텍스트 파일을 실행할 때는 일반적으로 vi 에디터 환경에서 실행해야 한다.
vi 에디터
vi ./[소스코드.cpp]
여기서 입력 모드로 들어가려면 i를 누르면 된다.
그러면 아래에 -- INSERT -- 라고 표시되는데 이는 현재 입력 모드임을 뜻한다.
이 상태에서
:set mouse=a
명령어를 입력하면 마우스로 텍스트 커서를 옮길 수 있다.
참고로 vi 에디터에서 되감기(Ctrl + Z)는 u이다.
이는 입력 모드를 종료한 상태에서 눌러야 한다.
입력 모드를 종료하려면 esc 를 누르면 된다.
:wq 명령어를 입력하면 w(write)-q(quit) 순으로 현재 소스를 저장하고 나오는 것이고
:q! 명령어를 입력하면 저장하지 않고 나오게 된다. (굳이 !를 입력하는 이유는 파일에 변경사항이 있는 상태에서는 :q로 빠져나오려 하면 E37 에러가 발생하고 여기서 명령어에 !를 추가하라고 나온다. 참고로 :! 는 잠깐 파일 바깥으로 나오는 명령어이다.)
이제 코드를 컴파일하고 실행해보자.
컴파일을 해서 목적파일을 만드는 명령어는 다음과 같다.
g++ [소스코드.cpp] -o [생성된 실행파일]
-o 옵션은 output을 의미한다. 그리고 뒤에는 생성될 파일의 이름을 적어주면 된다.
하지만 이 방식은 여러개의 소스코드와 라이브러리를 통합해야 하는 큰 범위의 환경에서는 적합하지 못한 방식이다.
따라서 컴파일 순서와 플래그(-O, -Wall 등)를 미리 지정해주는 Makefile을 만들어서 관리하는 것이 좋다.
Makefile
# Makefile
CXX = g++
CXXFLAGS = -Wall
PROGRAMS = InitSocket
all: $(PROGRAMS)
clean:
rm -f $(PROGRAMS)
CXX 와 CXXFLAGS, PROGRAMS 는 매크로라고 하며 '변수'와 같은 개념이다.
CXX 에는 g++를 할당하고
CXXFLAGS 는 -Wall 플래그를 할당한다. ( -Wall은 컴파일러의 모든 경고를 출력하라는 의미이다.)
PROGRAMS 에는 컴파일할 대상 소스코드를 지정한다. 그리고 이것은 동시에 실행파일의 이름이 된다.
매크로 이름은 임의로 설정할 수 있지만 관례를 따르는 것이 좋다.
Makefile의 Rule은
(Target): (Dependencies)
(Command)
형태를 가진다.
(Target)은 (Dependencies)에 의존한다는 뜻이고
즉 (Dependencies)의 파일들이 변경되면
(Target) 역시 새로 만들어져야 한다는 뜻이 된다.
(Command)는 위에서 설명한 컴파일 명령처럼 명령어가 들어가는 자리인데, 반드시 앞에 Tab 공간을 넣어줘야 한다.
$< 나 $@, $^ 같은 (Target) 또는 (Dependencies)의 파일을 지칭하는 자동 변수들이 있다.
$(OBJS): $(PROGRAMS)
$(CXX) $(CXXFLAGS) -c $<
위의 코드에서 $< 는 (Dependencies)의 첫번째 파일을 가리킨다.
여기서는 하나 밖에 없으므로 PROGRAMS 가 된다. (참고로 PROGRAMS 는 매크로일 뿐이며 내부에 여러 파일이 선언된 경우 그 파일 중 첫번째를 가리키게 된다.)
$@ 는 (Target)을 지칭한다. 여기서는 OBJS 가 된다.
$^ 는 모든 (Dependencies)를 가리킨다. PROGRAMS 매크로에 선언된 파일이 여러 개라면 그 파일 전체를 가리킨다.
all 과 clean 은 딱히 뒤에 명시된 파일들에 의존하는 무언가(?)가 아닌 단지 액션을 의미한다.
이런 (Target)을 Phony Target 이라고 부른다.
Phony Target이 만들어진 배경에는 동일 이름의 파일과의 충돌을 피하고 성능을 개선하는데에 있다.
all 과 clean 은 그 자체로 명령어는 아니지만
커널에서 make all 과 make clean 을 입력하면 all 과 clean 에 등록된 명령어들을 차례로 실행하게 된다.
즉, all 과 clean 은 임의로 정한 make의 액션일 뿐이며 Phony Target으로서 그것이 파일 이름이 아님을 명시한 것이다.
rm -f 의 경우 파일을 지우는 명령어인데 -f (force) 옵션은 파일의 존재 유무과 관계없이 지운다는 것을 의미한다.
그렇다면 Phony Target의 (Dependencies)는 무슨 역할을 하는가?
all 과 같은 Phony Target은 (Dependencies)를 만들기 위한 다른 의존성 파일이나 규칙을 찾게 된다.
그리고 이 과정에서 "추론"을 한다.
make의 추론
make 또는 make all 명령을 입력하면 실행 파일이 생성된다.
그리고 make clean 명령을 입력하면 실행 파일이 제거된다. (rm -f InitSocket 명령 수행)
근데 의문이 든다.
all 액션에는 아무런 명령도 없는데 어떻게 자동으로 컴파일을 하고 실행 파일을 만들어낼까?
이는 make 의 놀라운 추론 능력에 있다.
추론1: 실행 파일 InitSocket 을 통해 InitSocket.cpp 또는 InitSocket.c 파일을 찾는다.
추론2: CXX 와 CXXFLAGS 매크로를 통해 g++ 명령을 -Wall 플래그로 수행할 수 있음을 확인한다.
커널에서 make all 대신 make 를 적을 수 있는 것 역시 make 가 명령을 추론하여 수행하기 때문이다.
실제로
make 만 입력했는데도 g++ -Wall InitSocket.cpp -o InitSocket 으로 컴파일을 하라는 명령으로 전환된다.
'Linux' 카테고리의 다른 글
Linux) chrome 브라우저 실행 안 될 때 (0) | 2024.03.20 |
---|---|
Linux) mnt 디렉토리 (0) | 2024.03.15 |
Linux) g++ 컴파일러 설치 (0) | 2024.03.15 |