Library4developers

Ubuntu Korea Community Wiki
imported>Drake님의 2014년 6월 15일 (일) 13:42 판 (→‎getenv, putenv)
둘러보기로 이동 검색으로 이동

라이브러리란?

  • 도서관이라는 뜻처럼, 헤더 파일과 실행 루틴이 모여 있는 것으로 프로그래밍 언어에서 입출력과 문자열 관리와 같은 일상적인 작업을 추가할 때 사용한다.

동적 라이브러리와 정적 라이브러리의 특징

동적 라이브러리

  • 일반적으로 .so 로 끝나는 이름을 하고 있다.
  • 윈도우의 DLL 파일이라고 생각하면 된다.
  • 실행할 때 시스템에 설치되어 있는 라이브러리를 가져다 사용하므로, 정적 라이브러리에 비해 상대적으로 느리다.
  • 공유 라이브러리를 사용하면 실행파일에 라이브러리를 포함할 필요가 없으므로 스토리지가 절약된다.
  • 컴파일러의 기본값이다.
  • 사용법
gcc --shared -o parse parse.c -lxml2

정적 라이브러리

  • 일반적으로 .a 로 끝나는 이름을 하고 있다.
  • 윈도우의 lib 파일이라고 생각하면 된다.
  • 컴파일할 때 함수를 실행파일에 포함한다.
  • 실행할 때 모든 함수가 실행파일 안에 있으므로, 동적 라이브러리에 비해 상대적으로 빠르다.
  • 정적 라이브러리를 사용하면 의존성 등에서 조금 더 자유로울 수 있다.
  • 사용법
gcc --static -o parse parse.c -lxml2

기본 정적 라이브러리

  • 두개의 함수가 포함되어 있는 작은 라이브러리를 만들어보자. 예제에서는 이들 중 하나를 사용할 것이다. 이 함수들은 fred와 bill이며, 간단한 인사말을 출력한다. 우리는 각자 따로 소스파일을 작성할 것이다.

<source lang="c" title="fred.c"> /* fred.c */

  1. include <stdio.h>

void fred(int arg) {

 printf("fred: you passed %d\n", arg);

} </source> <source lang="c" title="bill.c"> /* bill.c */

  1. include <stdio.h>

void bill(char *arg) {

 printf("bill: you passed %s\n", arg);

} </source>

  • 이러한 함수들을 각자 컴파일한 후 오프젝트파일을 만듦으로서 라이브러리파일에 포함시킬 수 있다. 우리는 -c 옵션을 사용함으로서 gcc가 실행가능한 완벽한 프로그램을 만들지 못하도록 해야 한다(.o 파일을 뽑아야 한다). 만일 이 소스를 -c옵션을 사용하지 않고 컴파일하면 main이 정의되지 않았다고 지랄실패할 것이다.

<source lang="bash"> $ cc -c bill.c fred.c $ ls *.o bill.o fred.o </source>

  • bill 함수를 호출하는 프로그램을 작성해보자. 먼저, 라이브러리에 필요한 헤더파일을 만들자. 이 헤더파일은 라이브러리 내부에서 함수들을 선언하는 것이며, 본 라이브러리를 사용하는 프로그램은 모두 이 헤더파일을 포함해야 한다.

<source lang="c" title="lib.h"> /* 이건 lib.h다. fred와 bill을 사용할 어플들은 모두 이걸 include하도록 */ void bill(char *); void fred(int); </source>

  • 본 라이브러리를 사용하는 프로그램(program.c)을 작성하는 일은 존나 쉽다. 필요한 라이브러리와 헤더파일을 포함하고, 라이브러리에 들어있는 함수만 호출하면 된다.

<source lang="c" title="program.c"> /* program.c */

  1. include "lib.h"

int main() {

 bill("헬로 월드");
 return 0;

} </source>

  • 이제 프로그램을 컴파일하고 테스트해보자. gcc한테 소스코드를 모두 오브젝트 파일로 컴파일하고 실행파일로 링크하라고 하자.

