끄적끄적

반응형

1.1      void 포인터

포인터 타입은 원소 타입이 무엇이든 상관없이 프로그램의 메모리 주소를 갖고 있다는 공통점을 갖고 있습니다.  그리고, 프로그램의 메모리 주소가 특정 타입은 어떠한 메모리 주소를 갖어야 한다는 규제는 없습니다.  결국, 포인터 타입의 원소 타입이 있는 것은 개발자가 사용하기 용이하게 하기 위해서 있는 것이지 그 이상의 의미는 없습니다.  이번에 배울 void 포인터의 경우는 원소 타입을 명시하지 않고 선언하고 사용할 때 어떠한 포인터 타입도 받아서 사용할 수 있게 해 주는 포인터로 일반화 과정과 직접적으로 관련이 있습니다.  일반화에 대한 자세한 내용은 프로그래밍 과정에서 상세히 다루도록 하고 여기서는 기본적인 것에 대한 언급만 하도록 하겠습니다.

 

void foo()

{

   int a = 2;

   int *pi = &a;

   void *pv = 0;

 

   pv = pi;  //올바른 사용

   pi = pv;  //컴파일은 되지만 바르지 못한 사용(경고) 

   *pv = 3; //컴파일도 안되는 틀린 표현(에러) 

   pv++;  //컴파일도 안되는 틀린 표현(에러)

}

 

void 포인터는 원소 타입이 없다는 의미에서 void 포인터라 합니다.  이는 선언할 때에 원소타입을 명시하지 않고 해당 변수에 대입을 할 때 어떠한 포인터 타입도 받아서 처리할 목적으로 사용이 됩니다.  하지만 원소타입을 명시하지 않았다는 것으로 인해 주의해야 할 점들이 있습니다.  먼저, void 포인터에 다른 포인터 타입을 대입하는 것은 올바른 사용입니다.  하지만, 특정 포인터 타입의 변수에 void 포인터 타입의 변수를 대입하는 것은 바르지 못한 사용입니다.  현재 void 포인터변수에 어떠한 타입의 주소를 갖고 있는지는 명확하지 않기 때문에 잘못된 사용을 유발할 수가 있습니다.  물론, 개발자가 명확히 알고 사용한다고 한다면 큰 문제는 없겠지만 컴파일러에서는 타입이 다르다는 경고 메시지를 보냅니다.  이러한 경고 메시지를 무시하더라도 기계어로 번역은 됩니다.  하지만 앞으로 개발도구의 경고를 무시하고 사용하다보면 결국 개발자만 힘들어 질 뿐입니다.  명확하게 사용이 맞다라면 강제 형변환을 통해 경고를 제거하고 사용하시길 바랍니다.

 

pi = pv;  //컴파일은 되지만 바르지 못한 사용(경고)

pi = (int *)pv; //명확하게 사용이 맞다라면 강제 형변환을 통해 경고를 제거할 수 있습니다.

 

 또한, void 포인터는 원소 타입이 명시되지 않았기 때문에 간접 연산을 통해 해당 메모리에 값을 변경하거나 참조할 수가 없습니다.  char형 포인터 변수의 간접 연산은 해당 메모리 주소에서 1바이트를 얻어오는 연산이고 int형 포인터 변수의 간접 연산은 해당 메모리 주소에서 4바이트를 얻어오는 연산입니다.  즉, 포인터의 간접연산은 원소 타입이 무엇이냐에 따라 구체적인 연산 행위가 다르다는 것입니다.  결국, 원소타입이 명시되지 않은 void 포인터의 경우는 간접 연산을 할 수가 없습니다.(void **p는 void 포인터가 아닙니다.  이는 void 포인터에 대한 포인터 타입으로 void 포인터가 아닙니다.)  그렇다면, void 포인터를 통해 해당 메모리주소에 있는 값을 얻어오기 위해서는 어떻게 해야 할까요?  이때 사용할 수 있는 함수가 memory에 관련된 함수 memcpy가 있습니다.  물론, 개발자는 해당 void 포인터 타입의 변수에 실제 어떠한 타입의 포인터를  저장했는지(적어도 메모리 사이즈는) 알아야 합니다.  (void *memcpy(void *,const void *,size_t))

 

*pv = 3; //컴파일도 안되는 틀린 표현(에러) 

int val = 3;

...중략...

memcpy(pv,&val,sizeof(int));  // pv메모리 주소에 val변수가 할당된 주소에 있는 값을 4바이트 copy하시오.

 

마지막으로 void 포인터 변수의 경우 가감 연산도 불가능합니다.  포인터 변수의 가감연산에는 원소타입을 기준으로 하여 가감되기 때문에 원소타입이 명시되지 않은 void 포인터의 경우는 가감 연산이 불가능합니다.  이때 역시 개발자가 명확히 알고 있다면 강제형변환을 통해 연산이 가능합니다. 

 

