Library4developers

깊이있는 삽질 Ubuntu Korea Community Wiki
이동: 둘러보기, 검색

라이브러리란?[편집]

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

동적 라이브러리와 정적 라이브러리의 특징[편집]

동적 라이브러리[편집]

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

정적 라이브러리[편집]

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

기본 정적 라이브러리[편집]

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

void fred(int arg)
{
  printf("fred: you passed %d\n", arg);
}
/* bill.c */
#include <stdio.h>

void bill(char *arg)
{
  printf("bill: you passed %s\n", arg);
}
  • 이러한 함수들을 각자 컴파일한 후 오프젝트파일을 만듦으로서 라이브러리파일에 포함시킬 수 있다. 우리는 -c 옵션을 사용함으로서 gcc가 실행가능한 완벽한 프로그램을 만들지 못하도록 해야 한다(.o 파일을 뽑아야 한다). 만일 이 소스를 -c옵션을 사용하지 않고 컴파일하면 main이 정의되지 않았다고 지랄실패할 것이다.
$ cc -c bill.c fred.c
$ ls *.o
bill.o    fred.o
  • bill 함수를 호출하는 프로그램을 작성해보자. 먼저, 라이브러리에 필요한 헤더파일을 만들자. 이 헤더파일은 라이브러리 내부에서 함수들을 선언하는 것이며, 본 라이브러리를 사용하는 프로그램은 모두 이 헤더파일을 포함해야 한다.
/* 이건 lib.h다. fred와 bill을 사용할 어플들은 모두 이걸 include하도록 */
void bill(char *);
void fred(int);
  • 본 라이브러리를 사용하는 프로그램(program.c)을 작성하는 일은 존나 쉽다. 필요한 라이브러리와 헤더파일을 포함하고, 라이브러리에 들어있는 함수만 호출하면 된다.
/* program.c */
#include "lib.h"

int main()
{
  bill("헬로 월드");
  return 0;
}
  • 이제 프로그램을 컴파일하고 테스트해보자. gcc한테 소스코드를 모두 오브젝트 파일로 컴파일하고 실행파일로 링크하라고 하자.
$ cc -c program.c
$ cc -o program program.o bill.o
$ program
bill : you passed 헬로 월드
$
  • 이제 라이브러리를 만들고 사용해보자. ar프로그램을 사용하여 archive을 만들고 여기에 오프젝트 파일을 추가한다. 이 프로그램이 ar이라 불리는 까닭은 archive를 만들거나 각각의 파일을 모아서 하나의 커다란 파일에다 모아주기 때문이다.아카이빙이라고 하면 있어보이긴 하는데 못알아듣잖아 ar은 텍스트 파일들의 archive를 만들때에도 사용할 수 있기 때문에 주목해야 한다.리눅스는 뭐하나 알면 두고두고 우려먹을 수 있으니까 좋잖아
$ ar crv lib.a bill.o fred.o
c - bill.o
c - fred.o
$
  • 이제 라이브러리를 만들었고, 두개의 오브젝트파일을 추가했다. 버클리 유닉스에서 파생된 몇몇 시스템에서는 라이브러리를 성공적으로 사용하기 위해 라이브러리 목록이 필요하다. 이 일은 ranlib라는 명령으로 처리한다. 하지만 리눅스와 같은 시스템에서 GNU 개발툴(build-essential)을 사용하면 이 과정은 필요없다.BSD에선 필요하다. 짜증
$ ranlib lib.a
  • 이제 라이브러리를 사용할 준비는 끝났다. 보통 프로그램을 만들 때 이러한 라이브러리를 추가할 수 있다.
$ cc -o program program.o lib.a
$ ./program
bill : you passed 헬로 월드
$
  • 오브젝트 파일이나, 라이브러리 또는 실행 가능한 프로그램에 어떤 함수가 포함돼있는지 알아보기 위해 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를 사용하면 해당 프로그램에서 어떠한 공유 라이브러리를 요구하는지 알아볼 수 있다.
$ 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)
  • 위의 경우 표준 C라이브러리(libc)가 공유되고 있음을 알 수 있다. 다른 유닉스 시스템에서도 공유라이브러리 접근 방법은 비슷비슷하다. 좀더 자세한 사항은 아는사람 있으면 추가바람
  • 공유라이브러리는 Windows에서 사용되는 동적 링크 라이브러리와 많은 부분이 비슷하다. .so라이브러리는 .dll과 대응되며, .a라이브러리는 .lib와 비슷하다고 보면 된다.

라이브러리의 설치[편집]

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

ex)

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

라이브러리 사용법[편집]

getopt[편집]

  • 일단 받은 인자를 출력하는 코드부터 보자.
/* args.c */
#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;
}
  • 프로그램을 실행시키면 프로그램의 인자와 옵션을 출력한다.
$ ./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가 있는거다.
#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");
  • getopt의 동작 순서는 다음과 같다.
  1. 옵션이 값을 취한다면, 해당 값을 전역변수 optarg에 넣는다.
  2. 더이상 처리할 옵션이 없다면 getopt에 -1을 반환.
  3. 인식할 수 없는 옵션이 있으면 getopt는 ?를 반환. 해당 옵션은 전역변수 optopt에 저장.
  4. 옵션이 값을 취하고 해당 값이 없으면 getopt는 :를 반환
  • 전역변수 optind는 처리할 다음 인자의 인덱스로 설정한다. 그래서 남은 인자가 argv 몇번째인지 알 수 있다.
  • POSIX는 opterr 변수가 0이 아닐 경우 getopt가 stderr로 에러메세지를 출력해야 한다고 권고하고 있다.
  • 아래는 getopt를 사용하는 예제다.
#include <stdio.h>
#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;
}
  • 실행해보면 다음과 같다.
$ 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[편집]

#include <stdlib.h>