<source lang="bash"> $ cc -c program.c $ cc -o program program.o bill.o $ program bill : you passed 헬로 월드 $ </source>

  • 이제 라이브러리를 만들고 사용해보자. ar프로그램을 사용하여 archive을 만들고 여기에 오프젝트 파일을 추가한다. 이 프로그램이 ar이라 불리는 까닭은 archive를 만들거나 각각의 파일을 모아서 하나의 커다란 파일에다 모아주기 때문이다.아카이빙이라고 하면 있어보이긴 하는데 못알아듣잖아 ar은 텍스트 파일들의 archive를 만들때에도 사용할 수 있기 때문에 주목해야 한다.리눅스는 뭐하나 알면 두고두고 우려먹을 수 있으니까 좋잖아

<source lang="bash"> $ ar crv lib.a bill.o fred.o c - bill.o c - fred.o $ </source>

  • 이제 라이브러리를 만들었고, 두개의 오브젝트파일을 추가했다. 버클리 유닉스에서 파생된 몇몇 시스템에서는 라이브러리를 성공적으로 사용하기 위해 라이브러리 목록이 필요하다. 이 일은 ranlib라는 명령으로 처리한다. 하지만 리눅스와 같은 시스템에서 GNU 개발툴(build-essential)을 사용하면 이 과정은 필요없다.BSD에선 필요하다. 짜증

<source lang="bash"> $ ranlib lib.a </source>

  • 이제 라이브러리를 사용할 준비는 끝났다. 보통 프로그램을 만들 때 이러한 라이브러리를 추가할 수 있다.

<source lang="bash"> $ cc -o program program.o lib.a $ ./program bill : you passed 헬로 월드 $ </source>

  • 오브젝트 파일이나, 라이브러리 또는 실행 가능한 프로그램에 어떤 함수가 포함돼있는지 알아보기 위해 nm이라는 명령어를 사용할 수 있다. program과 lib.a를 살펴보면, 라이브러리는 fred와 bill을 포함하고 있지만 program은 단지 bill만 포함하고 있다는걸 알 수 있다. 프로그램이 만들어질 때, 실제로 필요한 함수들만 라이브러리에 포함된다. 라이브러리의 모든 함수들이 선언된 헤더 파일을 포함한다고 해서 모든 라이브러리 함수들이 포함되는건 아니다.
  • 만약에 여러분이 MSDOS나 윈도우 소프트웨어 개발에 대해 좀 알고 있다면 몇가지 유사한 점을 살펴볼 수 있다.
Item Unix DOS / Windows
오브젝트 모듈 lib.a lib.lib
실행파일 program program.exe
  • 정적라이브러리의 단점은 동시에 여러 프로그램을 띄웠을 경우 이들이 모두 같은 라이브러리의 함수들을 사용하니까 메모리에 같은 함수들을 여러번 복사하고, 프로그램 파일에도 같은 라이브러리를 여러번 복사한다는거다. 이는 메모리 용량과 디스크 용량을 많이 소모한다. 많은 유닉스 시스템에서는 이러한 불편함을 극복할 수 있게 공유라이브러리를 제공한다.
  • 공유라이브러리랑 정적라이브러리는 같은데 저장돼있는데 확장자가 다르다. 전형적인 리눅스 시스템에서 표준 C 라이브러리의 공유 라이브러리 버전은 /lib/아키텍쳐/libc.so.N이다. 32비트의 경우 그냥 /lib/i386-linux-gnu/libc.so.6일 것이고, 64비트라면 /lib/amd64-linux-gnu/libc.so.6일 것이다. 여기서 N은 대부분 메이저 버전 숫자를 나타내며 현재는 6이다.
  • 프로그램이 공유라이브러리를 사용할 때, /usr/lib/아키텍쳐/libc.so라는 라이브러리에 링크된다. 이 라이브러리는 그 자체로 함수코드를 포함하고 있지 않은 특별한 라이브러리다. 하지만 런타임에 포함할 공유라이브러리를 참조한다.
  • 프로그램이 실행을 위해 메모리에 적재될 때 함수에 대한 참조 부분이 분석되어 공유라이브러리의 적재가 가능하게 되며, 필요한 경우에 공유라이브러리는 메모리상에 적재된다.
  • 이경우 시스템은 많은 응용프로그램들이 하나의 라이브러리를 공유할 수 있게 해준다. 또, 프로그램마다 같은 라이브러리를 실행파일에 포함할 이유가 없으니 하드도 절약된다.
  • 공유라이브러리는 이것에 의존하는 프로그램과 상관없이 지혼자 업그레이드할 수 있는 부가적인 이점도 있다.이점은 무슨 씨벌 맨날 꼬이는데 사람들은 일반적으로 마이너버전 번호 수정은 라이브러리를 사용하는 프로그램한테 별 영향이 안 가도록 노력한다.이 구역에 미친놈 하나 나오면 끝장이야
  • 리눅스 시스템에서 공유 라이브러리의 적재를 관리하며, 클라이언트 프로그램의 함수 참조를 분석하는 일은 ld.so 또는 ld-linux.so가 맡고 있다. 공유 라이브러리 검색 디렉토리는 /etc/ld.so.conf에 설정된다. /etc/ld.so.conf파일에 변경이 있었다면 ldconfig를 수행해야 한다. 하나의 예로, X11 공유 라이브러리 검색 디렉토리가 추가됐을 때를 들 수 있겠다.x가 존나 빡치게 만들긴 하지 ㅇㅅㅇ
  • ldd를 사용하면 해당 프로그램에서 어떠한 공유 라이브러리를 요구하는지 알아볼 수 있다.

