노무현 대통령 배너


2006. 8. 10. 10:35

[응용] printf를 잘 쓰자

출처: http://kelp.or.kr/korweblog/stories.php?story=04/09/11/0212459

[응용] printf를 잘 쓰자
글쓴이 : 유영창 (2004년 09월 11일 오전 12:30) 읽은수: 4,365 [ 임베디드강좌/유영창 인쇄용 페이지 ]
APK003 print를 잘 쓰자
==============================================

1. 개요

이 문서는 ESP-NS에서 동작하는 응용 프로그램에서
printf 를 쓰는 방법에 대한 소개 입니다.

작성자 : 유영창
frog@falinux.com
작성일 : 2004년 9월 10일
수정일 :

관련된 ADK( Application Developer Kit ) 디렉토리

adk/sample/printf

2. 최강의 디버거 printf

응용 프로그램을 작성하는 과정에 반드시 수반되는 것이
디버깅입니다. 이 과정은 프로그래머에게는 숙명입니다.
이걸 피해갈 사람은 없읍니다.

윈도우 프로그래머라면 통합 환경에서 제공하는 디버깅기능을
즐겨 사용하게 됩니다.

이런 분들이 임베디드 리눅스에서 동작하는 프로그램을 디버깅
하면 갑갑함을 느낍니다.

왜냐?

임베디드 리눅스에서 사용할만한 통합환경이 없다는 것이
가장 큰 이유입니다

그렇다면 리눅스에는 쓸만한 디버거가 없을까요?

있읍니다 그것도 엄청 막강한 gdb라는 툴이 있읍니다.

그러나

제 주위에서 이 프로그램을 사용하는 사람 본적이
별로 없읍니다.

왜냐하면 준비하는데 엄청난(?) 작업이 필요하기 때문입니다.
특히 저같이 게으른 사람에게는 gdb를 이용해서 작업하는
과정은 무척 어려운 일입니다.

라인 명령을 사용해야하고 디버깅을 위한 준비작업 또한
만만치 않기 때문입니다.

그래서 저는 printf 라는 막강한 디버거를 사용합니다.

사실 임베디드 시스템에서 printf 함수는 목적하는 기능구현을
위해서 사용할 필요가 없는 함수입니다.

뭐 .... PC 리눅스에서도 마찬가지죠..

하지만

이 printf 함수 만큼 즐겨 사용하는 함수가 없읍니다.

주로 디버깅을 위해서 사용합니다.

이 printf 디버거는 무척 강력합니다.

일단 준비할 것이 없읍니다

그냥 printf 만 사용하면 됩니다.

그리고 내가 원하는 모든 변수값들을 찍어 볼수 있읍니다.
내가 원하는 함수의 문장을 통과하는지를 관찰할수 있읍니다.

디버깅이라는 것이 이게 다 아니겠읍니까?

리눅스 프로그램머들은 고수가 될수록 이 printf 함수를
자유자재로 사용합니다.

저 스스로도 고수라고 칭하므로 당근 이 printf 함수를
아주 적절히(?) 절묘하게(?) 사용합니다.
이건 고수들만의 노하우도 됩니다. ( 퍽~~ ㅜㅜ )

그런데 이 printf 를 그냥 사용하는 것은 조금 불편합니다.

그래서 조금 변경해서 사용하는데 이것을 소개할까 합니다.
그리고 더불어 주의점도요...

3. printf함수도 시간을 빼앗아 간다.

printf 를 그냥 사용해도 되지만 디버깅 단계에서는 유용하겠지만
나중에는 수많은 메세지들이 콘솔에 난무하는 엄청난 혼란을
발생합니다. ( 당해본 사람만 압니다.)

그래서 고수(?)들은 이 printf 를 그냥사용하지 않고 매크로 함수로
변형해서 사용합니다.

간단하게 소개 하면 이렇게 어딘가에 선언을 합니다.

//#define NDEBUG

#ifndef NDEBUG
#define dp(fmt,args...) printf( fmt, ## args )
#define dlp(fmt,args...) printf( "[%s %d]" fmt, __FILE__,__LINE__, ## args )
#else
#define dp(fmt,args...)
#define dlp(fmt,args...)
#endif