char *getenv(const char *name);
int putenv(const char *string);
extern char **environ;
  • 환경변수는 name=value 형식으로 구성되어 있다.
  • getenv 함수는 주어진 name으로 사용된 환경변수를 찾아서 있으면 value값을 반환. 없으면 null 반환.
  • putenv 함수는 "name=value" 형식의 문자열로 사용. 메모리가 부족하면 -1 반환, errno는 ENOMEM으로 세팅됨.요샌 이럴일 없다고 보면 됨
  • 환경변수 출력 예제
/* environ.c */
#include <stdlib.h>
#include <stdio.h>
#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;
}
  • 실행화면은 다음과 같다.
$ ./environ HOME
환경변수 HOME는 /home/drake 값을 가지고 있음
$ ./environ FRED
환경변수 FRED는 값이 없음.
$ ./environ FRED hello
환경변수 FRED는 값이 없음.
putenv 함수를 이렇게 호출함: FRED=hello
FRED의 새 값은 hello입니다.
$ ./environ FRED
환경변수 FRED는 값이 없음.
  • 프로그램 내부에서 환경변수를 마음껏 써도 외부에 영향을 주지 않는다.
  • 변수값이 쉘에게 전달되지 않기 때문.
  • 다음 프로그램으로 environ 변수를 불러보자.
#include <stdlib.h>
#include <stdio.h>

int main()
{
  char **env = environ;

  while(*env) {
    printf("%s\n", *env);
    env++;
  }
  return 0;
}
  • 코드는 쉘에서 export를 쳤을때랑 비슷하게 나올거다.

time[편집]

time[편집]

#include <time.h>

time_t time(time_t *tloc);
  • 시간을 처리하기 위한 데이터형은 time_t이다. 정수형이고, 날짜+시간을 초단위로 담는다. long타입.
  • tloc 인자가 null이 아닐 경우 tloc에 값을 넣는다.
/* envtime.c */
#include <time.h>
#include <stdio.h>
#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;
}
  • 실행결과는 다음과 같다.
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[편집]

#include <time.h>

double difftime(time_t time1, time_t time2);
  • 두 시간의 차이를 double로 반환한다.
  • 호환성을 위한 함수

gmtime[편집]

struct tm *gmtime(const time_t timeval);
  • 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까지 있다.
/* gmtime.c */
#include <time.h>
#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;
}
  • 이건 다음과 같이 결과가 나온다.
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[편집]

#include <time.h>

struct tm *localtime(const time_t *timeval);
  • gmtime 대신 localtime으로 실행하면 실제 지역 시간으로 나온다.

mktime[편집]

#include <time.h>

time_t mktime(struct tm *timeptr);
  • mktime은 tm 구조체로 되어있는 시간을 기본 시간으로 바꿔준다.

asctime, ctime[편집]

#include <time.h>

asctime(const struct tm *timeptr);
char *ctime(const time_t *timeval);
  • asctime은 tm 구조체를 받아서 어디서 많이 본것 같은 느낌의리눅스 기본 시간문자열로 변환해준다.
  • ctime은 기본 시간을 받아 리눅스 기본 시간문자열로 변환해준다.
/* ctime.c */
#include <time.h>
#include <stdio.h>

int main()
{
  time_t timeval;

  (void)time(&timeval);
  printf("The date is: %s", ctime(&timeval));
  return 0;
}
  • 실행하면 다음과 같이 나온다.
drake@debian:~/cc$ ./ctime
The date is: Sun Jun 15 09:43:11 2014
drake@debian:~/cc$

strftime[편집]

#include <time.h>

size_t strftime(char *s, size_t maxsize, const char *format, struct tm *timeptr);
  • strftime은 printf문과 비슷한 형식으로 형식화해주는 함수다.
  • 인자 s의 크기는 최소한 maxsize보다 커야 한다.
  • 변환 식별자는 다음과 같다.
  1.  %a - 단축형 주일 이름
  2.  %A - 전체형 주일 이름
  3.  %b - 단축형 달 이름
  4.  %B - 전체형 달 이름
  5.  %c - 날짜와 시간
  6.  %d - 달 중 오늘이 며칠인지, 1~31
  7.  %H - 24시간제 시간, 0~23
  8.  %I - 12시간제 시간, 0-12
  9.  %J - 연 중 오늘이 며칠인지, 1~366
  10.  %m - 연중 달, 1~12
  11.  %M - 분, 0~59
  12.  %p - AM, PM
  13.  %S - 초, 0~61
  14.  %u - 지금 요일, 1~7(1은 월요일)
  15.  %U - 연중 지금이 몇째주인지 1~53(일요일이 주중 첫째날이라고 본다)
  16.  %V - 연중 지금이 몇째주인지 1~53(월요일이 주중 첫째날이라고 본다)
  17.  %w - 지금 요일, 0~6(0은 일요일)
  18.  %x - 지역 날짜
  19.  %X - 지역 시간
  20.  %y - 네자리 연도 2자리 뒷부분
  21.  %Y - 네자리 연도
  22.  %z - 시간 지대 이름
  • 쉘에서 date로 출력되는 시간은 strftime으로 다음과 같은 format으로 하면 비슷하게 나온다.
"%a %b %d %H:%M:%S %Y"

strptime[편집]

#include <time.h>

char *strptime(const char *buf, const char *format, struct tm *timeptr);
  • strftime와는 반대로, 문자열에서 변수로 변환한다.
  • 꼭 맞아 떨어져야 잘 들어가는데, 쉽지 않다.그리고 거의 쓸일도 없지
#include <time.h>
#include <stdio.h>

int main()
{
  struct tm *tm_ptr, timestruct;
  time_t the_time;
  char buf[256];
  char *result;

  (void) time(&the_time);
  tm_ptr = localtime(&the_time);
  strftime(buf, 256, "%A %d %B, %I:%S %p", tm_ptr);

  printf("strftime gives: %s\n", buf);

  strcpy(buf, "Tue 4 December 1995, 17:53 will do fine");

  printf("calling strptime with: %s\n", buf);
  tm_ptr = &timestruct;

  result = strptime(buf, "%a %d %b %Y, %R", tm_ptr);
 printf("strptime consumed up to: %s\n", result);

  printf("strptime gives:\n");
  printf("date: %02d/%02d/%02d\n", tm_ptr->tm_year, tm_ptr->tm_mon+1, tm_ptr->tm_mday);
  printf("time: %02d:%02\n", tm_ptr->tm_hour, tm_ptr->tm_min);
  return 0;
}

tmpnam, tmpfile[편집]

#include <stdio.h>

char *tmpnam(char *s);
FILE *tmpfile(void);
  • tmpnam 함수는 임시파일을 만들기 위한 파일이름을 생성한다. 무조건 기존과 다른 이름을 생성한다.
  • tmpfile 함수는 임시파일을 만들고 표준 입출력으로 연다.
/* tmpnam.c */
#include <stdio.h>

int main()
{
  char tmpname[L_tmpnam];
  char *filename;
  FILE *tmpfp;

  filename = tmpnam(tmpname);

  printf("임시파일 이름은:%s\n", filename);

  tmpfp = tmpfile();
  if(tmpfp)
    printf("임시파일 열림\n");
  else
    perror("Failed to open temporary file.");
  return 0;
}
  • 이에 대한 실행 결과를 보자.
drake@debian:~/cc$ ./tmpnam
임시파일 이름은:/tmp/fileVA6s2t
임시파일 열림
drake@debian:~/cc$

mktemp, mkstemp[편집]

#include <stdlib.h>

char *mktemp(char *template);
int mkstemp(char *template);
  • mktemp는 임시파일을 위해 template로부터 유일한 파일 이름을 만든다.
  • mkstemp는 임시파일을 만들고 저수준 시스템 콜로 open한다.

getuid, getlogin[편집]

#include <sys/types.h>
#include <unistd.h>

uid_t getuid(void);
char *getlogin(void);
  • getuid는 현재 프로그램이 실행되는 계정의 uid를 정수로 반환한다.
  • getlogin은 현재 프로그램이 실행되는 계정을 문자열로 반환한다.

getpwuid, getpwnam[편집]

#include <sys/types.h>
#include <pwd.h>

struct passwd *getpwuid(uid_t uid);
struct passwd *getpwnam(const char *name);
  • passwd는 비밀번호 구조체다. 다음과 같은 구조로 되어 있다.
  1. char *pw_name - 계정명
  2. char uid_t pw_uid - 계정 번호
  3. char gid_t pw_gid - 그룹 번호
  4. char *pw_dir - 사용자의 홈 디렉토리
  5. char *pw_shell - 사용자의 기본 쉘
  • getpwuid와 getpwnam은 둘다 passwd 구조체를 반환한다.
/* user.c */
#include <sys/types.h>
#include <pwd.h>
#include <stdio.h>
#include <unistd.h>

int main()
{
  uid_t uid;
  gid_t gid;
  struct passwd *pw;

  uid = getuid();
  gid = getgid();

  printf("사용자는 %s\n", getlogin());
  printf("사용자 아이디: uid=%d, gid=%d\n", uid, gid);

  pw = getpwuid(uid);
  printf("UID passwd entry:\n계정명=%s, uid=%d, gid=%d, home=%s, shell=%s\n", pw->pw_name, pw->pw_uid, pw->pw_gid, pw->pw_dir, pw->pw_shell);

  pw = getpwnam("root");
  printf("root passwd entry:\n");
  printf("계정명=%s, uid=%d, gid=%d, home=%s, shell=%s\n", pw->pw_name, pw->pw_uid, pw->pw_gid, pw->pw_dir, pw->pw_shell);
  return 0;
}
  • 프로그램을 실행하면 다음과 같은 결과가 나온다.
drake@debian:~/cc$ ./user
사용자는 drake
사용자 아이디: uid=1000, gid=1000
UID passwd entry:
계정명=drake, uid=1000, gid=1000, home=/home/drake, shell=/bin/bash
root passwd entry:
계정명=root, uid=0, gid=0, home=/root, shell=/bin/bash
drake@debian:~/cc$

endpwent, getpwent, setpwent[편집]

#include <pwd.h>

void endpwent(void);
struct passwd *getpwent(void);
void setpwent(void);
  • getpwent는 사용자 목록을 읽는다.
  • endpwent는 목록을 읽다 만다.
  • setpwent는 사용자를 적어넣고 첫번째 엔트리로 간다.

geteuid, getgid, getegid, setuid, setgid[편집]

#include <sys/types.h>
#include <unistd.h>

uid_t geteuid(void);
gid_t getgid(void);
gid_t getegid(void);
int setuid(uid_t uid);
int setgid(gid_t gid);
  • uid와 gid를 얻어내는 함수
  • root만 setuid와 setgid를 쓸 수 있다.

gethostname[편집]

#include <unistd.h>

int gethostname(char *name, size_t namelen);
  • gethostname은 머신의 이름을 name에 기록한다.

uname[편집]

#include <sys/types.h>

int uname(struct utsname *name);
  • uname은 name 포인터가 가리키는 구조체에 호스트정보를 기록한다. 그 내용은 다음과 같다.
  1. char sysname[] - OS 이름
  2. char nodename[] - 호스트이름
  3. char release[] - 시스템 릴리즈
  4. char version[] - 시스템 버전 번호
  5. char machine[] - 하드웨어 타입
  • uname 함수는 성공할때는 양수를 반환하고, 에러시에는 -1을 반환하고 errorno가 설정된다.
/* hostget.c */
#include <sys/utsname.h>
#include <unistd.h>
#include <stdio.h>

int main()
{
  char computer[256];
  struct utsname uts;

  if(gethostname(computer, 255) != 0 || uname(&uts) < 0) {
    fprintf(stderr, "Could not get host information\n");
    return 1;
  }

  printf("Computer Host name is %s\n", computer);
  printf("System is %s on %s hardware \n", uts.sysname, uts.machine);
  printf("Nodename is %s\n", uts.nodename);
  printf("Version is %s, %s\n", uts.release, uts.version);
  return 0;
}
  • 실행하면 다음과 같은 결과가 나온다.
drake@debian:~/cc$ ./hostget
Computer Host name is debian
System is Linux on i686 hardware
Nodename is debian
Version is 3.2.0-4-686-pae, #1 SMP Debian 3.2.51-1
drake@debian:~/cc$
  • 쉘에서 uname이랑 거의 같다.

gethostid[편집]

#include <unistd.h>

long gethostid(void);
  • 호스트아이디는 컴퓨터의 식별번호로 쓰기 좋은 일련번호를 반환한다.
#include <syslog.h>

void syslog(int priority, const char *message, arguments...);
  • 메세지 로그를 위한 함수.
  • 다음은 유저단계다.
  1. LOG_USER - 유저 단계
  2. LOG_LOCAL1 - 로컬 1단계
  3. LOG_LOCAL2 - 로컬 2단계
  4. LOG_LOCAL3 - 로컬 3단계
  5. LOG_LOCAL4 - 로컬 4단계
  6. LOG_LOCAL5 - 로컬 5단계
  7. LOG_LOCAL6 - 로컬 6단계
  8. LOG_LOCAL7 - 로컬 7단계
  • 다음은 우선순위.
  1. LOG_EMERG - 비상사태
  2. LOG_ALERT - 비상상황(DB 에러 등)
  3. LOG_CRIT - 임계에러(하드웨어 실패 등)
  4. LOG_ERR - 에러
  5. LOG_WARNING - 경고개발자는 경고를 무시하지
  6. LOG_NOTICE - 주의사항
  7. LOG_INFO - 정보 메세지
  8. LOG_DEBUG - 디버그 메세지
  • 위의 우선순위와 비상사태 단계
  • 일반적으로 LOG_EMERG 메세지는 모든 사용자에게 방송되고, LOG_ALERT는 관리자메일로 발송될 수 있다.
/* syslog.c */
#include <syslog.h>
#include <stdio.h>

int main()
{
  /* 없는 파일 읽기 */
  FILE *f;

  f = fopen("not_here", "r");
  if(!f)
    syslog(LOG_ERR|LOG_USER, "Oops - %m\n");
  return 0;
}
  • 실행시에 출력되는건 없지만 dmesg | tail로 로그를 확인할 수 있다.

closelog, openlog, setlogmask[편집]

#include <syslog.h>

void closelog(void);
void openlog(const char *ident, int logopt, int facility);
int setlogmask(int maskpri);
  • openlog / closelog로 기록 메세지 표현방법을 변경할 수 있다.
  • ident 설정은 다음과 같다. | 연산자로 여러가지 지정이 가능하다.
  1. LOG_PID - 메세지 안에 프로세스 식별자 포함
  2. LOG_CONS - 메세지 기록에 실패하면 콘솔로 보낸다.
  3. LOG_ODELAY - 연결은 메세지가 작성될 때 확립
  4. LOG_NDELAY - 연결 즉시 확립
  • setlogmask로 예를 들어 LOG_DEBUG 메세지는 꺼버릴 수 있다.릴리즈 버전이 나뉘어 있을 경우 쓸만하다
/* logmask.c */
#include <syslog.h>
#include <stdio.h>
#include <unistd.h>

int main()
{
  int logmask;

  openlog("logmask", LOG_PID|LOG_CONS, LOG_USER);
  syslog(LOG_INFO, "informative message, pid = %d", getpid());
  syslog(LOG_DEBUG, "debug message, sould apeear");
  logmask = setlogmask(LOG_MASK(LOG_DEBUG));
  syslog(LOG_DEBUG, "debug message, should not appear");
  return 0;
}
  • 프로그램을 실행하면 출력은 없지만 /var/log/messages와 /var/log/debug에 로그가 추가된다.

getpriority, setpriority, getrlimit, setrlimit, getrusage[편집]

#include <sys/resource.h>

int getpriority(int which, id_t who);
int setpriority(int which, id_t who, int priority);
int getrlimit(int resource, struct rlimit *r_limit);
int setrlimit(int resource, const struct rlimit *r_limit);
int getrusage(int who, struct rusage *r_usage);
  • id_t는 사용자와 그룹 식별자에 사용되는 정수
  • rusage는 /usr/include/아키텍쳐-linux-gnu/sys/resource.h를 확인
  • rusage구조체는 다음과 같다.
  1. struct timeval ru_utime - 사용된 사용자 시간
  2. struct timeval ru_stime - 사용된 시스템 시간
  • timeval 구조체는 /usr/아키텍쳐-linux-gnu/sys/time.h에 정의되어 있음
  • 사용자 시간은 유저모드로 돌아간 시간, 시스템 시간은 커널모드로 돌아간 시간이다.
  • who는 다음 중 하나다.
  1. RUSAGE_SELF - 현재 프로그램 정보만 반환
  2. RUSAGE_CHILDREN - 자식 프로그램 정보도 포함해서 반환
  • 응용프로그램은 getpriority와 setpriority로 우선순위를 측정하거나 변경한다.
  • which는 다음중 하나를 선택한다.
  1. PRO_PROCESS - who는 프로세스 식별자
  2. PRO_PGRP - who는 프로세스 그룹
  3. PRO_USR - who는 사용자 식별자
  • 현재 프로세스의 우선순위를 알아보려면 다음과 같이 호출할 수 있다.
priority = getpriority(PRIO_PROCESS, getpid());
  • setpriority는 우선순위변경이 가능할 경우 새로운 우선순위로 설정한다. 기본값은 0
  • 음수는 높은 우선순위, 양수는 낮은 우선순위.
  • getpriority는 성공하면 우선순위가 반환되고 실패하면 -1 반환
  • 우선순위에 -1이 들어갈 수 있다. 그러므로 errno를 확인해야 한다.
  • getlimit와 setrlimit는 rlimit 구조체를 사용한다. 구조체는 다음의 멤버를 가진다.
  1. rlim_t rlim_cur - soft인지 hard인지, 현재의 자원수준
  2. rlim_t rlim_max - 최대 자원
  • resource에 지정하는 rlimit 관련 항목
  1. RLIMIT_CORE - 코어덤프 파일크기제한, 바이트 단위
  2. RLIMIT_CPU - CPU 시간제한, 초단위
  3. RLIMIT_DATA - 데이터 세그먼트 제한, 바이트 단위
  4. RLIMIT_FSIZE - 파일 사이즈 제한, 바이트 단위
  5. RLIMIT_NOFILE - 개방 파일 갯수 제한
  6. RLIMIT_STACK - 스택 크기 제한, 바이트 단위
  7. RLIMIT_AS - 주소 공간 제한, 바이트 단위
/* rlimit.c */
#include <sys/types.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
/* work 함수는 임시파일에 10000번 쓰기 시도하고, CPU에 부하를 걸기 위해 약간의 산술 연산을 수행한다. */
void work()
{
  FILE *f;
  int i;
  double x = 4.5;

  f = tmpfile();
  for(i = 0; i < 10000; i++) {
    fprintf(f, "Do some output\n");
    if(ferror(f)) {
      fprintf(stderr, "임시파일 작성 실패\n");
      exit(1);
    }
  }
  for(i = 0; i < 10000; i++)
    x = log(x*x + 3.21);
}
/* main은 work를 부르고 getrusage를 써서 얼마만큼의 CPU시간을 소비했는지 알아보고, 이에 관한 정보를 출력한다. */
int main()
{
  struct rusage r_usage;
  struct rlimit r_limit;
  int priority;

  work();
  getrusage(RUSAGE_SELF, &r_usage);

  printf("CPU usage: Uer = %ld.%06ld, System = %ld.%06ld\n", r_usage.ru_utime.tv_sec, r_usage.ru_utime.tv_usec, r_usage.ru_stime.tv_sec, r_usage.ru_stime.tv_usec);

  /* getpriority와 getrlimit를 사용해 현재의 우선순위값과 파일크기 제한을 알아낸다. */
  priority = getpriority(PRIO_PROCESS, getpid());
  printf("Current priority = %d\n", priority);

  getrlimit(RLIMIT_FSIZE, &r_limit);
  printf("Current FSIZE limit: soft = %ld, hard = %ld\n", r_limit.rlim_cur, r_limit.rlim_max);

  r_limit.rlim_cur = 2048;
  r_limit.rlim_max = 4096;
  printf("Setting a 2K file size limit\n");
  setrlimit(RLIMIT_FSIZE, &r_limit);

  work();
  return 0;
}
  • 실행결과는 다음과 같다.
drake@debian:~/cc$ cc -o limits limits.c -lm
drake@debian:~/cc$ ./limits
CPU usage: Uer = 0.004000, System = 0.004000
Current priority = 0
Current FSIZE limit: soft = -1, hard = -1
Setting a 2K file size limit
File size limit exceeded
drake@debian:~/cc/wrox$ nice ./limits
CPU usage: Uer = 0.008000, System = 0.000000
Current priority = 10
Current FSIZE limit: soft = -1, hard = -1
Setting a 2K file size limit
File size limit exceeded
drake@debian:~/cc$
  • Math 라이브러리를 사용했으니 -lm을 이용해 math 라이브러리를 포함해야 한다.
  • nice로 우선순위를 좀 낮춰줄 수 있다.

malloc[편집]

#include <stdlib.h>

void *malloc(size_t size);
  • 동적 메모리 할당을 해준다.
  • 원래 반환되는 데이터 타입은 void* 이다. 타입캐스트로 char*형으로 바꿔서 쓰는게 일반적이다.
  • 32비트에서는 4GB까지 지정할 수 있다.가상메모리니까 하드 존나 읽겠지 하지마 그런거
/* memory1.c */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

#define A_MEGABYTE (1024 * 1024)

int main() {
  char *some_memory;
  int megabyte = A_MEGABYTE;
  int exit_code = EXIT_FAILURE;

  some_memory = (char *)malloc(megabyte);
  if(some_memory != NULL) {
    sprintf(some_memory, "Hello World\n");
    printf("%s", some_memory);
    exit_code = EXIT_SUCCESS;
  }
  return exit_code;
}
  • 메모리를 자기 물리 메모리만큼 잡아보자. 뻗나 안 뻗나.
/* memory2.c */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

#define A_MEGABYTE (1024 * 1024)

int main() {
  char *some_memory;
  size_t size_to allocate = A_MEGABYTE;
  int megs_obtained = 0;

  while(megs_obtained < 8192) {
    some_memory = (char *)malloc(size_to_allocate);
    if(some_memory != NULL) {
      megs_obtained++;
      sprintf(some_memory, "Hello World");
      printf("%s - now allocated %d Megabytes\n", some_memory, megs_obtained);
    }
    else {
      exit(EXIT_FAILURE);
    }
  }
  exit(EXIT_SUCCESS);
}
  • 2GB의 물리시스템을 가진 시스템에서 3056MB까지 버텼다.
  • 좀 더 하드하게 해보자.
/* memory3.c */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

#define ONE_K (1024)

int main() {
  char *some_memory;
  int size_to_allocate = ONE_K;
  int megs_obtained = 0;
  int ks_obtained = 0;

  while(1) {
    for(ks_obtained = 0; ks_obtained < 1024; ks_obtained++) {
      some_memory = (char *)malloc(size_to_allocate);
      if(some_memory == NULL) exit(EXIT_FAILURE);
      sprintf(some_memory, "Hello World");
    }
    megs_obtained++;
    printf("Now allocated %d Megabytes\n", megs_obtained);
  }
  exit(EXIT_SUCCESS);
}
  • 같은 시스템에서 3045MB까지 버텼다.
  • 아마 64비트에서는 더 버틸거다.
  • 메모리를 조금 할당하고 메모리 외부에 write를 시도해볼까?