<source lang="bash"> $ ldd program

 linux-gate.so.1 => (0xb7785000)
 libc.so.6 => /lib/i386-linux/gnu/i686/cmov/libc.so.6 (0xb760e000)
 /lib/ld-linux.so.2 (0xb7786000)

</source>

  • 위의 경우 표준 C라이브러리(libc)가 공유되고 있음을 알 수 있다. 다른 유닉스 시스템에서도 공유라이브러리 접근 방법은 비슷비슷하다. 좀더 자세한 사항은 아는사람 있으면 추가바람
  • 공유라이브러리는 Windows에서 사용되는 동적 링크 라이브러리와 많은 부분이 비슷하다. .so라이브러리는 .dll과 대응되며, .a라이브러리는 .lib와 비슷하다고 보면 된다.

라이브러리의 설치

  • sudo apt-get install로 설치가 가능하다.

ex) <syntaxhighlight lang="bash"> sudo apt-get install libxml2-dev </syntaxhighlight>

  • 설치된 라이브러리는 일반적으로 /usr/include에 헤더파일이 들어가며, /usr/lib에 .so파일과 .a파일이 들어간다.

라이브러리 사용법

getopt

  • 일단 받은 인자를 출력하는 코드부터 보자.

<source lang="c"> /* args.c */

  1. include <stdio.h>

int main(int argc, char *argv[]) {

 int arg;
 for(arg = 0; arg < argc; arg++) {
   if(argv[arg][0] == '-')
     printf("Option: %s\n", argv[arg] + 1);
   else
     printf("argument %d: %s\n", arg, argv[arg]);
 }
 return 0;

} </source>

  • 프로그램을 실행시키면 프로그램의 인자와 옵션을 출력한다.
$ ./args -i -lr 'hi there' -f fred.c
argument 0: args
option: i
option: lr
argument 3: hi there
option: f
argument 5: fred.c
  • ... 이걸 라이브러리 안 쓰고 파싱하려면 머리에 쥐난다.
  • 그래서 getopt가 있는거다.

<source lang="c">

  1. include <unistd.h>

int getopt(int argc, char *const argv[], const char *optstring); extern char *optarg; extern int optind, opterr, optopt;

  • getopt는 main에서 전달된 argc, argv 인자를 가지고 옵션인지 아닌지 판단해주는 라이브러리다.

<source lang="c"> getopt(argc, argv, "if:lr"); </source>

  • getopt의 동작 순서는 다음과 같다.
  1. 옵션이 값을 취한다면, 해당 값을 전역변수 optarg에 넣는다.
  2. 더이상 처리할 옵션이 없다면 getopt에 -1을 반환.
  3. 인식할 수 없는 옵션이 있으면 getopt는 ?를 반환. 해당 옵션은 전역변수 optopt에 저장.
  4. 옵션이 값을 취하고 해당 값이 없으면 getopt는 :를 반환
  • 전역변수 optind는 처리할 다음 인자의 인덱스로 설정한다. 그래서 남은 인자가 argv 몇번째인지 알 수 있다.
  • POSIX는 opterr 변수가 0이 아닐 경우 getopt가 stderr로 에러메세지를 출력해야 한다고 권고하고 있다.
  • 아래는 getopt를 사용하는 예제다.

<source lang="c">

  1. include <stdio.h>
  2. include <unistd.h>

