brunch

매거진 Sinclair

You can make anything
by writing

C.S.Lewis

by Sinclair Feb 11. 2016

함수와 함수의 활용

그는 나에게로 와서 꽃이 되었다 III



함수를 호출하는 또 다른 방식


__cdecl, __stdcall, __fastcall, __pascal, etc  



이곳에서는 좀더 어렵고 깊이 있는 주제를 다룰 것입니다. 물론 지금까지도 충분히 어려웠겠지만 세상엔 이것 말고 더 힘든 일이 차고도 넘칩니다. 지금은 너무 어렵다고 생각된다면 그냥 "아~ 이런 게 있구나!" 하고 넘어가셔도 됩니다. 프로그래머가 되려면 반드시 공부 해야 할 컴퓨터 구조와 자료구조를 알고 있어야 이해하기 쉽기 때문입니다.


앞에서 우리는 함수를 호출하는 방식으로 인자를 전달하는 방식에 따라 달라지는 call by value와 call by reference를 공부했습니다. 일반적으로 프로그래밍을 할 때는 그것만 알아도 충분하지만 좀 더 깊이 들어가면 그것만으로는 부족한 것을 느끼게 됩니다.


함수를 호출할 때는 기본적으로 First Input Last Output의 데이터 입출력 방식으로 자료를 다루는 스택을 사용하게 되고 스택을 사용하는 방식에 따라 __cdecl, __pascal, __stdcall, __fastcall 등이 존재합니다.


이것 말고도 다른 것들이 몇 가지 더 존재하지만 C++과 관련이 있거나 현재는 사용하지 않는 것이므로 따로 설명하지 않겠습니다.  


우선 정리하자면 첫 번째는 함수의 인자들을 스택에 넣는 방향에 따라 나누게 되며, 두 번째는 호출된 함수를 종료할 때에 그 함수가 사용한 스택을 호출한 함수가 정리할 것 이냐 아니면 호출당한 함수가 정리할 것이냐의 차이입니다. 아래와 같이 다시 정리할 수가 있습니다.                                          




__stdcall은 __pascal방식과 __cdecl방식의 혼합이며 MS 윈도우즈 프로그래밍의 기본 방식이라고 합니다. 그리고 __fastcall방식은 레지스터를 사용하는 __stdcall 방식이라고 생각하면 됩니다.


각 함수의 호출방식의 지정은 강제적이거나 절대적이지 않고 단지 컴파일러에게 요청하는 수준입니다.


예를 들어 프로그래머가 가변인자를 사용하는 함수를 __stdcall 방식으로 지정하여 작성했다면 컴파일러는 자동으로 __cdecl방식으로 바꾸게 됩니다.



/*

* copyleft (l) 2006 - 2017 programmed by Sinclair

*/

// prototypes

int __stdcall add_S(int i , int j) ;

int __cdecl add_C(int i , int j) ;

//int __pascal add_P(int i , int j) ; // 현재는 사용 안 함

int __fastcall add_F(int i , int j) ;


/*

; 함수 이름을 붙이는 방식도 차이가 있습니다.

PUBLIC    _add_S@8        ; _함수이름@인자크기

PUBLIC    _add_C        ; _함수이름

PUBLIC    @add_F@8        ; @함수이름@인자크기

*/


main() {

    add_S(1,2) ;

    add_C(3,4) ;

//    add_P(5,6) ;

    add_F(7,8) ;

/*

; 9 :     add_S(1,2) ;

    push    2

    push    1

    call    _add_S@8

; 10 :     add_C(3,4) ;

    push    4

    push    3

    call    _add_C

    add    esp, 8         ; <- 이 부분이 스택을 정리

                            ; 함수가 호출 될 때마다 이 코드가 추가

; 12 :     add_F(7,8) ;

    mov    edx, 8        ; 레지스터 사용

    mov    ecx, 7        ; 레지스터 사용

    call    @add_F@8

*/

    return 0 ;

} // end main()


int __stdcall add_S(int i , int j) {

    int k ;

    k = i + j ;

    return k ;

} // end add_S()