이것은 NDEBUG 라는 것이 선언되어 있지 않으면
dp 라는 것은 printf 함수로 대치 됩니다.
( dlp 라는 것은 조금 나중에 설명하겠읍니다. )

만약 NDEBUG 라는것이 선언되면 printf 는 아무것도 하지 않고
프로그램 소스에서 제거 됩니다.

예를 들어

printf( "hello worldn" );

이라고 쓰는 것은

dp( "hello worldn" );

라고 쓰면 됩니다.

#define NDEBUG 라는 문장이 없으면
위 문장들은 출력을 발생하게 됩니다.

그러나

#define NDEBUG 를 선언하게 되면

dp( "hello worldn" );

는 아무런 일도 하지 않습니다. 더구나 컴파일러의 최적화에 의해서
실행코드의 크기도 작아 먹지 않습니다.

위에서 소개한 dp 문은 보통 전체 소스 파일들이 사용하는 공통 헤더파일에
정의해 써 놓고 사용하는 것이 좋습니다.

평소에는 dp 문을 이용하여 출력을 하다가

NDEBUG 라는 문자열만 사용하면 싹~ 없어지기 때문에 나중에 한꺼번에
출력이 안될수도 있기 때문에 추척 편리한 함수입니다.

하나의 소스파일에서 메세지 출력을 없애고 싶다면
해당 소스파일에 위 정의문의 바로 위나 위 정의문을 포함한 헤더파일을
선언하는 #include 문 앞에 NDEBUG 라는 문자열을 정의하면 됩니다.

정말 정말 편리한 기능입니다.

만약 프로그램 전체에서 dp를 사용한 문자열의 출력을 제거하고 싶다면
그냥 컴파일 옵션에 -DNDEBUG 라는 것만 추가 하면됩니다.

APK 에 있는 샘플에 소개한 Makefile을 사용한다면
Makefile 에 있는

CFLAGS += -Wall -O2 -g



CFLAGS += -Wall -O2 -g -DNDEBUG

로 바꾸면 됩니다.

4. printf함수를 이용한 위치 추적

혹시 다음과 같은 문장의 의미를 여러분은 아시는 지 모르겠읍니다.

printf( "%s %s %dn", __FILE__, __FUNCTION__, __LINE__ );

이것은 이 문장이 적혀진 라인을 포함하는 파일명과 함수명 그리고
파일의 라인번호를 출력하게 합니다.

그래서 이것을 이용해서 dlp라는 함수를 선언합니다.

dp는 단순히 어떤 메세지나 값을 표출하는데 사용한다면
dlp는 해당 메세지가 표출된 위치를 함께 표출하기 위해서 사용합니다.

5. r 을 출력하는 문자열 라인의 앞에 추가 하자!

초기에 보드에 프로그램을 작성 하다 보면 디버깅을 위한 메세지를
출력하면서 동작상태를 관찰하게 됩니다.

그런데 응용 프로그램을 자동으로 실행하게 했다면 로긴이 안된 상태이기
때문에 문자열의 'n' 문자 때문에 다음과 같은 현상이 발생합니다.

message line 1
message line 2
message line 3

원래는 줄 앞에 나란히 정렬해야 하는데 삐뚤 삐뚤하게 출력되기 때문에
애써 예쁘게 출력하도록 한 내용이 보기 어렵게 됩니다.

그래서 한 라인의 시작 문자열 맨 앞에서는 'r' 문자를 사용하는 것이 좋습니다.

예를 들어

printf( "message line 1n" );
printf( "message line 2n" );
printf( "message line 3n" );

이라고 한다면 이것을 다음과 같이 고쳐 주어야 합니다.

printf( "rmessage line 1n" );
printf( "rmessage line 2n" );
printf( "rmessage line 3n" );

6. n을 사용하기 전까지는 화면에 출력되지 않는다.

printf 문장을 사용해서 다음과 같이 특정 변수명값을 추적한다고 합시다.

while(1)
{
:
printf( "%d ", test_v );
:
}

이렇게 하고 출력이 나올걸 기대하면 아마도 전혀 의도하지 않는 현상이
발생할겁니다.

리눅스에서 printf 문은 'n' 을 만나기 전까지는 출력 처리가 되지 않는
다는 점을 기억합시다.!!!!