int main(int argc, char* argv[]) {

 int opt;
 while((opt = getopt(argc, argv, "if:lr")) != -1) {
   switch(opt) {
   case 'i':
   case 'l':
   case 'r':
     printf("옵션: %c\n", opt);
     break;
   case 'f':
     printf("파일: %s\n", optarg);
     break;
   case ':':
     printf("옵션에 값을 넣어주세요\n");
     break;
   case '?':
     printf("모르는 옵션: %c\n", optopt);
     break;
   }
 }
 for(; optind < argc; optind++)
   printf("argument: %s\n", argv[optind]));
 return 0;

} </source>

  • 실행해보면 다음과 같다.
$ argopt -i -lr 'hi there' -f fred.c -q
옵션: i
옵션: l
옵션: r
파일: fred.c
argopt: illegal option-q
모르는 옵션: q
argument: hi there
  • 반복적으로 getopt를 호출하여 출력하는게 일반적이다.

getopt_long

  • 추후 추가

getenv, putenv

<source lang="c">

  1. include <stdlib.h>

char *getenv(const char *name); int putenv(const char *string); extern char **environ; </source>

  • 환경변수는 name=value 형식으로 구성되어 있다.
  • getenv 함수는 주어진 name으로 사용된 환경변수를 찾아서 있으면 value값을 반환. 없으면 null 반환.
  • putenv 함수는 "name=value" 형식의 문자열로 사용. 메모리가 부족하면 -1 반환, errno는 ENOMEM으로 세팅됨.요샌 이럴일 없다고 보면 됨
  • 환경변수 출력 예제

<source lang="c"> /* environ.c */

  1. include <stdlib.h>
  2. include <stdio.h>
  3. include <string.h>

int main(int argc, char* argv[]) {

 char *var, *value;
 if(argc == 1 || argc > 3) {
   fprintf(stderr, "Usage: ./environ var [value]\n");
   return 1;
 }
 /* getenv로 환경변수를 읽는다 */
 var = argv[1];
 value = getenv(var);
 if(value)
   printf("환경변수 %s는 %s 값을 가지고 있음\n", var, value);
 else
   printf("환경변수 %s는 값이 없음\n", var);
 /* 프로그램에 두번째 인자가 전달되었는지 확인하고, 두번째 인자로 환경변수에 값을 써넣는다. */
 if(argc == 3) {
   char *string;
   value = argv[2];
   string = malloc(strlen(var)+strlen(value)+2);
   if(!string) {
     fprintf(stderr, "메모리 부족\n");
     return 1;
   }
   strcpy(string, var);
   strcat(string, "=");
   strcat(string, value);
   printf("putenv 함수를 이렇게 호출함: %s\n", string);
   if(putenv(string) != 0) {
     fprintf(stderr, "putenv 실패\n");
     free(string);
     return 1;
   }
   /* getenv 호출해서 변수가 바뀌었는지 확인 */
   value = getenv(var);
   if(value)
     printf("%s의 새 값은 %s입니다.\n", var, value);
   else
     printf("%s에 새 값이 지정되지 않았습니다.\n", var);
 }
 return 0;

} </source>

  • 실행화면은 다음과 같다.
$ ./environ HOME
환경변수 HOME는 /home/drake 값을 가지고 있음
$ ./environ FRED
환경변수 FRED는 값이 없음.
$ ./environ FRED hello
환경변수 FRED는 값이 없음.
putenv 함수를 이렇게 호출함: FRED=hello
FRED의 새 값은 hello입니다.
$ ./environ FRED
환경변수 FRED는 값이 없음.
  • 프로그램 내부에서 환경변수를 마음껏 써도 외부에 영향을 주지 않는다.
  • 변수값이 쉘에게 전달되지 않기 때문.
  • 다음 프로그램으로 environ 변수를 불러보자.

<source lang="c">

  1. include <stdlib.h>
  2. include <stdio.h>

int main() {

 char **env = environ;
 while(*env) {
   printf("%s\n", *env);
   env++;
 }
 return 0;

} </source>

  • 코드는 쉘에서 export를 쳤을때랑 비슷하게 나올거다.

time

<source lang="c">

  1. include <time.h>

time_t time(time_t *tloc); </source>

  • 시간을 처리하기 위한 데이터형은 time_t이다. 정수형이고, 날짜+시간을 초단위로 담는다. long타입.
  • tloc 인자가 null이 아닐 경우 tloc에 값을 넣는다.

