107 – GCC 옵션(자주 사용되는)

많은 옵션들 중에서 정리하고 싶은 옵션들을 리스트로 정리해보았다. 이젠 이 옵션들을 하나 하나 살펴보도록 하겠다. 내용은 최대한 간단하게 적고, 예시를 많이 보여주려고 하였기 때문에 은근 스크롤이 긴 편이다.

  • -o

C 코드를 컴파일할 때 생성되는 출력 파일 이름을 지정하는 옵션이다. 사용법은 다음과 같다.

스크린샷_2017-06-19_13-11-15.png

컴파일 할 파일과 옵션 순서를 바꿔도 똑같이 동작한다. 이전에 gcc의 작업 순서를 보여주기 위해서 다시 만들었던 hello world 소스코드이다. 아무 지정도 없었을 때에는 a.out이 나왔지만, -o 옵션을 통해 file이라는 파일이 만들어졌다. 이 파일을 실행하면 똑같이 hello world를 출력한다.

-o 옵션을 생략하고 컴파일 하면 실행 파일 이름이 자동으로 a.out이 되는 것 외에도 주의해야 할 것이 있다. 두 가지 다른 소스를 차례로 컴파일할 때, 먼저 생성된 a.out을 덮어쓸 수 있다. 근데 이 작업이 경고 없이 그냥 막 덮어쓰게 된다. 앞에 예제들은 그런 거 신경 안쓰고 했었지만, 나중에 프로그램을 만들고 할 때에는 중요한 요소가 된다.

  • -E

앞에서 C 소스 파일을 컴파일하는 과정에 네 단계가 있고, 각 단계를 수동으로 조정할 수 있다고 했다. -E 옵션은 컴파일의 첫 단계인 전처리까지만 실행한 결과를 화면에 출력한다. 실제로 실행을 해본다. 아래처럼 뭔가 막 실행된 것을 볼 수 있다. 이게 전처리된 결과를 보는 것이다.

-E 옵션만 주면 전처리된 결과가 화면에만 출력되고 파일로는 저장되지 않는다.

그러므로 파일로 저장하려면 -o 옵션을 함께 주어야 한다. 그러면 전처리 결과를 디스크에 저장할 수 있다. 아래 예시가 실제로 실행한 결과다. 화면에는 보여주지 않고 그대로 파일로 저장했으며, 저장된 파일을 vi로 열어보니  이전에 출력된 것이 그대로 출력된다.

  • -c

전처리, 컴파일, 어셈블까지 실행하여 오브젝트 파일(.o)을 생성한다. 이 오브젝트 파일은 나중에 실행 가능한 링킹 작업만 끝나면 되기 때문에 이 오브젝트만 가지고 gcc로 돌려도 실행 가능한 파일이 만들어진다. 우선 -c 옵션을 이용해서 오브젝트 파일을 만드는 것을 보도록 하자.

file.o 파일이 만들어진 것을 확인할 수 있다. 이제 이 오브젝트로 실행 파일이 만들어져서 무사히 hello world가 찍히는지 확인해보도록 하자.

a.out 파일을 지우고 그대로 file.o 파일을 가지고 컴파일을 완료한 후, 실행해보니 그대로 실행이 된다.

근데 이 옵션이 왜 필요한지 궁금한 사람들이 있을 것이다. 프로그램이 좀 커지고 프로젝트가 대형화 되어 수백, 수천 단위의 소스 파일을 컴파일 하는 경우에는 -c 옵션을 통해 하나의 프로그램을 여러 파일로 분리해 작성한 다음, 함께 컴파일하는 “분리 컴파일”을 할 때에 많이 이용된다. 즉, 왠만큼 규모가 커지게 되면 다 이용한다. 이걸 위해 간단한 예제를 하나 더 만들어보았다.

01 폴더 안에 main.c와 hi.c 파일을 작성하였다.