왜 이렇게 될까요?

그것은 여러 프로세스들이 서로 출력을 할때 보기 편하라는 의미가 담겨
있읍니다

A 프로그램도 출력도 하고 B 프로그램도 출력을 하고 있다면 아마도
먼저 보낸 순서대로 출력이 되어야 할겁니다. 그러면 서로 짬봉이
되는 바람에 무슨 내용인지 알수가 없죠...

그래서 'n'을 먼저 출력하는 프로그램의 출력 내용을 표출하는 것입니다.

그래서 출력 결과를 보시고자 한다면 항상 출력하고자 하는 내용의 마지막에는
'n'을 꼭 추가하시기 바랍니다.

7. printf 는 실제로 시리얼로 출력하는 것이다.

ESP 보드는 printf 가 메인 콘솔에서 본다면 시리얼로 데이터가 전송되는 것입니다.
메인 콘솔의 속도가 115200 으로 되어 있지만 이 속도가 그렇게 빠르지 않다는 점을
명심하시기 바랍니다.

나중에 printf 가 블럭 되기도 하고 아주 빠른 처리를 요구하는 부분에서 printf를
남발하면 큰 코 다치게 됩니다.

특히 printf 는 그 처리가 무척 복작한 함수입니다. 그래서 한번만 호출해도
엄청난 수행시간을 필요로 합니다.

그래서 가끔 아주 간단한 지연이 필요한 경우에는 이 printf 함수를 사용하기도
한다는 점 기억하시기 바랍니다. 그만큼 처리 속도를 잡아 먹는 다는 이야기 입니다.

첨부 파일: printf.zip printf.zip (26 KiB(26,216 Bytes))

[응용] printf를 잘 쓰자 | 답장: 12개(RSS) | 본문에 답장 | 답장 검색
정렬 :
답장 익명 (2004년 09월 11일 오후 08:20)
바로 위 글과 같은 것이 "문서(다큐먼트)"라고 불리움을 받는 것 같습니다.
세심하고 꼼꼼한 설명에 감사드립니다.
[ 이글에 답장 | 본문에 답장 ]

답장 유영창 (2004년 09월 11일 오후 11:01)
감사합니다.
몸둘바를 모르겠군요... ^^
[ 이글에 답장 | 본문에 답장 ]

답장 곽한택 (2004년 09월 13일 오후 06:08)
유영창님께서 잘 정리해주셔서 감사합니다.
제가 알고 있는 팁중에...설명하신 부분이외에 프로그램시
도움이 될것 같아 간단하게 적어봅니다.

#ifdef DEBUG
#define debugX(level, fmt, args...) if (DEBUG >= level) printf(fmt, ##args)
#endif

1. debug code 출력을 원할 경우

#define DEBUG 1
..........
debugX(0, "test:%dn", temp);
..........

라고 하면 DEBUG 값이 level 값보다 크기 때문에 printf문의 내용이 출력 됩니다.


2. debug code 출력을 원하지 않을 경우
#define DEBUG 1

...........
debugX(2, "test:%dn", temp);
...........

라고 하면 DEBUG 값보다 level 값이 크므로, printf의 내용이 출력이 안되겠죠?

level 값과 DEBUG 값을 잘 사용하시면 디버그 할 때 도움이 될거 같군요...

괜히 몇 자 더 적어 유영창님께서 쓰신 글에 누가 되지는 않는지 모르겠습니다..
사족을 단 것 같기도 하고....쩝...
혹시 유영창님께서 기분 나쁘신건 아니겠죠 ^^
[ 이글에 답장 | 본문에 답장 ]

답장 유영창 (2004년 09월 14일 오전 11:34)
절...대...루...

기분 나쁘지 않습니다.

전 제글에 질문보다. 이렇게 추가되거나 수정되어야 할
내용이 올라가는 것을 좋아합니다. ^^
[ 이글에 답장 | 본문에 답장 ]

답장 익명 (2004년 09월 15일 오후 03:41)
일단 좋은 정보에 감사드립니다...
문득 궁금한게 생겨서요..
#define dp(fmt,args...) printf( fmt, ## args )
위에 보면 이런 macro함수에서 ...이 리눅스에서는 잘 되는데..
윈도우에서 가변인자로 받게 되면 어떻게 수정해야 하나요..
(fmt,args...) .때문에 자꾸 에러가 나서요..

