Home | Info | Research | Blog | Repos | Messages | Contact Me

 


'C 언어'에 해당되는 글 2건

  1. 2007/03/29 printf() 함수의 여러가지 종류
  2. 2007/03/25 오파크 타입(Opaque Type) 활용하기 (2)

C언어를 배울때면 맨처음 만나게 되는 함수가 printf() 함수입니다. 명령 프롬프트(터미널)상에 문자열을 출력하는 함수죠.

하지만 프로그래밍을 더욱 배우다 보면 printf()와 이름이 비슷한 함수들이 많은 것을 알 수 있습니다. 막상 printf()는 그다지 쓸일이 없어집니다.

그러면 이제 printf() 함수의 종류를 알아보도록 하겠습니다.

printf() : 표준 출력 함수입니다.  도스시절 부터 화면상에 문자열을 출력하는 함수입니다. 윈도우에서는 명령 프롬프트에 출력을 하게 됩니다. (운영체제에 따라 시리얼 포트가 표준 출력으로 설정되어 있으면 모니터 화면이 아니라 시리얼 포트에 달린 터미널에 출력됩니다. 또는 임베디드 장비라면 소형 LCD 화면이 될 수도 있습니다.)

fprintf() : 파일에 문자열을 쓰는 함수입니다. 첫번째 인자로 파일(FILE)의 포인터를 받습니다. 그리고 그 파일에 문자열을 씁니다.

sprintf() : 배열이나 포인터로된 버퍼에 문자열을 쓰는 함수입니다. 첫번째 인자로 버퍼의 포인터를 받습니다. 그리고 그 버퍼에 문자열을 씁니다.

vprintf() : 화면상에 문자열을 표시합니다. printf() 함수처럼 가변인자 함수가 아니라 인자의 갯수가 2개로 정해져 있습니다. 첫번째 인자는 출력 할 문자열(포맷)이고, 두번째 인자는 va_list를 받습니다. va_list는 va_start, va_end, va_arg 매크로를 사용하여 얻어와야 합니다.

printf() 계열 함수는 크게 4가지로 나눌 수 있고, 여기에 버퍼 길이를 지정해 줄 수 있는 *n*printf() 함수가 있습니다. 그리고 s, f, v를 조합하여 여러가지 함수가 나옵니다.

이렇게해서 나온 변종들을 살펴 보면

snprintf : 버퍼에 문자열을 쓰고 버퍼 길이를 지정해 줄 수 있습니다.
vfprintf : 파일에 문자열을 쓰고 va_list를 받습니다.
vsprintf: 버퍼에 문자열을 쓰고 va_list를 받습니다.
vsnprintf: 버퍼에 문자열을 쓰고 버퍼길이를 지정해 줄 수 있으며 va_list를 받습니다.

w가 붙은 함수가 있는데 이것은 wide character(wchar_t)를 받습니다. 유니코드를 처리할 때 사용합니다. wprintf(), fwprintf(), swprintf(), snwprintf(), vwprintf(), vswprintf(), vsnwprintf() 등이 있습니다.

t가 붙은 함수는 컴파일 옵션에 따라서 함수가 바뀝니다. tprintf() 함수는 전처리기(preprocessor) 옵션이 MBCS 일 경우 printf 함수가 사용되고 UNICODE일 경우 wprintf() 함수가 사용됩니다. tprintf(), ftprintf(), stprintf(), vtprintf() 등이 있습니다. 물론 n계열 함수도 모두 있습니다.

c가 붙은 함수는 콘솔(화면)에 바로 출력하는 함수입니다. 이 함수는 conio.h에 정의되어 있습니다. 일반 printf() 함수는 표준 출력 함수이고, cprintf() 함수는 비디오 메모리에 바로 출력하는 것입니다. 하지만 윈도우에 와서는 그다지 차이가 없습니다. cprintf(), scprintf(), vcprintf() 등이 있습니다. 이 함수들은 n계열은 없고, w계열 함수만 있습니다.

여기까지는 C 표준 함수들이고, 아래는 각 운영체제에서 지원하는 디버그 전용 printf() 함수들입니다. 이 디버그 전용 함수들은 윈도우의 경우 디버그(debug, checked) 모드에서만 프로그램에 포함되고, 릴리즈(release, free) 모드로 빌드 할 때에는 프로그램에 포함되지 않습니다. (이것도 전처리기 옵션을 따릅니다.)

DbgPrint() : 윈도우 커널 모드 드라이버에서 문자열을 출력하기 위해 사용하는 함수입니다. KdPrint()와 똑같은 함수입니다. (KdPrint()는 DbgPrint() 함수의 매크로입니다.) vDbgPrintEx()는 va_list를 받습니다.

printk() : 리눅스 커널에서 문자열을 화면이나 dmesg, syslog 등에 출력하는 함수입니다. 콘솔의 로그레벨에 따라서 출력이 될 수도 있고 안될 수 도 있습니다.