이 두 파일을 컴파일 하는 것은, 앞의 예제에서 여러 파일을 컴파일하는 예제를 다룬 적이 있다. 이 둘을 함께 컴파일하는 것을 분리 컴파일이라 한다. 아래와 같이 하면 정상적으로 실행되는 것을 확인할 수 있다.

그러나, 이 작업을 하나의 파일마다 각각 하게되면 오류가 발생한다. 당연히 서로가 참조해야 할 정의를 찾을 수 없어서 발생하는 것이다. hi랑 main 함수를 몾찾는 거다.

하지만, 오브젝트까지만 만들어진 파일에 대해서는 나중에 링크를 할 수 있다. 링크가 진행되는 구조와 실행 결과는 아래와 같다.

이 나중에 링크가 될 수 있는 기능 덕분에 여러 사람들이 자신이 개발한 작업 소스에 대해서 해당 소스만 수정하면 되는 그런 상황이 된다. 예를 들어, hi 함수에 기능을 수정하고 싶으면 hi만 수정해서 다시 오브젝트를 만들면, 나중에 수정된 오브젝트만 가지고 한번에 진행하면 된다. 그러면 main.c를 다시 컴파일 할 필요가 없어지므로 컴파일 시간도 줄어들게 된다.

이 옵션은 make와 함께 쓰면 상당히 강력한 기능이 된다. make에 대한 설명을 할 때, 한번 더 다뤄보기로 한다.

-I

C 소스는 표준 디렉토리에 있는 헤더 파일을 이용하여 개발을 할 수도 있지만, 표준 디렉토리가 아닌 위치에 있는 레더 파일을 가져와서도 개발을 할 수 있다. 그 때 그 디렉토리의 위치를 지정해주는 옵션이다. 이 옵션 또한 예제를 보고 하면 금방 진행할 수 있다.

02라는 폴더에 예제를 만들어보았다. 그리고 예제의 구조 및 사용되는 소스코드를 먼저 작성해보고 시작한다.

myheader.h 파일에는 나이가 적혀있고, mydir 폴더 안에 들어있다.

이 상태에서 age.c 파일을 컴파일하면 myheader.h 파일이 없다는 오류가 나타난다.

표준 디렉토리에 없기 때문에 오류가 발생한 것이다. 이제는 옵션을 주고 실행을 해보겠다.

옵션에 해당되는 디렉토리의 위치를 지정해줬더니 바로 컴파일 오류가 나지 않는 것을 볼 수 있다.

이처럼 기본적으로 이용되는 옵션들에 대해서 예시와 함께 살펴보았다. 이 옵션들은 뒤에 더 많은 리눅스 개발 환경을 다루다보면 하나 둘은 반드시 보게 되는 기능들이기 때문에 이런 기능이 있다는 것은 꼭 알아야 하는 것들만 정리해보았다. 이제 다음에는 라이브러리 지정 옵션에 대해서 살펴보겠다.

106 – GCC 옵션 (요약)

gcc에 대해서는 솔직히 다 다루는 거 자체는 무리다. 진짜다. 그래서 정말 자주 사용하는 옵션, 라이브러리 지정 옵션, 디버깅 옵션, 최적화 옵션 등에 대해서만 다루더라도 엄청나게 많아지는데, 그 중에서도 좀 갈려서 다뤄야 할 거 같아서 내용을 쪼개고 옵션들을 하나하나 천천히 살펴보았다.

