[C,C++ 프로그래밍 목차 링크 모음]
안녕하세요~~!! 양햄찌 주인장입니다. 저번 시간에 여러 인자를 넘겨 받을 수 있도록 가변인자(variable argument)사용하는 법을 배웠는데요. 이 주제에 이어서 가변인자 매크로에 대해 얘기해볼까 합니다.
혹시 Variable Argument에 관한 내용을 모른다! 하시는 분들은 아래 포스팅을 먼저 읽고 오시면 이번편을 수월하게 이해하실 수 있습니다.
https://jhnyang.tistory.com/293
[C,C++] 가변인자 함수의 사용(va_start, va_arg, va_list등등) 함수에 불특정 여러개의 인자를 넘기고 싶�
[C, C++ 프로그래밍 강좌 목차] 안녕하세요~ 양햄찌 주인장입니다. 오늘은 오랜만에 프로그래밍 언어에 관련된 포스팅을 들고왔어요. 오늘의 주제 포스팅을 들어가기 전 'C++의 오버로딩'에 대한 ��
jhnyang.tistory.com
가변 인자가 무엇인가에 대한 부분은 알고 있다는 가정하에 진행하도록 할게용 :)
♣ 가변인자! 이제 쓸 줄 알아! 근데 가변매크로가 왜 필요해??
가장 많이 사용되는 대표적인 상황을 볼까요?
'어떤 변수에 무슨 값이 들어가있지/???' 이런거 확인하고 싶을 때 가장 쉽게 디버깅하는 방법은 바로, printf 함수를 써서 바로바로 찍어보는거잖아요. 개발할때야 툭하면 출력함수를 써서 이 값 저 값 확인하면서 개발을 진행하는데~
내가 개발을 다했어. 근데 내가 개발한게 다른 회사 서버랑 데이터를 주고받는 뭐 그런 프로그램이라고 해봅시다.
A회사가 어떤 데이터를 던져주면 그 데이터들 중 필요한 값을 뽑아서 쿵짝쿵짝 필요한 작업을 자동으로 하는 그런 프로그램이예요. 그런데 A회사에서 데이터를 아주 이상하게 보냈을 수 있겠죠. 하지만 얘가 데이터를 잘 줬는지 안줬는지 대체 우리가 어떻게 알죠?? 가장 좋은 방법은 프로그램이 실행될때마다 로그를 파일로 남기는겁니다.
이를 위해 로그를 찍는 printlog를 만들어줄거예요 (물론 정확히 하려면 파일 write를 해줘야하지만, 간단히 볼게요)
#include <stdio.h>
#include <stdarg.h> //가변인자를 위한 헤더파일~
void printlog_(const char* filename, const char* funcname, const int lineno, const char* fmt, ...);
void afunc(); void bfunc();
int main()
{
afunc(); //a함수 실행, 여기서 a함수가 가진 데이터 및 로그정보를 찍어주고 싶어
bfunc(); //b함수 실행, 여기서 b함수가 가진 데이터 및 로그 정보를 찍어주고 싶어
return 0;
}
//실행중인 파일명, 함수명, 라인번호와 함께 개발자가 확인하고 싶은 데이터를 출력해주는 로그함수
void printlog_(const char* filename,const char* funcname, const int lineno, const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
printf("[FILENAME: %s, FUNCNAME :%s, LINENO :%d] ", filename, funcname, lineno);
vprintf(fmt, ap);
va_end(ap);
}
void afunc()
{
int num = 3; //a함수가 가진 num값은 3
printlog_(__FILE__, __FUNC__, __LINENO__, "num: %d\n", num); //~아 너무 길어
}
void bfunc()
{
int num = 5; //b함수가 가진 num 값은 5
printlog_(__FILE__, __FUNC__, __LINENO__, "num: %d\n", num);
}
이렇게 아주아주 간단한 레이아웃만 짜봤어요. __FILE__, __FUNC__, __LINENO__등의 매크로가 궁금하신 분은 '로그출력함수만들기 포스팅'을 참고해주세요.
그런데 __FILE__, __FUNC__, __LINENO__를 로그출력할때마다 일일이 쓰기엔 너무 번거롭지 않나요?? 그냥 우리가 쓰는 printf 처럼 로그출력함수도 사용할 수 있음 좋겠다라는 생각이 들거예요. 매크로로 넘겨줘서 좀 간단하게 호출부분을 만들어보려고 합니다. 그런데 우리 가변인수인 '...'을 썼는데 이걸 매크로로 그대로 넘겨도 될까?? 넘길 수 있을까? 얘는 어떻게 넘기지?
#include <stdio.h>
#include <stdarg.h>
#define printlog(fmt, ...) printlog_(__FILE__, __FUNCTION__, __LINE__, fmt, ##__VA_ARGS__)
void printlog_(const char* filename, const char* funcname, const int lineno, const char* fmt, ...);
void afunc(); void bfunc();
int main()
{
afunc();
bfunc();
return 0;
}
void printlog_(const char* filename,const char* funcname, const int lineno, const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
printf("[FILENAME: %s, FUNCNAME :%s, LINENO :%d] ", filename, funcname, lineno);
vprintf(fmt, ap);
va_end(ap);
}
void afunc()
{
int num = 3;
printlog("num: %d\n", num);
}
void bfunc()
{
int num = 5;
printlog("num: %d\n", num);
}
넵 넘길 수 있습니다. 위처럼 가변인수 매크로를 사용해주면 된답니당. afunc과 bfunc의 로그호출부분을 보면 훨씬 간단해졌죠?? 이와 같은 상황 뿐만 아니라 가변인수 함수를 써야할 일이 매우 많은데요.
매크로도 가변 개의 인수를 인자로 넘길 수 있는데 이때 사용하는 게 가변인자 매크로입니다.
♣ 가변인수 매크로 알기 - '__VA_ARGS__'와 '##__VA_ARGS__'
가변 인수 매크로는 __VA_ARGS__로 표현됩니다.
요렇게요! __VA_ARGS__가 줄임표'...'와 일치하는 인수로 대체됩니다. 이런 가변인수 매크로를 Variadic Macros라고 해요.
//사용예시들
#define CHECK1(x, ...) if (!(x)) { printf(__VA_ARGS__); }
#define CHECK2(...) { printf(__VA_ARGS__); }
#define eprintf(…) fprintf(stderr, __VA_ARGS__)
잠깐! Variadic Macros?
Variadic 이란 단어는 사실 컴퓨터 용어입니다. 이 용어는 '가변적인'이라는 variable과 -adic이라는 접미사가 더해져 생긴 단어예요.
-adic은 'having a certain number of arguments' 특정 개수의 인자를 의미할 때 붙여져요. 따라서 Variadic은 개수가 변할 수 있는 인자를 가지는걸 의미합니다.
근데 얘가 문제가 하나 있어요.
printlog("num: %d\n", num);
이렇게 들어오면 위 사진의 경우, printlog_("num: %d\n", num) 즉 __VA_ARGS__부분에 'num'이 들어가니까 문제가 없는데,
printlog("done!\n");
이렇게 들어오면 실제 확장은 printlog_("done!\n", ) 가 되어 문제가 생깁니다.
printlog_("done!\n")가 아니라 printlog_("done!\n", )처럼 함수 내부가 컴마로 끝나면 gcc 컴파일러에서는 에러가 발생합니다. 즉 가변인수가 들어왔을 경우에는 괜찮은데, 가변인수가 한개도 들어오지 않으면 문제가 되는거예요.
(요새는 컴파일러들이 똑똑이라 알아서 자동적으로 처리해주는 경우도 물론 있습니다ㅎㅎ)
이를 보완하고자 나온게 '##__VA_ARGS__'예요. 기존 '__VA_ARGS__'에다가 샾(#)두개를 붙여주면 마지막에 컴마로 끝나더라도 컴마를 삭제해버려 무시가 됩니다. (참고로 더블샵 기호는 token paste operator라고 해요)
♣ 가변인수 매크로 알기 - (()), ##args
위에서 설명한 '__VA_ARGS__'와 '##_VA_ARGS__'는 C99에서 지정된 표준이예요. 이 매크로들은 그럼 표준 전에는 이런 방법이 없었나~? 필수적으로 이 기능을 써야 할 때가 분명 있었을 테니까 과거에도 이를 가능케 하는꼼수가 존재했습니다.
괄호 두개 (())
바로 괄호를 두개 사용하는건데요. 원리는 간단합니다.
이렇게 매크로를 정의하고 printlog를 호출할 때
이렇게 원하는 값을 'fmt'에 해당하는 부분에 넣어줍시다.
위 예시처럼 인자가 두 개인 ("num: %d\n", num)을 넣어줄 수도 있고 인자가 세 개인 ("num1: %d, num2: %d\n", num1, num2)를 넣어줄수도 있겠죠. 표준이 지정되기 전에는 이렇게 확장해서 사용했어요.
##args (named variable arguments)
Variadic macros are a new feature in C99. GNU CPP has supported them for a long time, but only with a named variable argument (args…, not … and __VA_ARGS__).
__VA_ARGS__이런 형태의 가변매크로들은 C99에 등장한 녀석들입니다. 그 전에도 GNU CPP는 오랫동안 이 가변매크로와 유사한 기능을 수행할 수 있도록 지원해왔는데, '...'도 아니고 당근 그 이후에 표준으로 잡힌 __VA_ARGS__도 아니고, 바로 'args...'형태로 지원했습니다.
#define printlog(fmt, args...) printlog_(fmt, ##args)
사용 방법은 가능합니다. 줄임말(...)부분 대신에는 'args...'을 '##__VA_ARGS__' 부분 대신에는'##args'를 써주시면 돼요. (##이 붙은 이유는 위에서 설명한 것과 동일!)
gnu에서만 가능해서 비주얼스튜디오에서는 적용 안됩니다. 리눅스에선 가능해요.
#include <stdio.h>
#include <stdarg.h>
#define printlog(fmt, args...) printlog_(fmt, ##args)
void printlog_(char* fmt, ...)
{
va_list ap; va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
}
int main()
{
printlog("num:%d\n", 5);
return 0;
}
참고로 꼭 'args'일 필요는 없어요. named variable arguments에서 유추할 수 있듯이 네이밍해주는 거기 때문에
#define printlog(fmt, var...) printlog_(fmt, ##var)
요렇게 짝짝꿍만 잘 맞춰줘도 된답니다. 이 방법은 비표준이라 다른 컴파일러에서 동작되리란 보장이 없습니다.
여담)
제가 이번에, 맡은 서비스 손좀 본답시고, '##__VA_ARGS__'를 활용해 소스를 좀 짰는데요.
서버에 대강 테스트로 짜서 돌려보니까 컴파일도 잘 되고 실행도 잘 되길래 기존 소스에다 삽입을 했습니다.
그런데...! 왠걸 컴파일이 안되는거예요. 하..핳ㅎ 맨 처음에는 make파일 문제인줄 알았죠.
암튼, 말하고자 하려는 부분은, 컴파일러가 가변인수 매크로를 지원을 해줘도, 소스 환경은 워낙 무궁무진하여... 이게 컴파일시 어떤 옵션과 라이브러리에 얽혀있느냐에 따라 ##__VA_ARGS__가 표준이여도 먹히지 않을 수 있습니다.
특히나 금융권은 위험에 대해 민감해 보수적이기 때문에 아주 오래된 언어를 사용하기도 하고, 비효율적인 소스들이 녹아있어도 잘 변경하지 않습니다. 가변인수 매크로를 'args...' 형태로 변경해서 결국 에러 잡는데 성공했는데,
GNU임이 확실하다면, 표준보다는 portable 을 생각해 'args...' & '##args'를 사용하는게 올바를 수 있어요. 상황에 맞게 잘 선별해서 쓰도록 합시다.
오늘 포스팅은 여기까지예요~!!
오늘도 고생하셨습니다. 다음 포스팅에서 또 봐용~~! :)
최신 댓글