dprintf() : 유닉스 계열 운영체제에서 볼 수 있는 디버그 전용 함수입니다. 문자열을 화면에 출력할 수도 있고, 로그파일에 쓸 수도 있습니다. 함수로 구현된 것도 있고 매크로인 것도 있습니다. 유닉스 계열 함수는 워낙 제멋대로라서 형태는 여러가지입니다.




우리가 프로그램을 만들다 보면 다른 곳에서 만든 라이브러리를 가져다 쓰거나, 운영체제에서 제공해주는 API를 사용하는 경우가 대부분입니다.

이러한 라이브러리나 API에서 제공해주는 함수를 사용하려면 그 라이브러리, API에서 제공해주는 자료형을 사용해야 하는 경우가 많습니다. 그런데 이 자료형들은 대부분 구조체가 아닌 포인터들입니다. 그냥 메모리 주소만 담고 있죠. 구조체로 정의되어 있지 않기 때문에 내부가 어떤 멤버들로 구성되어 있는지 알 방법이 없습니다. 이런 것들을 오파크 타입(Opaque Type)이라고 합니다. 대표적인 예가 윈도우 프로그래밍에서의 HANDLE 입니다.

오파크 타입에 대한 설명은 이정도로 하고 실제로 사용하는 방법을 알아보도록 하겠습니다.


먼저 헤더 파일을 2개를 만들도록 하겠습니다.

implement.h : 실제 구현용 헤더 파일
struct opaque_t_ {
    int hello;
    int world;
}

typedef struct opaque_t_ *opaque_t;

opaque.h : 배포용 헤더 파일
typedef void *opaque_t;

void OpaqueInit(opaque_t *opa);
void OpaqueFree(opaque_t *opa);
void SetValue(opaque_t opa);
void PrintValue(opaque_t opa);

이렇게 헤더 파일을 2개로 만든 이유는 실제 구현에 쓰기 위한 헤더 파일과 배포하기 위한 헤더 파일을 나누기 위해서입니다.

실제 구현에 쓰기 위한 헤더파일에는 모든 구조체 정보가 정의되어 있고 배포용 헤더 파일은 opaque_t를 void 포인터로만 정의하고 있습니다. 그래서 배포용 헤더 파일만 받는 쪽은 구조를 알 수 없는 오파크 타입을 이용하게 되는 것입니다.

이제 이 오파크 타입을 처리하는 함수들을 만들어 보겠습니다.

implement.c : 라이브러리 구현
#include <stdio.h>
#include <stdlib.h>
#include "implement.h"


/* 오파크 타입이 구조체 선언이 아닌 포인터이기 때문에 동적 메모리 할당을 해줘야 한다. */
void OpaqueInit(opaque_t *opa)
{
    opaque_t opaque;

    opaque = malloc(sizeof(struct opaque_t_));

    *opa = opaque;
}

/* 동적 메모리 해제 */
void OpaqueFree(opaque_t *opa)
{
    free(*opa);
}

void SetValue(opaque_t opa)
{
    opa->hello = 10;
    opa->world = 20;
}

void PrintValue(opaque_t opa)
{
    printf("hello : %d, world : %d\n", opa->hello, opa->world);
}

OpaqueInit() 함수는 주석에서도 알 수 있듯이 opaque_t가 단순한 구조체 선언이 아니라 void 포인터이기 때문에 실제로 사용하려면 동적 메모리 할당을 해야 사용할 수 있습니다. 그리고 동적 메모리를 할당 했기 때문에 OpaqueFree() 함수로 메모리를 해제 합니다.

SetValue() 함수는 이 opaque_t에 특정 값을 넣고, PrintValue() 함수는 opaque_t의 내용을 화면에 출력해 줍니다. 라이브러리를 구현할 때에는 implement.h를 가지고 있기 때문에 구조체 멤버 변수에 마음대로 접근 할 수 있습니다.

이제 이 opaque.h, implement.h, implement.c를 정적 라이브러리(Static Library)로 만듭니다. 그리고 배포를 할 때에는 implement.h는 빼고, opaque.h와 컴파일된 라이브러리 opaque.lib만 배포하면 됩니다. 그래서 라이브러리를 사용하는 쪽에서는 implement.h 파일이 없기 때문에 opaque_t의 구조를 알 수가 없는 것입니다.

program.c : opaque.lib과 opaque.h을 사용하여 프로그램 작성
#include <opaque.h>


int main()
{
    opaque_t opa;

    OpaqueInit(&opa);

    SetValue(opa);
    PrintValue(opa);

    OpaqueFree(&opa);

    return 0;
}

opaque.lib에서 제공하는 함수들을 사용하여 프로그램을 작성합니다. 이 프로그램을 실행하면 hello : 10, world : 20이 출력될 것입니다.

윈도우 API와 비교하자면 OpaqueInit() 함수와 SetValue() 함수를 합친 것이 CreateFile() 함수이며, OpaqueFree() 함수는 CloseHandle() 함수라고 할 수 있습니다. (단 핸들은 메모리 주소가 아닌 핸들 테이블상의 순서이므로 내부적으로 한번 더 처리를 합니다.)

opaque-src.zip : Visual C++ 2005에서 컴파일 할 수 있도록 만든 소스입니다.