그래서 몇 단위로 하려는 옵션들을 아래와 같이 쭉 한번 요약해보았다. 옵션에 대한 간단한 설명이나 특수한 설명들은 다음 글에서 조금씩 다뤄보기로 하겠다. 당장 이것만 봐선 일단 이런 옵션들이 많이 쓰인다고만 알아두면 된다.

  • 옵션 | 의미
  • -E | 전처리를 실행하고 컴파일을 중단한다.
  • -c | 소스 파일을 컴파일만 하고 링크를 수행하지 않는다. 오브젝트 파일이 생성된다.
  • -o | 바이너리 형식의 출력 파일 이름을 지정한다. 지정하지 않으면 기본 출력인 a.out이 적용되어 결과물이 만들어진다.
  • -I | 헤더 파일을 검색하는 디렉토리 목록을 추가한다.
  • -L | 라이브러리 파일을 검색하는 디렉토리 목록을 추가한다.
  • -l | 라이브러리 파일을 컴파일 시 링크한다.
  • -g | 바이너리 파일에 표준 디버깅 정보를 포함시킨다.
  • -ggdb | 바이너리 파일에 GNU 디버거인 gdb만이 이해할 수 있는 디버깅 정보를 포함시킨다.
  • -O | 컴파일 코드를 최적화시킨다.
  • -ON | 최적화 N단계를 지정한다.
  • -DFOO=RAR | 명령라인에서 RAR의 값을 가지는 FOO라는 선행 처리기 매크로를 정의한다.
  • -static | 정적 라이브러리에 링크한다.
  • -ansi | 표준과 충돌하는 GNU 확장안을 취소하며, ANSI/ISO C 표준을 지원한다. 이 옵션은 ANSI 호환 코드를 보장하지 않는다.
  • -traditional | 과거 스타일의 함수 정의 형식과 같이 전통적인 K&R C 언어 형식을 지원한다.
  • -MM | make 호환 의존성 목록을 출력한다.
  • -V | 컴파일의 각 단계에서 사용되는 명령을 보여준다.

105 – GCC 실행하기

gcc의 실행 원리를 이해하기 위해 우리는 아주 간단한 예제를 다시 불러오기로 하였다. 바로 “Hello World”를 출력하는 예제이다. file.c 파일을 만들고 hello world를 출력하는 프로그램을 다시 짜보겠다.

이 파일을 저장한 다음에 gcc를 이용하여 파일을 컴파일하여 보겠다.

[gcc “소스파일이름”] 과 같은 구조를 이용하여 gcc를 이용하여 컴파일을 진행하였고, 진행 후 프롬프트($)가 그대로 나오면 컴파일이 성공한 것이다. 그럼 이제 컴파일 후에 어떤 파일이 생성되었는지를 확인하자.

a.out이라는 파일이 추가로 생성된 것을 볼 수 있는데, a.out이 바로 실행 가능한 링킹까지 전부 완료된 파일이다. 컴파일 도중에 만들어지는 파일은 디스크에 저장되지 않는 파일도 있고, 않을 수도 있다고도 이전 글에서 작성하였는데, 아무 옵션이 없어서 그냥 바로 실행 파일까지 만든 것이다.

이 파일을 실행하기 위해 a.out을 입력하여 보자.

우리가 전에 실행하던 방식(./a.out)과는 다르게 실행하였는데, 적고 싶은 내용이 있어서 좀 정리했다.

위와 같이 명령을 찾을 수 없다는 오류가 발생하는 것을 볼 수 있다. 이는 a.out이 저장된 디렉토리를 path로 설정하지 않았기 때문이다. 터미널에서 실행되는 명령어들은 해당 명령어로 된 실행 파일을 실행하는 것인데, 그 실행 파일들이 path라는 터미널의 명령어 실행 쉘의 path변수에 지정되어 있다. 그렇기 때문에 우리가 직접 만든 a.out은 터미널 명령 쉘에 등록되지 않은 명령어로 인식되었기 때문에 명령을 실행할 수 없습니다라는 오류를 나타내는 것이다.

그럼 우리가 전에 쓰던 “./a.out”이라는 것은 어떤 것인가? 바로 현재 디렉토리 위치 정보인 “./”명령어와 같이 실행된 것이다. 그래서 현재 디렉토리임을 확인하는 명령과 함께 현재 디렉토리에 있는 a.out을 실행한 것이다.

지금까지 우리가 C언어를 배우면서 예제 코드를 컴파일하기 위한 gcc의 실행 법과 실행을 위해 항상 적어왔던 ./a.out이 어떤 것인지를 알 수 있었을 것이다.

