가변인자 포워딩 시키기 :: 2007/09/07 13:57


[마소 플러스]
C 프로그래밍 테크닉
가변인자 포워딩시키기
-------------------------------------
신영진 http://www.jiniya.net | 웰비아닷컴에서 보안 프로그래머로 일하고 있다. 시스템 프로그래밍에 관심이 많으며 다수의 PC 보안 프로그램 개발에 참여했다. 현재 데브피아 Visual C++ 섹션 시삽과 Microsoft Visual C++ MVP로 활동하고 있다. C와 C++, Programming에 관한 이야기를 좋아한다.
-------------------------------------

C 언어 함수의 가장 큰 특징 중의 하나는 가변 인자를 지원한다는 점이다. 가변 인자란 인자의 개수가 가변적이란 의미다. func란 함수가 가변 인자 함수라고 한다면 func(1), func(1,2), func(a,b,str) 등으로 호출할 수 있다. 이러한 특징을 가장 잘 활용한 대표적인 함수가 printf다.

<리스트 1>의 코드에 이번 달에 언급할 문제가 나와 있다. mysum은 단순히 인자를 realsum으로 전달하는 기능을 담당하고 있다. 이번 달의 문제는 mysum을 구현하는 것이다. <리스트 1>의 mysum처럼 구현하면 될 것 같지만, 이 코드는 컴파일되지 않는다.  realsum을 <리스트 1>과 같이 호출하는 것이 불가능하기 때문이다. 나머지 부분을 읽기 전에 이 문제를 어떻게 해결할 수 있을지를 한번쯤 고민해 보자.

<리스트 1> 다른 함수로 가변 인자 전달

문제를 해결하기 위해서는 가변 인자가 동작하는 구조에 대해 정확하게 이해할 필요가 있다. 가변 인자를 이해하기 위해서는 __cdecl 함수 호출 규약과 함수 호출에 스택이 어떻게 사용되는지에 대해 먼저 이해해야 한다(http://www.jiniya.net/lecture/techbox/callconv.html 참고). <리스트 2>에는 가변 인자에 사용되는 매크로의 구현 소스가 소개되어 있다. 이를 살펴보면 알 수 있겠지만 va_start, va_end, va_arg에는 사실 그다지 대단한 내용이 들어 있지 않다.

<리스트 2> 가변 인자 관련 매크로 구현 소스

가변 인자를 조작하는 매크로의 핵심은 va_list다. 이 인자로부터 모든 가변 인자를 구할 수 있기 때문이다. 따라서 문제를 해결하는 가장 손쉬운 방법은 va_list를 인자로 받아들이는 내부 함수를 만드는 것이다. 이렇게 해결한 코드가 <리스트 3>에 나와 있다. coresum 함수는 원래 realsum 함수가 가진 역할을 옮겨놓은 함수다. 이제 realsum, mysum 함수는 coresum 함수를 호출해 동일한 기능을 할 수 있다.

<리스트 3> va_list를 이용한 가변 인자 전달

앞의 해답의 경우에는 realsum 코드를 수정했기 때문에 우리가 처음 의도했던 정답이라고 할 순 없다. 그렇다면 realsum의 코드를 하나도 수정하지 않고 인자를 전달할 방법은 없을까? 정상적인 방법으로 할 순 없지만 앞에서 살펴본 가변 인자의 동작 원리와 약간의 어셈블리 지식을 결합하면 만들 수 있다. <리스트 4>는 이러한 방법으로 가변 인자를 통째로 전달하는 방법을 보여준다.

<리스트 4> 어셈블리를 통해 가변 인자를 전달하는 함수

<리스트 4>의 코드는 가변 인자가 정수라는 가정을 하고 있다. 만약 printf 류의 함수 인자를 포워딩시켜야 한다면 조금 더 문제가 복잡해진다. 넘어온 문자열로부터 인자의 크기를 계산해야 하기 때문이다.

스폰서
글타래

  • 2주간 인기 글
  • 2주간 인기글이 없습니다.
Trackback Address :: http://jiniya.net/tt/trackback/599
  • Gravatar Image.
    stdcall | 2010/06/03 17:11 | PERMALINK | EDIT/DEL | REPLY

    4535번째 본 이글에 첫번째 댓글을 답니다. ^^;

    좋은글 보면서 한가지 의문점이 있습니다.

    http://www.jiniya.net/lecture/techbox/callconv.html의 정리를 보면
    __cdecl의 특징이 가변인자 지원, __stdcall은 Windows 표준 호출 규약..

    여기서 질문) __stdcall은 가변인자를 지원 하지 않나요?
    하겠죠? 그러니깐 StringCbPrintf의 경우 __stdcall 로 호출하니깐??

    오래된 글이지만 질문하나 띡 남기고 감니다.

  • Gravatar Image.
    nguri | 2012/07/11 00:50 | PERMALINK | EDIT/DEL | REPLY

    변수 명명이 좀 적절치 않은 것 같네요.
    argsize라는 변수명은 vargs_size 라는 이름으로 바꾸고 4라는 하드코딩된 값 대신 sizeof(int) 또는 sizeof(void*)가 대신 쓰였으면 더 나았을 것 같습니다. 태글이었습니다.. ㅎㅎ

    그리고 문제에서 조건으로 realsum의 코드를 건드리지 않는것만을 주문했기 때문에.. 저라면 이렇게 코를 풀겠습니다. ㅋㅋㅋㅋ

    __declspec(naked) int mysum(int count, ...)
    {
    __asm jmp realsum;
    }

    M$의 확장 구문을 쓰는 것은 잘못됐다고 우기셔도 __asm 부터가 벌써 확장이니 ㅡ.ㅡ 태글 걸지 못하실듯..
    조건에 'mysum의 프로토타입(인터페이스)를 건드리지 않고'라는 조건이 있었어야 했어요 ㅎㅎ

    웃자는 소립니다. 그나저나 저랑 생각이 비슷한 개발자를 만나뵙게 되서 반갑습니다. (한참 블로그의 글을 쭉 읽어보고 나서 느꼈습니다..)

Name
Password
Homepage
Secret