/* memory4.c */
#include <unistd.h>
#include <stdlib.h>

#define ONE_K (1024)

int main() {
  char *some_memory;
  char *scan_ptr;

  some_memory = (char *)malloc(ONE_K);
  if(some_memory == NULL) exit(EXIT_FAILURE);

  scan_ptr = some_memory;
  while(1) {
  *scan_ptr = '\0';
    scan_ptr++;
  }
  exit(EXIT_SUCCESS);
}
  • 세그먼트 폴트.
  • 윈도우에서 잘못된 메모리 접근도 다 이거다.
  • 널포인터 접근을 보자.
/* memory5.c */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main() {
  char *some_memory = (char *)0;

  printf("A read from null %s\n", some_memory);
  sprintf(some_memory, "A write to null\n");
  exit(EXIT_SUCCESS);
}

free[편집]

#include <stdlib.h>

void free(void *ptr_to_memory);
  • 할당된 메모리를 해제하는 역할을 한다.
  • c가 어렵다고 다들 이야기하는 부분이 이 부분이다하지만 존나 중요하지

calloc, realloc[편집]

#include <stdlib.h>

void *calloc(size_t number_of_elements, size_t element_size);
void *realloc(void *existing_memory, size_t new_size);
  • calloc는 배열 동적할당이다. number_of_elements가 원소 갯수, element_size가 원소의 크기.
  • realloc는 재할당이다.크기조절에 실패하면 널포인터를 반환하는데 이게 좀 거시기하다.
  • realloc을 사용하기보다는, 새로 할당하고 이전 할당된걸 해제하는 식으로 설계하는것이 좋다.

gdbm[편집]

  • 데비안 계열(우분투 포함)에서는 libgdbm-dev 패키지를 설치하면 된다.
  • dbm은 데이터블록과 키 데이터로 이뤄져 있다.
  • 기본적으로는 FILE형과 유사하다.
  • 데이터베이스 파일은 .dir과 .pag로 이루어져 있다.

dbm_open, dbm_store, dbm_fetch, dbm_close[편집]

#include <gdbm.h>

DBM *gdbm_open(const char *filename, int file_open_flags, mode_t file_mode);
int gdbm_store(DBM *database_descriptor, datum key, datum content, int store_mode);
datum gdbm_fetch(DBM *database_descriptor, datum key);
void gdbm_close(DBM *database_descriptor);
  • gdbm_open은 존재하는 db를 열거나 새 db를 만드는 역할을 한다. open이랑 비슷. 성공하면 DBM, 실패하면 (DBM *)0 반환
  • gdbm_store는 db에 데이터를 저장한다.
  1. store_mode를 dbm_insert로 설정하면 저장이 안되고 1을 반환
  2. store_mode를 dbm_replace로 설정하면 예전 데이터를 덮어쓰고 0을 반환
  3. 에러시에는 음수를 반환
  • gdbm_fetch는 db에서 데이터를 가져온다. 반환에는 datum만 반환된다.
  • gdbm_close는 db를 닫는다.

sqlite3[편집]

  • SQLite는 파일 기반의 RDBMS다.
  • SQLite는 다른 RDBMS에 비해 작아서 좀더 여러 디바이스에서 사용한다.
  • 일단 스마트폰에 들어가는 RDBMS는 다 이거라고 볼 수 있겠다.
  • 2014년 현재 sqlite3가 주력으로 사용되고 있다.
  • 라이브러리는 다음과 같이 설치가 가능하다.
$ sudo apt-get install libsqlite3-dev

sqlite3_open, sqlite3_exec, sqlite3_close[편집]

#include <sqlite3.h>

int sqlite3_open(const char *filename, sqlite3 **ppDb);
int sqlite3_exec(sqlite3*, const char *sql, sqlite_callback, void *data, char **errmsg);
sqlite3_close(sqlite3*);
  • sqlite3_open은 디비를 연다. 파일이름은 utf8형식이다파일이름 그냥 영어로 좀 해 제발
  • sqlite3_exec는 SQL쿼리를 실행한다. sql은 sql문이고, callback 함수와 그에 들어가는 데이터를 집어넣을 수 있다.
  • sqlite3_close는 디비를 닫는다.
  • 일단 디비를 한번 열어보자.
/* sqliteopen.c */
#include <stdio.h>
#include <sqlite3.h> 

int main(int argc, char* argv[])
{
   sqlite3 *db;
   char *zErrMsg = 0;
   int rc;

   rc = sqlite3_open("test.db", &db);

   if( rc ){
      fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
      return -1;
   }else{
      fprintf(stderr, "Opened database successfully\n");
   }
   sqlite3_close(db);
}
  • mysql도 쉽지만, 이거에 비할바는 안되지.
  • 컴파일시 -lsqlite3 만 붙이면 된다.
  • db 여는걸 확인했으면 테이블을 한번 만들어 보자.
/* sqlite3createtable.c */
#include <stdio.h>
#include <stdlib.h>
#include <sqlite3.h> 

static int callback(void *NotUsed, int argc, char **argv, char **azColName){
   int i;
   for(i=0; i<argc; i++){
      printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
   }
   printf("\n");
   return 0;
}

int main(int argc, char* argv[])
{
   sqlite3 *db;
   char *zErrMsg = 0;
   int  rc;
   char *sql;

   /* Open database */
   rc = sqlite3_open("test.db", &db);
   if( rc ){
      fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
      return -1;
   }else{
      fprintf(stdout, "Opened database successfully\n");
   }

   /* Create SQL statement */
   sql = "CREATE TABLE COMPANY("  \
         "ID INT PRIMARY KEY     NOT NULL," \
         "NAME           TEXT    NOT NULL," \
         "AGE            INT     NOT NULL," \
         "ADDRESS        CHAR(50)," \
         "SALARY         REAL );";

   /* Execute SQL statement */
   rc = sqlite3_exec(db, sql, callback, 0, &zErrMsg);
   if( rc != SQLITE_OK ){
   fprintf(stderr, "SQL error: %s\n", zErrMsg);
      sqlite3_free(zErrMsg);
   }else{
      fprintf(stdout, "Table created successfully\n");
   }
   sqlite3_close(db);
   return 0;
}
  • SQL은 그냥 exec만 하면 된다. 별다를게 없다 정말...
  • 테이블도 열렸는데 insert 한번 해보자.
/* sqlite3insert.c */
#include <stdio.h>
#include <stdlib.h>
#include <sqlite3.h> 

static int callback(void *NotUsed, int argc, char **argv, char **azColName){
   int i;
   for(i=0; i<argc; i++){
      printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
   }
   printf("\n");
   return 0;
}

int main(int argc, char* argv[])
{
   sqlite3 *db;
   char *zErrMsg = 0;
   int rc;
   char *sql;

   /* Open database */
   rc = sqlite3_open("test.db", &db);
   if( rc ){
      fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
      return -1;
   }else{
      fprintf(stderr, "Opened database successfully\n");
   }

   /* Create SQL statement */
   sql = "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) "  \
         "VALUES (1, 'Paul', 32, 'California', 20000.00 ); " \
         "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) "  \
         "VALUES (2, 'Allen', 25, 'Texas', 15000.00 ); "     \
         "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY)" \
         "VALUES (3, 'Teddy', 23, 'Norway', 20000.00 );" \
         "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY)" \
         "VALUES (4, 'Mark', 25, 'Rich-Mond ', 65000.00 );";

   /* Execute SQL statement */
   rc = sqlite3_exec(db, sql, callback, 0, &zErrMsg);
   if( rc != SQLITE_OK ){
      fprintf(stderr, "SQL error: %s\n", zErrMsg);
      sqlite3_free(zErrMsg);
   }else{
      fprintf(stdout, "Records created successfully\n");
   }
   sqlite3_close(db);
   return 0;
}
* 값을 넣어봤으면 읽어오는것도 필요하지
<source lang="c">
/* sqlite3select.c */
#include <stdio.h>
#include <stdlib.h>
#include <sqlite3.h> 

static int callback(void *data, int argc, char **argv, char **azColName){
   int i;
   fprintf(stderr, "%s: ", (const char*)data);
   for(i=0; i<argc; i++){
      printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
   }
   printf("\n");
   return 0;
}

int main(int argc, char* argv[])
{
   sqlite3 *db;
   char *zErrMsg = 0;
   int rc;
   char *sql;
   const char* data = "Callback function called";

   /* Open database */
   rc = sqlite3_open("test.db", &db);
   if( rc ){
      fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
      return -1;
   }else{
      fprintf(stderr, "Opened database successfully\n");
   }

   /* Create SQL statement */
   sql = "SELECT * from COMPANY";

   /* Execute SQL statement */
   rc = sqlite3_exec(db, sql, callback, (void*)data, &zErrMsg);
   if( rc != SQLITE_OK ){
      fprintf(stderr, "SQL error: %s\n", zErrMsg);
      sqlite3_free(zErrMsg);
   }else{
      fprintf(stdout, "Operation done successfully\n");
   }
   sqlite3_close(db);
   return 0;
}
  • update도 한번 봅시당
/* sqlite3update.c */
#include <stdio.h>
#include <stdlib.h>
#include <sqlite3.h> 

static int callback(void *data, int argc, char **argv, char **azColName){
   int i;
   fprintf(stderr, "%s: ", (const char*)data);
   for(i=0; i<argc; i++){
      printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
   }
   printf("\n");
   return 0;
}

int main(int argc, char* argv[])
{
   sqlite3 *db;
   char *zErrMsg = 0;
   int rc;
   char *sql;
   const char* data = "Callback function called";

   /* Open database */
   rc = sqlite3_open("test.db", &db);
   if( rc ){
      fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
      return -1;
   }else{
      fprintf(stderr, "Opened database successfully\n");
   }

   /* Create merged SQL statement */
   sql = "UPDATE COMPANY set SALARY = 25000.00 where ID=1; " \
         "SELECT * from COMPANY";

   /* Execute SQL statement */
   rc = sqlite3_exec(db, sql, callback, (void*)data, &zErrMsg);
   if( rc != SQLITE_OK ){
      fprintf(stderr, "SQL error: %s\n", zErrMsg);
      sqlite3_free(zErrMsg);
   }else{
      fprintf(stdout, "Operation done successfully\n");
   }
   sqlite3_close(db);
   return 0;
}
  • 같은 내용 지겹죠? 한번만 더 해봅시다 delete.
/* sqlite3delete.c */
#include <stdio.h>
#include <stdlib.h>
#include <sqlite3.h> 

static int callback(void *data, int argc, char **argv, char **azColName){
   int i;
   fprintf(stderr, "%s: ", (const char*)data);
   for(i=0; i<argc; i++){
      printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
   }
   printf("\n");
   return 0;
}

int main(int argc, char* argv[])
{
   sqlite3 *db;
   char *zErrMsg = 0;
   int rc;
   char *sql;
   const char* data = "Callback function called";

   /* Open database */
   rc = sqlite3_open("test.db", &db);
   if( rc ){
      fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
      return -1;
   }else{
      fprintf(stderr, "Opened database successfully\n");
   }

   /* Create merged SQL statement */
   sql = "DELETE from COMPANY where ID=2; " \
         "SELECT * from COMPANY";

   /* Execute SQL statement */
   rc = sqlite3_exec(db, sql, callback, (void*)data, &zErrMsg);
   if( rc != SQLITE_OK ){
      fprintf(stderr, "SQL error: %s\n", zErrMsg);
      sqlite3_free(zErrMsg);
   }else{
      fprintf(stdout, "Operation done successfully\n");
   }
   sqlite3_close(db);
   return 0;
}
  • sqlite3는 사용하는데 쫄지만 않으면 매우 쉽다고 볼 수 있다.