104 – GCC의 파일 확장자에 따른 처리법

gcc는 파일 확장자에 따라 처리 방법을 달리 한다. 소스코드 파일을 식별하기도 하고, 단계별로 처리된 파일임을 식별할 때 이용하기도 한다. 그래서 이전글에서 각각의 과정에 따라 파일명을 적은 것도 각 과정에 따라 확장자가 다르게 나오는 것과 그에 따라 과정이 나뉘는 걸 보여주기 위해 설명하였다. C 프로그래밍을 위해서 알아둬야 할 것들만 간단하게 정리를 해보겠다.

  • 확장자 | 종류 | 처리 방법
  • .c | C 소스 파일 | gcc로 전처리, 컴파일, 어셈블, 링크
  • .C .CC | C++ 소스 파일 | g++로 전처리, 컴파일, 어셈블, 링크
  • .i | 전처리된 C 파일 | gcc로 컴파일, 어셈블, 링크
  • .ii | 전처리된 C++ 파일 | g++로 컴파일, 어셈블, 링크
  • .s | 어셈블리어로 된 파일 | 어셈블, 링크
  • .S | 어벨블리어로 된 파일 | 전처리, 어셈블, 링크
  • .o | 오브젝트 파일 | 링크
  • .a .so | 컴파일된 라이브러리 파일 | 링크

103 – GCC의 동작 과정

gcc는 C, C++, objective-c, fortran 등 여러 언어를 컴파일 할 수 있다. 1999년부터 gcc는 자유 소프트웨어 재단에서 지원하는 컴파일러의 집합을 의미하기 때문이다. (그 전까지는 GNU C Compiler라고 지정하였었다.) 그 와중에도 우리는 C언어를 컴파일하기 위해 이용한다. 그래서 이 글도 C언어를 기준으로 설명하겠다.

gcc를 컴파일러라고 일반적으로 이야기한다. 그런데 정확히 말하면 gcc는 소스 파일을 이용해 실행 파일을 만들 때까지 필요한 다음과 같은 프로그램을 차례로 실행하는 일종의 툴이다.

cpp | 전처리기
cc1 | 컴파일러
as | 어셈블러
ld |링커

그리고 GCC는 이러한 프로그램을 다음과 같은 순서로 동작시킨다.

gcc는 소스 파일을 cpp로 전처리하고, cc1으로 컴파일하고, as로 어셈블링, ld로 링크해서 실행 파일인 a.out을 만들어낸다. 이 각 단계가 어떤 식인지를 file.c라는 소스코드를 처리한다고 가정하고 진행해 보겠다.

  • 전처리 단계

우선 소스파일에 gcc를 동작 시키면 가장 먼저 전처리기인 cpp가 동작한다.

cpp는 소스 파일의 #include와 #define과 같은 전처리기 부분을 처리한다. 즉, 필요한 헤더 파일을 삽입하고 실행 문장의 매크로를 상수로 반환하여둔다. 소스 파일이 전처리기를 거치면 file.i라는 이름의 파일이 생성되지만 디스크에는 저장되지 않는다.

  • 컴파일 단계

컴파일러가 전처리된 파일로부터 어셈블리어로 된 파일을 생성한다.

생성된 파일은 file.s이다. 이 파일도 보통은 디스크에 저장되진 않는다.

  • 어셈블 단계

어셈블리어로 된 파일을 기계가 직접 이해할 수 있는 오브젝트 파일로 변환한다.

오브젝트 파일(file.o)은 디스크에 생기기도 한다.

  • 링크 단계

오브젝트 파일은 기본적으로 라이브러리 함수(printf, scanf 등)에 해당하는 코드가 없다. 그래서 실행을 할수 없다. 또한 여러 파일로 이루어진 프로그램의 경우에도 파일 간의 연결 과정이 이루어져 있지 않기 때문에 실행을 할 수 없다. 이러한 라이브러리 함수나 다른 오브젝트 파일들을 연결해주는 작업이 링크 단계이다.

