본문 바로가기
리눅스/프로그래밍

[리눅스]리눅스 C언어 개발환경 갖추고 샘플 프로그램 실행해보기(+ GCC, Vim, Make)

by hseoy 2020. 4. 28.
반응형

[리눅스  C언어 개발환경 갖추고 샘플 프로그램 실행해보기(+ GCC, Vim, Make)]

이번 글에서는 리눅스 환경에서 C언어를 개발하기 위한 개발환경을 갖추고 간단한 프로그램을 작성하여 실행시켜보려고 한다. 본격적으로 시작하기 앞서 아래와 같은 명령을 터미널에 타이핑하자.

$ cat /etc/issue

그러면 위와 같은 결과를 얻을 것인데 "cat /etc/issue"는 현재 사용중인 리눅스의 버전을 알려준다. 나같은 경우는 현재 Ubuntu 18.04.2 LTS 버전에서 진행하고 있으니 글을 읽을 때 이 점 고려해서 읽어 주기를 바란다.

이제 본격적으로 시작해보자. 이 글에서는 C컴파일러로 gcc, 에디터로는 Vim을 사용한다. 그리고 빌드를 쉽게 하기 위해서 빌드 자동화 유틸리티인 make를 사용한다. 이것들이 무슨 일을 하고 어떻게 사용하는 지에 대해서는 아래에서 자세하게 이야기할 것이고 지금은 일단 "sudo apt install gcc vim make" 명령을 터미널에 타이핑하여 gcc, vim, make를 각각 설치해준다.

$ sudo apt install gcc vim make

이렇게 입력하고 엔터를 입력하게 되면 계속하겠냐는 물음이 나오는 데 y를 입력하면 된다. 만약에 이것을 중간에 입력하는 것이 아니라 명령을 타이핑하는 시점부터 y로 하고 싶다면 위 명령에서 -y라는 옵션을 추가해주면 된다. -y 옵션을 더한 전체 명령은 아래와 같다.

$ sudo apt install gcc vim make -y

명령을 실행하고 나면 C 컴파일러인 gcc와 에디터인 vim, 그리고 빌드를 도와주는 make 유틸리티가 설치되게 된다. 그러면 이제 "Hello World\n"를 10번 출력하는 매우 간단한 프로그램을 에디터인 vim으로 편집을 하고 gcc와 make를 이용해서 빌드한 후 실행해보자.

가장 먼저 할일은 파일을 생성하는 것이다. main함수가 위치할 파일인 main.c 파일을 vim을 통해 생성해보자

$ vim main.c

그러면 아래와 같이 터미널에 새로운 편집창이 나오게 된다. 나같은 경우는 미리 vim에 대한 설정을 작성해놨는데 이것에 대해서는 이 글을 참고했다.

분명히 편집을 하는 프로그램이라 했는데 하얀색의 메모장 같은 화면이 나오는 것이 아니라 터미널의 모습이 바뀐 것이 의아할 수도 있으나 정상이다. 왜냐하면 Vim은 GUI(Graphic User Interface) 기반의 프로그램이 아니라 CUI(Command User Interface) 기반의 프로그램으로써 터미널에서 사용하는 프로그램이기 때문이다. 마우스를 사용해서 제어하기 보다는 명령을 기반으로 제어를 하기 때문에 처음에는 사용하기 어려울 수 있으나 단축키 등에 익숙해지면 마우스를 이용하지 않아도 된다는 점과 Vim에서 제공하는 막대한 기능 덕분에 생산성이 매우 향상되는 효과를 가져올 수 있다고 한다. 

Vim은 명령 기반으로 제어를 하기 때문에 편집을 시작하기 위해서도 어떠한 '명령command'을 입력해 줘야 한다. 'i' 키를 입력해주면 편집모드로 들어가게 되며, i는 insert의 앞 글자를 따온 것이다. i를 입력하게 되면 이제 정상적으로 편집을 할 수 있다. 아래와 같이 코드를 타이핑 해주도록 하자. 

#include <stdio.h>
#include "PrintHelloWorld_10.h"

int main()
{
    PrintHelloWorld_10();
    return 0;
 }

PrintHelloWorld_10()함수는 "Hello World\n"을 10번 출력하는 기능을 수행하는 함수로써 이제부터 작성할 PrintHelloWorld_10.c와 PrintHelloWorld_10.h에 정의되고 선언되어 있다. 즉 위에서 main함수는 "Hello World\n"을 10번 출력하는 PrintHelloWorld_10함수를 호출함으로써 "Hello World\n"을 10번 출력하는 기능을 수행하고 프로그램을 종료시킨다.