MySQL[편집]

  • 사용하려면 다음의 패키지를 설치해야 한다.
$ sudo apt-get install libmysqlclient-dev

mysql_get_client_info[편집]

/* mysqlinfo.c */
#include <stdio.h>
#include <mysql.h>

int main(int argc, char **argv)
{
  printf("MySQL Client version: %s\n", mysql_get_client_info());
  return 0;
}
  • 간단히 버전을 알아보는 프로그램이다.
  • 컴파일 방법은 다음과 같다.
$ cc -o mysqlinfo mysqlinfo.c `mysql_config --cflags --libs`

mysql_real_connect, mysql_query[편집]

/* mysqlcreatedb.c */
#include <my_global.h>
#include <mysql.h>

int main(int argc, char **argv)
{  
  MYSQL *con = mysql_init(NULL);

  if (con == NULL) 
  {
      fprintf(stderr, "%s\n", mysql_error(con));
      exit(1);
  }

  if (mysql_real_connect(con, "localhost", "root", "root_pswd", 
          NULL, 0, NULL, 0) == NULL) 
  {
      fprintf(stderr, "%s\n", mysql_error(con));
      mysql_close(con);
      exit(1);
  }  

  if (mysql_query(con, "CREATE DATABASE testdb")) 
  {
      fprintf(stderr, "%s\n", mysql_error(con));
      mysql_close(con);
      exit(1);
  }

  mysql_close(con);
  return 0;
}
  • 위의 프로그램은 testdb라는 데이터베이스를 추가한다.
  • root를 사용할 수 있는데서만 쓸 수 있으니, 교육용으로는 별로 좋지 않다.
  • 그러므로, 유저를 하나 만들어 보자.
mysql> CREATE USER testdb@localhost IDENTIFIED BY '34klq*';
mysql> GRANT ALL ON testdb.* to testdb@localhost;
  • testdb라는 아이디에 34klq*라는 비밀번호를 주고 testdb의 권한을 주는거다.
  • phpmyadmin으로 해도 되고, 위의 프로그램에 쿼리문을 변경하여 사용해도 된다.
  • testdb라는 데이터베이스가 있고, testdb라는 유저가 testdb에 접근할 수 있다는 가정 하에 값을 입력하는걸 짜보자.
/* mysqlinsert.c */
#include <my_global.h>
#include <mysql.h>

void finish_with_error(MYSQL *con)
{
  fprintf(stderr, "%s\n", mysql_error(con));
  mysql_close(con);
  exit(1);        
}

int main(int argc, char **argv)
{
  MYSQL *con = mysql_init(NULL);
  
  if (con == NULL) 
  {
      fprintf(stderr, "%s\n", mysql_error(con));
      exit(1);
  }  

  if (mysql_real_connect(con, "localhost", "testdb", "34klq*", 
          "testdb", 0, NULL, 0) == NULL) 
  {
      finish_with_error(con);
  }    
  
  if (mysql_query(con, "DROP TABLE IF EXISTS Cars")) {
      finish_with_error(con);
  }
  
  if (mysql_query(con, "CREATE TABLE Cars(Id INT, Name TEXT, Price INT)")) {      
      finish_with_error(con);
  }
  
  if (mysql_query(con, "INSERT INTO Cars VALUES(1,'Audi',52642)")) {
      finish_with_error(con);
  }
  
  if (mysql_query(con, "INSERT INTO Cars VALUES(2,'Mercedes',57127)")) {
      finish_with_error(con);
  }
  
  if (mysql_query(con, "INSERT INTO Cars VALUES(3,'Skoda',9000)")) {
      finish_with_error(con);
  }
  
  if (mysql_query(con, "INSERT INTO Cars VALUES(4,'Volvo',29000)")) {
      finish_with_error(con);
  }
  
  if (mysql_query(con, "INSERT INTO Cars VALUES(5,'Bentley',350000)")) {
      finish_with_error(con);
  }
  
  if (mysql_query(con, "INSERT INTO Cars VALUES(6,'Citroen',21000)")) {
      finish_with_error(con);
  }
  
  if (mysql_query(con, "INSERT INTO Cars VALUES(7,'Hummer',41400)")) {
      finish_with_error(con);
  }
  
  if (mysql_query(con, "INSERT INTO Cars VALUES(8,'Volkswagen',21600)")) {
      finish_with_error(con);
  }

  mysql_close(con);
  return 0;
}
  • 프로그램을 컴파일해서 실행하고 phpmyadmin이나 콘솔로 확인해보면 데이터가 들어가있는걸 확인할 수 있다.

mysql_store_result, mysql_num_fields, mysql_fetch_row[편집]

/* mysqlfetch.c */
#include <my_global.h>
#include <mysql.h>

void finish_with_error(MYSQL *con)
{
  fprintf(stderr, "%s\n", mysql_error(con));
  mysql_close(con);
  exit(1);        
}

int main(int argc, char **argv)
{      
  MYSQL *con = mysql_init(NULL);
  
  if (con == NULL)
  {
      fprintf(stderr, "mysql_init() failed\n");
      exit(1);
  }  
  
  if (mysql_real_connect(con, "localhost", "testdb", "34klq*", 
          "testdb", 0, NULL, 0) == NULL) 
  {
      finish_with_error(con);
  }    
  
  if (mysql_query(con, "SELECT * FROM Cars")) 
  {
      finish_with_error(con);
  }
  
  MYSQL_RES *result = mysql_store_result(con);
  
  if (result == NULL) 
  {
      finish_with_error(con);
  }

  int num_fields = mysql_num_fields(result);

  MYSQL_ROW row;
  
  while ((row = mysql_fetch_row(result))) 
  { 
      for(int i = 0; i < num_fields; i++) 
      { 
          printf("%s ", row[i] ? row[i] : "NULL"); 
      } 
          printf("\n"); 
  }
  
  mysql_free_result(result);
  mysql_close(con);
  
  return 0;
}
  • 이제 데이터베이스에서 뽑아오는것도 할 수 있다.