728x90

이번 장은 앞쪽 포인터부분 공부가 필수적입니다. 앞쪽 포인터부분을 이해했다면 이 장은 그냥 덤입니다. ㅡㅡ;;

1, void 포인터

모든 변수는 변수의 크기와 메모리  저장형식인 자료형이라는 것이 존재합니다. C언어에서 void는 '자료형이 없다', '자료형이 정해지지 않았다'라는 의미로 사용됩니다. 그렇다면 int n; 처럼 void v; 이 가능할까요? 당근 불가합니다. 다시말하지만(변수장에서 했습니다.) 자료형이란 변수의 연산, 크기, 저장형태을 나타낸다고 했습니다. void는 자료형이 없다는 의미이므로 변수를 만들 수 없습니다.

 

여러분 포인터의 크기는 얼마일까요? 모든 분들이 아실거라 믿습니다. 4byte입니다. 포인터란 무엇일까요? 주소를 저장하는 변수입니다. 

 

void 포이터란? 주소를 저장하는 포인터입니다. 음? 이게 다야? 네~ void 포인터는 자료형이 결정되지 않은(어떤 자료형의 주소든) 주소를 저장하는 포인터입니다. (malloc() 함수의 리턴 자료형이 void* 입니다. 함 생각해 보세요.)

 

void 포인터는 어떤 자료형의 주소든 모두모두 담을 수 있습니다.

void main( )
{
    void *pv;
    char c = 'A';
    int n = 10;
    double d = 3.14;

    pv = &c;
    pv = &n;
    pv = &d;
}

 이처럼 어떤 자료형의 주소든 저장할 수 있습니다. 어떤 자료형의 주소든 4byte를 넘지 않을 것이고 void* 형 pv는 단지 주소를 저장하는 포인터입니다. 그렇다면, 주소 pv로 할 수 있는 일은? 자료형이 없어서 아직 할 수 있는 일은 없습니다. 주소는 단지 주소일 뿐 어떤 데이터가 메모리공간에 저장돼 있는지 알 수 없으므로 의미가 없습니다.

 

왜? void 포인터가 의미 없는 주소인지 포인터를 복습해 보도록 할까요?

그림을 보며 설명드리도록 하겠습니다. (이해가 쉽지 않다면 포인터 장을 참고하세요)

void main( )
{
    int n = 10;
    int * pn = &n;

}

 

 

pn이 int* 이므로 *pn은 int형이 됩니다.

 

또, .....

void main( )
{
    int n = 10;
    int * pn = &n;

    int ** ppn = &pn;

}

 

 

 ppn이 int ** 형이므로 *pn은 int* 형이며 **ppn은 int형 정수가 됩니다.

 

이제 다시 void*를 볼까요?

void main( )
{
    void *pv;
    int n = 10;
    pv = &n;

*pv // 에러~~

}

 

 

 

*pv는 연산 에러(컴파일 에러)입니다. void*형에 *연산자를 사용할 수 없습니다.

 

가지고 있는 주소 메모리에 접근하려면 아래와 같이 주소 형변환 후 접근할 수 있습니다.

void main( )
{
    void *vp;
    char c = 'A';
    int n = 10;
    double d = 3.14;

    vp = &c;
    printf("%c\n", *(char*)vp );
    vp = &n;
    printf("%d\n", *(int*)vp );
    vp = &d;
    printf("%lf\n", *(double*)vp );
}
  1. A
    10
    3.140000

 vp를 각 자료형으로 변환한 후 *연산자로 메모리 접근합니다.

아래 그림을  참고하세요

 

 

보통 아래 예제처럼 사용합니다.

void main( )
{
    void *vp;
    int *pn;
    int n = 10;
   
    vp = &n;
    pn = (int*)vp;
    printf("%d\n", *pn );
}
  1. 10

pn변수에 대입 후 *pn으로 메모리 접근합니다. 위 예제는 바보 같습니다. 바로 pn을 사용하면 되는 데 복잡하게 사용하고 있으니까요. 하지만, 이것은 예제일 뿐이고 실전에서는 정당한 이 예제와 같이 사용해야 하는 정당한 이유가 있습니다. 지금은 패~스!

 

2, 함수 포인터

포인터는 주소를 저장하는 변수이므로 함수 포인터는 함수의 주소를 저장하는 포인터입니다.

어?? 어어?? 함수의 주소를 저장해? 함수도 주소가 있다는 말인가?

그렇습니다. 실제 함수를 호출하면 함수의 주소를 이용하여 호출합니다.

 

함수의 주소를 출력해 보도록 하겠습니다.