링크 단계가 진행되어야 실행 가능한 파일이 생성된다.

컴파일을 하는 각각의 단계에 대해서 간단한 설명을 진행하였는데, 각 단계별 옵션을 사용해서 얼마나 진행되었는지 그 상화을 볼 수 있게 수동으로도 제어가 가능하다. 이를 위해서는 gcc의 옵션들을 알아야 하는데, 나중 글에서 다루기로 하겠다.

102 – GCC로 컴파일하기

우리는 리눅스에서 실습을 할 때, C언어를 컴파일 할 때 쓰는 도구로 gcc를 이용하였다. 그러나, 이 gcc에 대해서 글을 자세히 적은 적은 거의 없다. 아마 대부분의 내 글을 본 사람들은 내가 따라할 수 있도록 만든 예제만을 보고 그대로 따라했을 것이다.

프로그램이 점점 더 커지고 더더욱 복잡한 개발 환경들이 들어갈 때, 우리는 이 컴파일러에 대해서 이해를 하고 있어야 한다. 컴퓨터는 사용자가 C언어와 같이 고급 언어로 작성한 많은 소스 프로그램을 이해하지 못한다. 이를 컴퓨터가 이해할 수 있는 기계어 형태로 변환해 주는 작업을 하는 툴이 gcc이기 때문이다. 우리가 영어를 쓰는 사람들과 대화하기 위해 영어를 공부하는 데, 어느 정도의 문법적인 요소를 한 번 정도는 상세하게 살펴보고 가는 것과 같은 이치이다.

리눅스에서 C언어로 개발할 때에는 주로 gcc를 이용한다. c언어를 개발할 때 쓰는 컴파일러는 여럿 존재하지만 gcc가 제일 압도적으로 많이 이용된다. 그러므로 일단 gcc가 어떤 것인지, 컴파일러가 어떤 것인지, 어떠한 실행과 옵션들이 있는지를 알아보도록 하겠다.

101 – 기타 시간 관련 함수

시간과 관련된 기타 함수에서 많이 쓰는 게 difftime과 clock이 있다. difftime  함수는 두 개의 time_t형 함수로부터 시간 차를 구하는 함수고, clock함수는 프로그램 실행 후 현재깨지 경과한 시간을 반환한다.

  • 함수 이름 | 기능
  • difftime | 초 단위의 시간차를 구한다.
  • clock | 프로그램 실행 후 현재까지의 시간을 구한다.

이 함수들을 직접 확인하려면 시간차가 있는 프로그램을 만들어야 하는데, 예시처럼 만들어 보았다. sleep이라는 함수를 사용하였는데, 이 함수는 일정 시간동안 프로그램이 정지를 하는 함수이다.

이렇게 스크린샷으로만 봐선 잘 모를 거 같아서 실행 화면을 녹화해서 유튜브에 올렸다. sleep으로 지정한 딱 5초 있다가 프로그램이 다시 실행되어서 시간차 5를 반환했다.

 

100 – 형식 변환 함수

시간 정보를 여러 형식으로 변환할 수 있는데, localtime과 gmtime은 time_t 형 시간 정보를 struct tm형으로 변환한다. 이 형을 한번 살펴보자.

struct tm {
int tm_sec;           //초 (0~59)
int tm_min;         //분 (0~59)
int tm_hour;       //시간 (0~23)
int tm_mday;      //일 (1~31)
int tm_mon;        //월 (0~11)
int tm_year;        //연도(1990~)
int tm_wday;      //요일 (0~6)
int tm_yday;       //1월 1일 이후의 날짜
int tm_isdst;       //썸머타임(양수일 때 유효, 0일때 무효, 음수일 때 사용 불가)
}

  • 함수 이름 | 기능
  • localtime | time_t형 시간 정보를 struct tm형 지역 시간으로 변환한다.
  • gmtime  | time_t형  시간 정보를 struct tm형 세계 표준 시간으로 변환한다.