/*

    pop    edi

    pop    esi

    pop    ebx

    mov    esp, ebp

    pop    ebp

    ret    8             ; <- 이 부분이 스택을 정리

*/


int __cdecl add_C(int i , int j) {

    int k ;

    k = i + j ;

    return k ;
 

} // end add_C()


/*

    pop    edi

    pop    esi

    pop    ebx

    mov    esp, ebp

    pop    ebp

    ret    0

*/


/* // 사용 안 함

int __pascal add_P(int i , int j) {

    int k ;

    k = i + j ;

    return k ;

} // end add_P()

*/ 


int __fastcall add_F(int i , int j) {

    int k ;

    k = i + j ;

    return k ;

} // end add_F()


/*

    pop    edi

    pop    esi

    pop    ebx

    mov    esp, ebp

    pop    ebp

    ret    0

*/




C의 함수들이 오른쪽에서부터 인자를 넣는 방식을 확인하려면 아래의 코드를 실행하면 됩니다.


    int i = 11 ;

    printf("%d %d %d %d\n" , i , ++i , i++ , i ) ;

    // 예상: 11 12 12 13

    // 결과: 13 13 11 11

    i = 11 ;

    printf("%d %d %d %d\n" , i , i++ , ++i , i ) ;

    // 예상: 11 11 13 13

    // 결과: 13 12 12 11




__pascal 방식은 Java나 Pascal에서 간단하게 확인할 수 있습니다.


// PascalCallTest.java

public class PascalCallTest

{

    public static void main(String[] args)

    {

        int i = 11 ;

        test(i , ++i , i++ , i);

        i = 11 ;

        test(i , i++ , ++i , i);

    } // end main()

    private static void test(int i , int j , int k , int l)

    {

        System.out.println(" " + i + " " + j + " " + k + " " + l);

    } // end test()

} // end PascalCallTest class



결과

11 12 12 13

11 11 13 13




여기서 무엇보다 __stdcall과 __cdecl 방식의 가장 큰 차이는 __stdcall 방식이 보다 작은 사이즈의 코드를 만들어 낸다는 점입니다. __cdecl 방식의 경우 스택을 정리하는 코드가 호출할 때 마다 반복해서 따라 들어가야 하는 반면 __stdcall 방식은 함수 안에 이미 포함되어 있기 때문에 그냥 부르기만 하면 됩니다. 하지만 가변인자처럼 부를 때마다 인자의 개수가 바뀌는 경우는 몇 개의 인자를 전달하였는지 호출한 함수가 확실히 알 수 있기 때문에 호출한 쪽에서 스택을 정리하는 __cdecl 방식이 유일한 해결책이 됩니다. 가변인자 처리에 대한 부분은 Advanced Pointer를 다루는 글타래에서 자세히 이야기 할 것입니다.

 















제가 어렸을 적에 읽었던 동화 중에 "코끼리를 먹는 신랑" 이란 게 있었습니다.

누구 작품인지는 기억나지 않지만… 그 내용이 아직도 생생하게 기억 나는걸 보면 어린 나이였음에도 제게 많은 영향을 끼쳤던 거 같습니다.

내용인즉슨…

어느 미모의 아리따운 아가씨가 우리나라 공항에 내렸답니다. 제가 어릴 당시만해도 외국인이 요즘처럼 많이   없었으니까요. (흑흑흑… 너무 오래 살았어…) 암튼 기자들이 몰려가서 사진기를 들이밀며 한국을 방문하는 이유를 물었답니다. 그 예쁜 아가씨 왈… 자기 아부지가 자신의 신랑감을 고르는데 조건이 너무 까다로워서 전세계를 돌며 신랑감을 찾고 있다고 말했겠죠. 그래서 기자들이 그 조건이 뭐냐구 물었고 아가씨는 조건은 단 한가지 코끼리를 먹어 치워야 한다고 했습니다. 그 말을 듣고 내심 기대에 부풀었던 사람들이 입을 모아…

"에이 코끼리? 코끼리어떻게 먹어? 다  먹어 치우기 전에 내가 먼저 죽겠네..."

하며 돌아섰죠. 아가씨도 한숨을 내쉬었답니다. 세상에 코끼리를 먹어야 하다니… 시집을 안 보내겠다는 것인지…  