void func( )
{
}
void main ( ) {
    printf("main : %x\n", main);
    printf("func : %x\n", func);
    printf("printf : %x\n", printf);
}
  1. main : 411140
    func : 4111d1
    printf : 1025abb0

함수의 이름은 그 함수의 시작주소입니다. 배열의 이름도 그 배열이 시작하는 시작주소죠? 같습니다.

함수 포인터의 크기는 4byte입니다. 모든 포인터는 4byte입니다. 함수 포인터도 단지 주소를 저장하는 4byte 메모리 공간일 뿐입니다.

아래 그림을 참고하세요.

 

 

 

어떤 함수의 주소가 K라면 우리는 함수의 주소를 이용하여

  •  K( );
  •  함수주소( );

이렇게 함수를 호출하는 것입니다.

 

함수 포인터란 위와 같은 함수의 주소를 저장하는 포인터입니다.

int형 주소는 int형 포인터에

double형 주소는 double형 포인터에

함수형 주소는 함수형 포인터에 저장합니다.

 

함수 포인터 선언

함수의 원형만 알면 쉽게 포인터를 선언할 수 있습니다.

예로

void func( )
{
}

원형은 ?

void func( ); 입니다.

위 함수의 주소를 저장하는 포인터 변수 선언은 void (*pf)( );

 

void  func(int n )
{
}

원형은 ?

void func(int  ); 입니다. 

위 함수의 주소를 저장하는 포인터 변수 선언은 void (*pf)(int );

원형과 같고 변수에 (* ) 연산자를 붙이면 됩니다. 연산자가 없으면 함수 원형 선어이 되겠죠?

 

간단한  함수의 주소를 저장하는 예제입니다.

void func( )
{
    printf("함수 포인터 예제!\n");
}
void main ( )
{
    void (*pf)( );
    pf = func ;

    func( );
    pf( );
}
  1. 함수 포인터 예제!
    함수 포인터 예제!

 함수포인터 pf를 선언하고 함수의 주소(func)를 저장한 후 함수를 호출하고(pf() ) 있습니다.

 

아래와 같이 호출할 수도 있습니다.

void func( )
{
    printf("함수 포인터 예제!\n");
}
void main ( )
{
    void (*pf)( );
    pf = func ;

    func( );
    (*pf)( );
}
  1. 함수 포인터 예제!
    함수 포인터 예제!

 함수 포인터는 pf()와 (*pf)() 로 포인터가 가리키는 함수를 호출할 수 있습니다. pf는 함수의 주소인데 어떻게 (*pf)와 같나? 이렇게 의문이 생길 수 있지만 함수 포인터는 완벽히 동일합니다. pf는 함수의 주소이며 *pf도 함수의 주소입니다. 연산자 공부할 때 연산자는 위치와 사용 용도에 따라 연산자 의미가 달라진다고 했습니다.

pf와 *pf는 함수의 주소로 같다. 아래 예제를 보시죠!

void func( )
{
    printf("함수 포인터 예제!\n");
}
void main ( )
{
    void (*pf)( );
    pf = func ;

    printf("%x  %x  %x\n", func, pf, *pf);
}
  1. 4111d1   4111d1   4111d1

모두 같은 함수의 주소입니다.

 

하나의 함수 포인터로 서로다른 함수를 호출하는 예제입니다.

void func1(int n1, int n2)
{
    printf("함수 func1(  ) 인자 : %d, %d\n", n1, n2);
}
void func2(int n1, int n2)
{
    printf("함수 func2(  ) 인자 : %d, %d\n", n1, n2);
}
void func3(int n1, int n2)
{
    printf("함수 func3(  ) 인자 : %d, %d\n", n1, n2);
}
void main ( )
{
    void (*pf)(intint );
    pf = func1 ;
    pf( 10, 20);

    pf = func2 ;
    pf( 10, 20);

    pf = func3 ;
    pf( 10, 20);
}
  1. 함수 func1(  ) 인자 : 10, 20
    함수 func2(  ) 인자 : 10, 20
    함수 func3(  ) 인자 : 10, 20

 간단하죠? ㅡㅡ; 패~스!

 

마지막으로 함수 포인터를 사용하는 이유는 무엇일까요?

첫째, 서로 다른 함수에 대해 동일한 인터페이스를 제공한다. 같은 이름으로 서로다른 함수를 호출할 수 있다는 것입니다.

둘째, 실행시간에 알고리즘이나 기능을 변경하는 유연성을 제공한다.

셋째, 콜백(callback) 메커니즘을 제공한다. 등..


출처 : http://blog.daum.net/coolprogramming/76

728x90

+ Recent posts