기능에 보면 시간 표기의 차이가 있다는 것을 볼 수 있다. 리눅스 설치 시에 지정한 로컬 타임존에 따라서 지역 시간으로 변환하느냐 아니면 글로벌 시간으로 변환하느냐의 차이다. 사용법은 아래의 예시를 확인하자.

이와 반대로 struct tm 형 시간을 time_t형으로 변환하는 함수 또한 존재한다.

  • 함수 이름 | 기능
  • mktime | struct tm형 시간 정보를 time_t형으로 변환한다.

또한 struct tm형의 시간 정보를 사용자가 알아보기 쉽게 문자열로 변환해주는 함수 또한 존재한다. 기본적으로 제공되는 서식 형태로도 만들 수 있지만, 개발자가 직접 서식을 지정할 수 잇는 함수 또한 존재한다.

  • 함수 이름 | 기능
  • asctime | struct tm 형 시간 정보를 문자열로 변환한다.
  • strftime | struct tm형 시간 정보를 서식을 갖춘 시간 정보로 변환한다.

변환에 필요한 변환 형식은 변환 문자열을 확인하여 직접 작성하면 된다. 두 번째에 변환 포맷을 지정할 수 있고, 변환 문자열은 다음과 같다.

  • 변환 문자열 |  의미
  • %a | 요일 이름의 약자
  • %A | 요일 이름
  • %b | 월 이름의 약자
  • %B | 월 이름
  • %c | 지역 날짜와 시간
  • %d | 날짜
  • %H | 시간 (24시간)
  • %I | 시간 (12시간)
  • %j | 1월 1일 이후의 날짜
  • %m | 월
  • %M | 분
  • %p  | AM, PM
  • %S | 초
  • %w | 요일
  • %x | 지역 날짜
  • %X | 지역 시간
  • %y | 연도
  • %Y | 연도(4자리수)
  • %% | 퍼센트 기호

어기까지의 내용만 가지고 예제를 확인해보자. 함수 여럿 좀 써보겠다고 해서 자잘하게 해봤다.

99 – 시간 표시 함수

시스템의 시간을 얻는 함수인 time을 사용하면 “1970년 1월 1일 00:00:00 UTC” 이후의 시간을 초 단위로 반환하고 이 때 t가 시간 정보를 가리킨다. 이 시작시간이 되게 중요하다. 흔히들 POSIX Time 또는 Unix Time이라고 한다.

  • 함수 이름 | 기능
  • time | 초 단위의 현재 시간 정보를 얻는다.
  • ctime | time_t형 시간 정보를 문자열로 반환한다.

반환되거나 time이 가리키는 시간 정보는 별도로 정의되어 있다.

typedef long time_t;

예제를 보면 사용법을 금방 알 수 있다.

실행 결과에서 볼 수 있듯이 long형 정수로 표현된다. 정의된 대로 받아온 것이긴 한데,  이걸 일일이 따져서 계산하기 전에는 어떤 시간인지를 알 수 없다. 그래서 이걸 사용자가 알아볼 수 있게 변환하는 함수가 ctime  함수이다. ctime 예제도 같이 살펴보자.

98 – 날짜와 시간 함수

리눅스 시스템 앞에 앉아 있다면 시계가 없어도 date 명령어를 이용하여 현재의 시간을 정확히 알 수 있다. 라이브러리에서는 이렇게 날짜와 시간 관련 함수를 제공하고 있으므로 이를 이용하면 date 명령어와 같이 시스템의 현재 시간을 알아내는 프로그램을 간단하게 구현할 수 있다.

또한 이러한 날짜와 시간 함수를 이용하면 현재 시간을 알 수 있을 뿐만 아니라 사용자들이 보기 좋게 변환하거나 시간차를 구할 수도 있다.

날짜와 시간은 프로그램에서 여러모로 관리되는 기능이기 때문에 날짜와 시간 함수가 사용되는 경우는 많이 존재한다.