위와 같이 편집을 완료하고 나면 파일을 저장하고 원래의 터미널로 돌아가야 한다. 이러한 제어 동작을 수행하는 명령을 입력하기 위해서는 명령모드로 진입을 해야 하는 데 진입하는 방법은 ESC키를 입력하는 것이다. ESC키를 입력하고 콜론(:)을 입력하면 이후 입력하는 명령을 제어하는 명령으로 인식하게 된다. 파일을 저장하는 명령은 'w'이고 Vim을 종료하고 원래의 터미널 환경으로 돌아가는 명령어는 'q'이다. 그리고 이러한 명령어들은 함께 사용할 수 있다. 즉 ESC+:wq와 같이 입력할 수 있고 이는 파일을 저장하고 vim을 종료한다는 것이다. 이 명령어(ESC+:wq)를 입력해서 파일을 저장하고 vim을 종료하자.

그러면 이제 PrintHelloWorld_10.c와 PrintHelloWorld_10.h의 파일을 아래를 참고하여 각각 입력해준다.  위에서 봤듯이 "vim [만들고 싶은 파일명]"의 형태로 파일을 생성하고 "i"키를 입력하여 편집을 진행하면 된다. 그리고 ESC+:wq를 입력하여 파일을 저장하고 vim을 종료한다.

// PrintHelloWorld_10.c
#include <stdio.h>
void PrintHelloWorld_10()
{
    int i = 0;
    for(i = 0; i < 10; i++)
    {
        printf("Hello World\n");
    }
}



// PrintHelloWorld_10.h
#ifndef PRINT_HELLOWORLD_10
#define PRINT_HELLOWORLD_10

void PrintHelloWorld_10();
#endif

잠깐 설명해보자면 PrintHelloWorld_10.c에서는 PrintHelloWorld_10함수를 정의해줬고, PrintHelloWorld_10.h에서는 PrintHelloWorld_10함수의 원형을 선언해줌으로써 다른 파일에서 해당 함수를 가져다 사용할 수 있도록 하였다. 여기서 PrintHelloWorld_10.h에서 #ifndef ~ #endif 부분이 어색할 수 있는 데 이 부분은 헤더파일의 중복 선언을 방지하는 부분이다. #ifndef는 어떠한 매크로가 정의되어 있지 않을때만  #endif까지 실행한다. 위 부분은 PRINT_HELLOWORLD_10 이 define되어 있지 않다면 #endif까지 실행하여 PRINT_HELLOWORLD_10을 정의하고 이후 다시 헤더파일이 include 되었을 때는 PRINT_HELLOWORLD_10이 이미 define되어 있기 때문에 실행되지 않도록 하여 헤더파일의 중복 선언을 방지한다. 

코드를 모두 작성했기 때문에 이제는 빌드를 할 차례이다. 여기서는 먼저 gcc만을 이용해서 빌드를 해보고 이후 make 유틸리티를 활용해서 빌드 작업을 쉽게 하는 방법에 대해서 알아보겠다. 일단 아래와 같은 명령을 위 소스 파일이 위치한 경로에서 실행한다.

$ gcc -c -o main.o main.c
$ gcc -c -o PrintHelloWorld_10.o PrintHelloWorld_10.c
$ gcc -o app main.o PrintHelloWorld_10.o

설명을 해보자면 gcc 컴파일러에서 -c옵션은 링크를 하지 않고 컴파일만 하여 목적파일objectfile을 생성하게 된다. 그리고 -o 옵션은 생성되는 결과 파일의 이름을 지정하는 옵션이다. 즉 1번 라인부터 2번 라인까지는 목적파일을 각각 생성한다. 마지막으로 3번 라인은 목적파일들을 링킹시켜 최종적으로 app이라는 실행파일을 생성하게 된다. 

여기서 주의할 것이 -o 옵션을 사용할 때이다. 아래와 같이 같은 이름인 main.c로 컴파일하게 되면 원래 그 안에 있던 소스 코드가 모두 날라가고 결과가 덮어쓰기 된다. 삽질의 결과물을 날려버리고 싶지 않다면 이후에 설명할 make와 같은 유틸리티를 이용하거나 매우 조심해서 작성해야 한다.

$ gcc -o main.c main.c

그러면 실행파일이 생성되었으니 이것을 실행해보자. "./[실행파일명]"의 형태로 실행시킬 수 있다. 여기서 ./는 실행파일의 경로를 나타내는 것으로 .이 현재 디렉토리를 의미하니 결론적으로 ./[실행파일명]은 실행파일이 현재 디렉토리 안에 있음을 의미한다. 만약 상위 경로에 있다면 ../[실행파일명], 하위 경로에 있다면 [하위 디렉토리명]/[실행파일명]과 같이 사용하여 실행할 수 있다. 실행하고 나면 아래와 같은 결과를 얻게 되었을 것이다.

여기까지 샘플 프로그램을 작성하고 gcc를 통해 빌드한 후 실행하는 것까지 모두 완료되었다. 하지만 여기서 멈춰서는 안된다. 지금은 분할된 파일이 2개뿐이지만 만약에 분할된 파일이 수백 개라면 어떻게 될까? 명령을 수백 번 입력해서 일일히 목적 파일로 만들어 주고 링킹시켜 빌드해야 할 것이다. 그리고 ls 명령을 한번 실행해보자. 그러면 명령의 결과로 생성된 .o의 파일들과 실행파일이 보이게 될 것이다. 분할된 파일이 수백 개라면 이 파일들도 수백 개가 될 것인데 이러한 것들을 일일히 지우려면 매우 힘들것이다. 

이러한 많은 어려움이 Make를 만들어 냈다. Make는 빌드 자동화 툴로써 컴파일 명령들을 스크립트 형태로 만들어 놓아 다시 컴파일 할 때 매우 간단하게 실행할 수 있다. 또한 변경된 파일에 대해서만 다시 컴파일 하기 때문에 불필요한 작업을 최소화 시킨다. 그러면 "vim Makefile"을 통해서 Makefile을 생성하고 아래와 같이 간단하게 Makefile을 작성해보자.

app: main.o PrintHelloWorld_10.o
	gcc -o app main.o PrintHelloWorld_10.o

main.o: PrintHelloWorld_10.h main.c
	gcc -c -o main.o main.c
    
PrintHelloWorld_10.o: PrintHelloWorld_10.h PrintHelloWorld_10.c
	gcc -c -o PrintHelloWorld_10.o PrintHelloWorld_10.c

clean:
	rm *.o app

여기서 중요한 게 스페이스 4번이 아닌 탭을 사용해야 한다는 것이다. "make"를 터미널에 타이핑하여 Makefile을 실행했을 때 만약에 아래와 같은 에러가 발생했다면 그러한 오류로 인해 발생한 것이다.

Makefile:2: *** 분리 기호가 빠졌음.  멈춤.

만약 탭을 사용했음해도 위와 같은 오류가 발생했다면 에디터 설정을 확인해야 한다. 나같은 경우는 아래와 같은 옵션이 .vimrc에 설정되어 있어서 자동으로 탭을 스페이스로 바꿔서 오류가 발생했다.

set expandtab       " tab 대신 띄어쓰기로

그러면 이제 Makefile에 대해서 설명하겠다. 먼저 make 명령을 실행했을 때 Makefile의 첫 번째에 있는 target을 만들려고 Makefile은 한다. 여기서 target은 콜론(:) 왼쪽에 있는 것을 target이라고 한다. 즉 위 파일에서 첫 번째 target은 app이라고 할 수 있다. 그리고 콜론(:) 오른쪽에는 target을 만들기 위해 필요한 다른 파일, target을 의미한다. Makefile은 현재 수행중인 target에서 의존하고 있는 다른 파일이 없다면 해당하는 target으로 이동해서 아래에 있는 명령(gcc로 시작하는)을 실행하여 해당 파일을 생성한다. 그렇게 의존하고 있는 모든 파일을 생성하고 생성되고 나면 비로소 원래 target의 명령을 실행하여 최종적인 결과물을 만들어 내게 된다. 

위에서는 첫 번째 target인 app으로 시작하지만 app이 의존하고 있는 main.o와 PrintHelloWorld_10.o가 없기 때문에 해당 target인 main.o와 PrintHelloWorld_10.o target으로 이동해서 명령을 수행하여 해당 파일을 생성한다. 그리고 다시 app target으로 돌아가 "gcc -o app main.o PrintHelloWorld_10.o"를 실행하여 app 실행파일을 생성하고 종료하게 된다. 

여기서 한 가지 더 짚고 넘어갈 것은 "make [타겟명]"과 같이 사용하여 특정 target을 생성할 수도 있다는 것이다. 그냥 make만 사용하면 첫 번째 target만 생성하려고 하지만 다른 target을 지명하면 해당 target으로 이동시킬 수 있다. 위 파일에서는 clean이라는 target이 눈에 띌 것이다. 이 target은 다른 의존하고 있는 파일이나 target이 없기 때문에 바로 "rm *.o app"만을 실행하게 된다. rm이라는 명령은 파일을 지우는 명령이며 *.o는 모든 .o 확장자 파일(목적 파일)을 선택한다는 것을 의미한다. 즉 "rm *.o app"은 모든 목적 파일과 실행파일을 지운다. 만약 "make clean"과 같이 실행하게 되면 빌드 과정에서 생성된 부수적인 파일들을 한번에 삭제할 수 있다. 물론 "make app"과 같이 사용하여 프로그램을 빌드시킬 수도 있다 :) 사용하기 나름이다. 

gcc와 vim, 그리고 make의 사용, 활용 방법은 무궁무진하다. 하지만 여기서는 흐름과 느낌만 아는 것을 목표로 뒀기에 이쯤에서 그만하려고 한다. 여기까지 따라왔다면 나도 리눅스에서 개발한다고 말해도 좋을 듯 하다 :)

반응형

댓글