pv++;  //컴파일도 안되는 틀린 표현(에러)

pv = (char *)pv + sizeof(int); //pv에 있는 값을 char *로 강제 형변환은 원하는 메모리 주소를 더함

 

 우리는 C언어에서 제공되는 라이브러리(특히 메모리에 관련된)를 사용할 때 함수 원형(signature)이 void포인터로 된 것을 발견하게 될 것입니다.  이를 사용하는 것에는 void 포인터에 대한 깊은 이해를 필요로 하지 않지만 우리가 일반화된 라이브러리 혹은 함수를 작성하게 된다면 void 포인터에 대한 명확한 이해가 필요합니다.  자세한 사항은 프로그래밍 과정에서 일반화 과정을 다루면서 설명하도록 하겠습니다.

 

1.2      함수 포인터

C언어로 작성된 프로그램의 흐름은 기본적으로 main함수에서 순차적으로 진행되게 되어 있습니다.  CPU에 있는 PC(프로그램 카운터)레지스터는 다음에 수행할 코드의 주소를 갖고 있는데 컴퓨터는 해당 구문을 얻어와 DC(디코더)를 통해 피연산자와 명령어를 구분하여 이들을 ALU(산술논리처리장치)를 통해 계산을 하여 결고를 얻고 나서 다시 PC값을 1증가합니다.  이를 반복하는 것이 기본적인 프로그램의 흐름입니다.  다만, 함수의 호출구문을 만날경우에는 해당 함수가 정의되어 있는 코드 메모리 주소를 PC레지스터에 Setting(물론, 이 작업전에 함수 호출문이 수행 후에 돌아와야 할 코드 메모리 주소를 스택 메모리에 PUSH하는 동작이 있습니다.)을 함으로써 호출한 함수의 코드가 수행되게 됩니다.  실제로 C언어의 함수명은 해당 함수의 코드가 정의된 코드 메모리주소를 지칭하는 포인터 상수입니다. 

 

 흠... 함수명이 포인터 상수로 취급이 된다면 원소타입은 무엇입니까?   ... ...

 

 함수명은 포인터 상수로 취급이 되며 포인터의 원소 타입은 입력 매개변수와 리턴타입 입니다. 

 

int fnAdd(int a, int b)

{

   return a+b;

}

int fnSub(int a, int b)

{

   return a-b;

}

 

void foo()

{

int (*fnP)(int ,int)=0; 

//리턴 타입이 int 이고 입력매개변수가 int,int인 포인터 타입의 변수 fnP를 선언하였다.

 

fnP = fnAdd;  //fnAdd와 fnP는 동일한 타입이므로 바른 표현

printf(“[%d]\n”,fnP(2,3));  //함수 포인터 타입의 변수는 함수 사용과 같은 표현을 사용할 수가 있다.

 

fnP = fnSub;  //fnSub와 fnP는 동일한 타입이므로 바른 표현

printf(“[%d]\n”,fnP(2,3));  //함수 포인터 타입의 변수는 함수 사용과 같은 표현을 사용할 수가 있다.

}

 

 위의 예처럼 함수 포인터 타입의 변수를 선언하고 같은 타입의 함수명을 대입을 하면 함수 포인터 타입의 변수를 마치 함수처럼 사용을 할 수가 있습니다.  실제 함수 호출문도 복잡한 연산자일 뿐이랍니다.

 

 

typedef int (*FNCAL)(int,int);

//리턴 타입이 int 이고 입력 매개변수가 int, int인 포인터 타입을 FNCAL이라 정의하였다.

 

extern int fnAdd(int a, int b);

extern int fnSub(int a, int b);

extern int fnMul(int a,int b);

extern int fnDiv(int a,int b);

 

FNCAL fnArr[4] = {fnAdd, fnSub, fnMul, fnDiv};

//FNCAL타입을 원소로 하고 원소의 개수가 4인 배열 fnArr을 선언

//fnArr의 초기값으로 fnAdd,  fnSub, fnMul, fnDiv를 대입 – 각 함수명은 FNCAL타입과 동일하다.

 

void foo()

{

int index = 0;

printf(“피연산자가 2와 3일 때 사칙연산을 하려고 합니다.\n”);

printf(“사용할 연산자를 선택하세요 [0]:덧셈 [1]:뺄셈 [2]:곱셈 [3]: 나눗셈.\n”);

 

scanf(“%d”,&index);

fflush(stdin);

 

if((index>=0) && (index<=3))

{

    printf(“연산 결과:[%d]\n”,fnArr[index](2,3));

}

}

반응형
Please Enable JavaScript!
Mohon Aktifkan Javascript![ Enable JavaScript ]