그럼 좋은하루되세요.
^^
[ 이글에 답장 | 본문에 답장 ]

답장 익명 (2004년 09월 16일 오전 11:20)
에뮬레이터가 없는 이상 사실상 가장 유용한 정보가 이부분이 아닌가 생각 합니다.
하지만.. 말씀 하신거 처럼 이러한 정보를 보기 위해서 시간 딜레이가 많이 생기져나요.
또한 가끔은 출력 내용이 밀려서 사라지기도 하죠. (무작정 버퍼를 늘릴수는 없는 노릇이니)
흔히 두가지, 인터럽트 방식으로 출력하냐 폴링 방식으로 출력하냐 하는 문제가
적절히 조화 되어야 정확한 디버깅 정보를 볼수 있겠죠.
이런 부분에 대해 좀더 정리를 해 주시면 만점 정보가 될수 있을거 같네요.
예전에 하던 일에서는 printf를 이 두가지방법에 따라 나눠서 사용했던
것이 있었는데.. 격이 가물 가물하네요.
여튼.. 항상 매진하시는 보습이 부럽습니다. ㅡㅜ
[ 이글에 답장 | 본문에 답장 ]

답장 익명 (2004년 10월 13일 오전 10:58)
Visual C++ 6.0 에서는 이렇게 해 보시죠

#define DEBUG 1

#if DEBUG
#define dp printf
#define dlp printf( "[%s %d] ", __FILE__,__LINE__);
printf
#else
#define dp
#define dlp
#endif
[ 이글에 답장 | 본문에 답장 ]

답장 엘스디 (2004년 10월 29일 오후 03:12)
Visual C++에서는 인자를 여러개사용하는게 안가능하던데여.. ㅡ_ㅡ;;
리눅스환경에서는 args... 이렇게 쓰면 그뒤로 복수의 인자들이 다 args...에
들어가는 거 같던데
비주얼환경에서는 args... 가 에러가 납니다...
그래서 비쥬얼 환경에서는 인자를 여러개 찍을수가 없고
args 만 문법에러가 안나기때문에 인자를 하나밖에 찍을 수가 없네요

비쥬얼에서도 여러개 찍을수 있게 하려면 어떤 문법을 사용해야 할까요?
[ 이글에 답장 | 본문에 답장 ]

답장 쫑아빠 (2004년 11월 06일 오전 01:49)
이렇게 하시면 됩니다.
#ifdef _DEBUG_DD
#define DEBUG_MSG(s) printf s
#else
#define DEBUG_MSG(s)
#endif

괄호가 printf 우측에 없어서 이상하시죠?...
하지만 이렇게 하면 인자의 수에 관계없이 사용하실 수 있습니다.
단 사용하실때 주의하셔야 하는데요..
다음과 같이 괄호를 항상 두개씩 넣어 주셔야 합니다.
ex)
DEBUG_MSG(("Flash SST39LF800A Detectedn"));
DEBUG_MSG(("FlashProgram Failed at [0x%x] by TimeOutn",write_ptr));
DEBUG_MSG(("FlashProgram write Failed at [0x%x], retry=[%d] n",write_ptr,nRetry));
위의 세가지 경우처럼 인자가 없는 경우, 하나있는경우 둘 있는 경우
모두 문제없이 사용할 수 있습니다.
그럼.. 도움 되시길...
[ 이글에 답장 | 본문에 답장 ]

답장 익명 (2005년 06월 11일 오전 10:41)
근데.. n 에 대한 설명이 그게 맞나요?

r 과 n 은 현재의 커서 위치를 라인의 맨 앞, 다음 라인..

으로 옮기는거 아닌가요?

n 이 먼저 나오는 놈을 출력하다뇨??
[ 이글에 답장 | 본문에 답장 ]

답장 익명 (2005년 07월 07일 오전 09:53)
unix에서 gcc로 컴파일을 하는데요, #define NDEBUG가 작동을 안하네요.
#include 바로 윗부분에 define문장을 써넣었는데...
왜 그런지 알수 없습니다.
답변 부탁드립니다