그렇게 한국의 방방곡곡을 떠돌던 아가씨.. 어느 시골길을 걷게 되었답니다. 멀리서 어린 소년이 자기보다 덩치가 훨씬 큰 소를 몰고 오는 걸 보고는 호기심 반 장난 반으로 물었겠죠.. "얘 꼬마야, 저기 너 있잖아… 코끼릴 먹어 치울 수 있어?"

그랬더니 그 소년 가라사대… "음… 그럼요…" 하지 않겠어요? 크크크~

아가씨는 눈이 동그래져서 "아니 그 큰걸 어떻게? 니가 먹히겠다." 라고 반문했죠..

"음… 그거야 조금씩 조금씩 먹다 보면 다 먹을 수 있지 않겠어요? 한번에 다 먹어야 하는 건 아니잖아요?"

우와 드디어 신랑감을 찾긴 했지만 너무 어리잖아요. 하하하하~ 우여곡절 끝에 출국 날이 되었고 구름 같이 기자들이 다시 몰려 들었죠.

"신랑감은 찾으셨나요?"

아가씨 얼굴이 환해지면서 말했답니다.

"그럼요… 너무 멋진 신랑감을 찾았는데 그분이 너무 어려서 그냥 돌아갑니다."

하하하~ 너무 허무하죠? 뭐 다른 내용이 더 있을 듯 한데 기억에 남는 부분은 이것뿐 입니다. 어쨌든 그냥 그렇게 허무하게 끝나는 내용이었답니다…


왜 이런 얘길 하냐구요?

제가 살아보니 때론 우리 앞에 닥쳐 있는 문제들이 코끼리보다 크게 다가올 때가 너무 많더라구요. 때론 C언어가 우릴 무겁게 짓누를 때도 있죠…

하지만 조금씩 먹어 치우다 보면 코끼리쯤은 거뜬히 먹어 치울 수 있는 것처럼.. 차근차근 문제를 분석하며 하나씩 해결해 나가다 보면 언젠가는 멋진 프로그래머가 되어있을 겁니다. 저도 이렇게 프로그래머가 되었는걸요.


아~ 제가 고등학교 때 문과였었단 얘기는 했죠?

첨에 프로그램 짤 때… 아주 죽을 뻔했는데, 그 뒤론 하루에 프로그램 하나씩 짜야겠단 맘을 굳게 먹고 거의 일년간을 학교 전산실에서 살았더랬죠. 첨엔 얼마나 악명이 높았던지… 제가 만지고 나면 컴터가 늘 고장이 나는 거 있죠? 선배들한테는 제가 안 그랬다고 막 우기고 그랬는데. 게다가 제가 늦게까지 집에 안가고 버티니 항상 눈에 가시였겠죠.

그렇게 일년을 버텼더니 조금씩 할만 해지더라구요. 아주 조금이요.

아세요? 밤새워 프로그램을 만들어 갈 때 새벽녘 동이 터오는 아침을 맞이하면서 제대로 돌아가는 프로그램을 바라보는 그 멋진 느낌을…

다들 열심히 하시니까… 좋은 열매 맺으실 겁니다. 더운 여름날 뜨거운 햇살과 지루한 장마가 빛나는 가을 고운 열매를 만들어 내는 자연의 이치는 프로그래밍에서도 그대로 적용된답니다.

기억하세요… 세상에 고수들은 많지만 정말 멋진 프로그래머, 좋은 프로그래머는 드물답니다. 꼭 멋진 프로그래머 좋은 프로그래머가 되세요.



자 아자 아자!

저겨…  근데 코끼리를 냉장고에 어떻게 넣죠?








C언어 및 기타 프로그래밍 관련 질문은 오픈 카톡으로

group talk - https://is.gd/yourc

1:1 talk - https://is.gd/aboutc

#Sinclair #씽클레어 #싱클레어 #씽클레어도씨 #씨언어 #씨프로그래밍  #C언어 #Cprogramming #C_Programming #C #Programming #Clanguage #C_Language

매거진의 이전글 배열과 포인터: X-files 배열
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari