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

'2007/03'에 해당되는 글 5건
- 2007/03/29 printf() 함수의 여러가지 종류
- 2007/03/25 오파크 타입(Opaque Type) 활용하기 (2)
- 2007/03/24 윈도우에서는 1초를 이렇게 표현한다
- 2007/03/19 Subversion의 mailer.py 스크립트에서 Gmail SMTP 사용하기 (1)
- 2007/03/03 윈도우에서 Subversion과 ViewVC 사용하기와 한글 패치 공개 (13)
이러한 라이브러리나 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에서 컴파일 할 수 있도록 만든 소스입니다.
윈도우에서 1초를 어떻게 표현하는지 설명해 보겠습니다.
먼저 시간의 단위부터 알아야 하겠죠? 다들 잘 아시겠지만 시간 단위에 대해 애매한 분들을 위해 단위 부터 먼저 설명하겠습니다.
- 밀리 초(millisecond) : 천 분의 1초입니다. 10^-3, 1/1000
- 마이크로 초(microsecond) : 백만 분의 1초입니다. 10^-6, 1/1000000
- 나노 초(nanosecond): 십억 분의 1초입니다. 10^-9, 1/1000000000
나노 초 이하의 단위는 잘 쓰이지 않기 때문에 생략했습니다.
그러면 윈도우에서는 시간을 64비트 크기의 변수에 저장하며 최소 단위는 100 나노 초입니다. 즉 1씩 증가하면 100나노 초씩 증가하는 것이죠.
그래서 1초를 표현해보면 아래와 같습니다.
0000 0000 0000 1000 0000 (1초, 10 진수 만 단위)
1초가 되려면 10억 나노 초가 되야 하는데 천만을 쓴 것은 최소 단위가 100 나노 초이기 때문에 100을 나누어 주어서 입니다.
이것을 16진수로 표현하면 0x00000000 00989680이 됩니다. 이건 절대시간이므로 1601년 1월 1일 00시 00분 1초가 됩니다.
상대 시간은 음수로 표현하는데, 0x00000000 00989680을 음수로 바꾸어 보겠습니다. 일단 2진수로 바꾸면
0x00000000 00989680
11111111111111111111111111111111 11111111011001110110100101111111
1의 보수 (비트 반전)
11111111111111111111111111111111 11111111011001110110100110000000
2의 보수 (+1)
이렇게 하여 최종 값인 1111111111111111111111111111111111111111011001110110100110000000이 나왔습니다. 맨 앞에 비트가 1이므로 음수입니다.
그래서 상대시간 1초는 0xFFFFFFFF FF676980로 표현할 수 있습니다.
mailer.py 스크립트는 Subversion 소스 안에 tools/hook-scripts/mailer에 있습니다.
일단 이 스크립트를 사용하려면 저장소의 hooks 디렉토리안에 post-commit.tmpl 파일을 post-commit으로 이름을 바꿉니다. (Windows의 경우 post-commit.bat으로) 그리고 post-commit 파일의 맨 아래에 다음을 추가합니다.
Linux, BSD 등.
Windows
그리고 mailer.conf.example를 mailer.conf로 이름을 바꾸고 내용 Gmail에 맞게 설정을 합니다.
# This option specifies the hostname for delivery via SMTP.
smtp_hostname = smtp.gmail.com:587
# Username and password for SMTP servers requiring authorisation.
smtp_username = example@gmail.com
smtp_password = (이메일 암호)다른 설정은 자신의 환경에 맞게 설정합니다.
mailer.py의 SMTPOutput 클래스 finish 함수를 아래와 같이 수정합니다.
def finish(self):
server = smtplib.SMTP(self.cfg.general.smtp_hostname)
if self.cfg.is_set('general.smtp_username'):
server.ehlo() # 추가된 부분
server.starttls() # 추가된 부분
server.ehlo() # 추가된 부분
server.login(self.cfg.general.smtp_username,
self.cfg.general.smtp_password)
server.sendmail(self.from_addr, self.to_addrs, self.buffer.getvalue())
server.close() # server.quit()를 server.close()로 수정.이렇게 하면 Gmail SMTP 서버를 통해서 커밋 로그 메일을 보낼 수 있습니다.
Gmail은 TLS 인증을 사용하기 때문에 starttls()함수를 호출해 줘야 로그인이 됩니다.
ViewVC는 Subversion, CVS를 웹에서 간편하게 볼 수 있게 해주는 도구입니다. 원래는 ViewCVS였는데 Subversion을 지원하기 시작하면서 이름이 바뀌었습니다.
이번에는 윈도우에서 아파치와 Mod Python을 이용하여 ViewVC를 사용하는 방법을 다루었습니다.
처음에 제가 먼저 사용해 보려고 했을 때 CGI 모드와 Stand Alone 모드는 잘 동작했는데, 유독 Mod Python으로는 동작이 잘 되지 않았습니다. handler.py에서 path를 잘 받아오지 못해서 모듈을 못찾는 것이었습니다. 이 부분은 문서에 포함시켰습니다.
그리고 로그는 UTF-8로 인코딩 되어 있기 때문에 잘 나오는데, 소스 안에 있는 한글 주석과 diff를 했을 때, blame을 했을 때 한글이 깨지는 것, 시간을 UTC에서 한국 표준시 표시하도록 수정한 패치와, 패치된 파일도 첨부하였습니다. 패치된 소스는 저장소에서도 확인 할 수 있습니다.
enscript 사용하여 소스코드 컬러링(Syntax highlighting) 설정하는 방법도 설명하였습니다.
http://www.pyrasis.com/main/SubversionWithViewVCForWindows

댓글을 달아 주세요