<source lang="c"> /* envtime.c */

  1. include <time.h>
  2. include <stdio.h>
  3. include <unistd.h>

int main() {

 int i;
 time_t the_time;
 for(i = 1; i <= 10; i++) {
   the_time = time((time_t *) 0);
   printf("The time is %ld\n", the_time);
   sleep(2);
 }
 return 0;

} </source>

  • 실행결과는 다음과 같다.
drake@debian:~/cc$ cc -o envtime envtime.c
drake@debian:~/cc$ ./envtime
The time is 1402826222
The time is 1402826224
The time is 1402826226
The time is 1402826228
The time is 1402826230
The time is 1402826232
The time is 1402826234
The time is 1402826236
The time is 1402826238
The time is 1402826240
drake@debian:~/cc$

difftime

<source lang="c">

  1. include <time.h>

double difftime(time_t time1, time_t time2); </source>

  • 두 시간의 차이를 double로 반환한다.
  • 호환성을 위한 함수

gmtime

<source lang="c"

  1. include <time.h>

struct tm *gmtime(const time_t timeval); </source>

  • tm 구조체의 멤버
  1. int tm_sec - Seconds, 0~61
  2. int tm_min - Minutes, 0~59
  3. int tm_hour - Hours, 0~23
  4. int tm_mday - Day in month, 1~31
  5. int tm_mon - Month in the year, 0~11(1월은 0)
  6. int tm_year - 1900부터 시작
  7. int tm_wday - 무슨요일인지. 0~6(일요일이 0)
  8. int tm_yday - 1년중 몇일인지, 0~365
  9. int tm_isdst - 일광절약일인지이거 한국에서는 안쓴다. 몇몇 일광절약일이 타임존에 들어가있는데 적용하려면 필요
  • tm_sec의 범위는 가끔씩 윤초세계협정시에 오차범위 수정할때 가끔 쓰는거. 신경안써도 된다를 허용하므로 61까지 있다.

<source lang="c"> /* gmtime.c */

  1. include <time.h>
  2. include <stdio.h>

int main() {

 struct tm *tm_ptr;
 time_t the_time;
 (void) time(&the_time);
 tm_ptr = gmtime(&the_time);
 printf("Raw time is %ld\n", the_time);
 printf("gmtime gives:\n");
 printf("date: %02d/%02d/%02d\n", tm_ptr->tm_year, tm_ptr->tm_mon+1, tm_ptr->tm_mday);
 printf("time: %02d:%02d:%02d\n", tm_ptr->tm_hour, tm_ptr->tm_min, tm_ptr->tm_sec);
 return 0;

} </source>

  • 이건 다음과 같이 결과가 나온다.
drake@debian:~/cc$ ./gmtime
Raw time is 1402839108
gmtime gives:
date: 114/06/15
time: 13:31:48
drake@debian:~/cc$
  • year 부분이 이상하다. 원래 1900년으로부터 시작되니 그런거다. 정확히 나타내기 위해서는 1900을 더하면 된다.
  • 실제 실행해본 사람은 현재 시각이 이상하다는걸 느낄거다. gmtime은 협정세계시를 기준으로 나타낸다.

localtime

<source lang="c">

  1. include <time.h>

struct tm *localtime(const time_t *timeval); </source>

  • gmtime 대신 localtime으로 실행하면 실제 지역 시간으로 나온다.

mktime

<source lang="c">

  1. include <time.h>

time_t mktime(struct tm *timeptr); </source>

  • mktime은 tm 구조체로 되어있는 시간을 기본 시간으로 바꿔준다.

asctime, ctime

<source lang="c">

  1. include <time.h>

asctime(const struct tm *timeptr); char *ctime(const time_t *timeval); </source>

  • asctime은 tm 구조체를 받아서 어디서 많이 본것 같은 느낌의리눅스 기본 시간문자열로 변환해준다.
  • ctime은 기본 시간을 받아 리눅스 기본 시간문자열로 변환해준다.

<source lang="c"> /* ctime.c */

  1. include <time.h>
  2. include <stdio.h>

int main() {

 time_t timeval;
 (void)time(&timeval);
 printf("The date is: %s", ctime(&timeval));
 return 0;

} </source>

  • 실행하면 다음과 같이 나온다.
drake@debian:~/cc$ ./ctime
The date is: Sun Jun 15 09:43:11 2014
drake@debian:~/cc$