함수 포인터

Programming/C 2013. 2. 15. 02:44 |

함수 포인터를 매개변수로 선언하는 방법.






함수의 이름이 제공하는 두 가지


ⓐ 정의된 함수의 호출.

ⓑ 함수가 위치하고 있는 주소 값의 확인.


함수의 이름은 해당 함수가 저장되어 있는 메모리의 주소 값을 의미한다. 

그리고 이러한 메모리의 주소 값을 저장하기 위한 포인터 변수를 가리켜 "함수 포인터 변수"라 한다.


int FuncName(int arg) 라는 함수가 정의되어있다고 가정하면 

int (*fctPtr)(int) = FuncName; 라는 함수 포인터를 생성해서 FuncName 함수를 가리킬 수 있고 호출 할 수 있다.


함수 포인터를 선언할 때 반환형과 매개변수 형을 일치시켜줘야 하는 이유는, 함수 포인터를 이용해서 해당 함수의 호출을 가능하도록 하기 위함이다. (함수 포인터에 반환형 정보와 매개변수 형 정보가 존재해야 가리키는 함수를 호출할 수 있다.)


'Programming > C' 카테고리의 다른 글

atexit 함수, exit 함수, abort 함수  (2) 2013.02.15
함수 포인터를 반환하는 함수의 정의  (0) 2013.02.15
메모리 컨트롤 함수. memmove, memcpy  (0) 2013.02.15
volatile 과 restict  (0) 2013.02.15
const 키워드  (0) 2013.02.15
Posted by scii
:

memmove : 어떠한 경우에도 사용할 수 있는 메모리 복사 함수.

: momory와 move의 합성어이다. 메모리에 저장된 데이터를 다른 영역으로 이동하는 함수이다. (여기서 말하는 이동은 복사를 의미)


#include<string.h>

void* memmove(void* dest, const void*, size_t len);

매개변수 dest로 전달된 주소 값이 그대로 반환.


매개변수 src로 전달된 값을 시작주소로 하여 len 바이트를 읽어 들여서, 매개변수 dest로 전달된 주소에 복사.



 주의해야 할 사항: 복사의 대상이 되는 메모리 공간의 크기가 복사할 바이트의 수보다 작다면 문제가 발생하기 때문에 복사의 대상이 되는 메모리 공간의 크기가 작게 할당되는 일이 없어야 한다.



memcpy : 제한된 상황에서의 메모리 복사. memmove보다 빠르다.


:memory와 copy의 합성어이다. 역시 메모리에 저장된 데이터를 다른 영역으로 복사하는 기능을 제공한다.

#include <string.h>

void* memcpy(void* restrict dest, const void* restrict src, size_t len);

매개변수 dest로 전달된 주소 값이 그대로 반환된다.


memmove 함수와의 차이점 : 위 함수의 매개변수 선언을 보면 dest와 src가 restirct로 선언되었음을 알 수 있다. 따라서 함수가 호출되면서 dest와 src로 전달된 주소 값의 메모리는 각각 dest와 src로만 접근이 가능해야한다.


 복사할 때 메모리가 겹치는 경우, memcpy 함수를 사용해서 원하는 결과를 얻지 못한다. 왜냐하면 restrict로 선언된 포인터가 가리키는 메모리 영역은 해당 포인터로만 접근이 허용되기 때문이다. (복사할 대상에 다른 포인터가 가리키고 있기 때문에.)

이럴때는 memmove 함수를 사용해야 원하는 결과를 얻을 수 있다.

반면에 겹치지 않는 상황이라면, memcpy 함수를 사용 함으로서 성능의 향상을 기대할 수 있다.



※ memcpy가 memmove보다 빠른 이유


: 복사 원본과 복사 대상이 겹치는 경우의 복사는 겹치지 않는 경우의 복사보다 시간과 메모리를 더 많이 요구한다. 뿐만 아니라 복사 원본과 복사 대상이 겹치는지 겹치지 않는지를 확인하는 것도 부담되는 일이다. 이처럼 memmove 함수는 다양한 부담을 안고 동작하도록 디자인 되어있다.

반면 복사 원본과 복사 대상이 겹치지 않는다는 확신이 있다면, 지금 말한 함수의 부담을 덜어 줄 수 있다. 때문에 그만큼 고속으로 복사가 이뤄지도록 함수의 구현이 가능하다.


'Programming > C' 카테고리의 다른 글

함수 포인터를 반환하는 함수의 정의  (0) 2013.02.15
함수 포인터  (0) 2013.02.15
volatile 과 restict  (0) 2013.02.15
const 키워드  (0) 2013.02.15
힙에 할당된 메모리의 확장 시 호출하는 realloc 함수.  (0) 2013.02.15
Posted by scii
:

volatile 과 restict

Programming/C 2013. 2. 15. 02:40 |

volatile : 최적화를 수행하지 마세요.


volatile int num;

volatile int* ptr;

이렇게 변수 num을 volatile로 선언하면, 이는 컴파일러에게 다음과 같은 메시지를 전달하는 효과가 있다.

"변수 num에 저장된 값은 순간적으로 (일시적으로) 다른 영역으로부터 참조될 수 있습니다. 그러니 코드 최적화를 수행하면 안되요"


num+=10;

num+=20;

num+=30;

이와 같은 코드가 있다고 가정할 때 컴파일러는 변수 num에 한번에 60을 넣는다. 

이런 식으로 변함.

num+=60;


이는 컴파일러의 코드 최적화(code optimization) 기능에 따른 것이다.


하지만, 이런 코드 최적화가 문제가 되는 상황이 존재한다. 다른 영역의 프로그램 또는 하드웨어를 통해서 변수 num에 저장된 값이 참조되는 경우이다.

즉, 변수 num에 저장된 값이 10 증가되고 나서 20이 증가되기 이전에 순간적으로 다른 영역으로부터 값이 참조될 수 있다.  이러한 경우에는 코드 최적화가 치명적인 문제가 된다. 

그래서 volatile 을 써서 변수 num과 관련해서는 코드 최적화를 수행하면 안된다고 컴파일러에게 알려주어야 한다.

volatile int num = 20; 이런식으로 선언하면 된다.


※ 참고로 이렇게 volatile로 선언된 변수 값의 변경은 항상 메인 메모리(RAM)에 반영이 된다. volatile 선언이 없는 변수는 성능의 향상을 위해서 값의 변경이 캐쉬 메모리(또는 레지스터)에만 반영되었다가, 뒤늦게 메인 메모리에 반영되기도 한다.

그리고 volatile로 선언된 변수의 값은 참조될 때에도 캐쉬가 아닌 메인 메모리로부터 참조가 이뤄진다.


정리하면 volatile로 선언된 변수는 캐쉬 메모리를 거치지 않는다. 다른 영역으로부터 언제 어떠한 형태로 값의 참조가 이뤄질지 모르기 때문에 값의 변경이 실시간으로 메인 메모리에 반영이 된다.



○ 포인터 변수 선언에서의 volatile 선언


: 포인터가 다른 영역에서  참조될 수 있음을 의미하는 것이 아니라, 포인터가 가리키는 메모리 공간이 다른 영역으로부터 참조될 수 있음을 의미하는 것이다. 따라서 volatile로 선언된 포인터 변수의 *연산은 코드 최적화의 대상에서 제외가 된다. 




restrict : 원하는 대로 마음껏 최적화를 수행.


restrict는 volatile과 반대의 의미를 지니고 있다. volatile이 코드 최적화에 제한을 거는 선언이라면, restirct는 코드 최적화를 유도하는 선언이다. 그리고 restirct 선언은 포인터에만 선언할 수 있다.

int* restirct ptr;

이 포인터가 가리키는 메모리 공간은 이 포인터만으로 접근이 가능한 영역이다 라고 컴파일러에게 알려준다.


int main(void)

{

      int num = 10;

      int* ptr = &num;

}

이 때 포인터 ptr을 restrict로 선언하는 것은 적절치 않다. 왜냐하면 포인터 ptr을 이용해서 접근 가능한 메모리 공간은 num이라는 변수 이름으로도 접근이 가능하기 때문이다. 


int main(void)

{

      int* restirct ptr = (int*)malloc(sizeof(int));

}

위와 같이 선언된 포인터 ptr은 restrict로 선언하기에 적절하다. 왜냐하면 malloc 함수를 통해서 할당된 메모리 공간은 포인터 ptr을 통해서만 접근이 가능하기 때문이다.


이렇게 포인터가 restrct로 선언되면, 해당 포인터가 가리키는 메모리 영역의 접근 연산은 캐쉬 메모리를 기반으로 최고의 성능을 낼 수 있도록 코드가 최적화된다.


Posted by scii
:

const 키워드

Programming/C 2013. 2. 15. 02:38 |

int num = 5;

int* ptr = &num;


포인터 ptr의 선언에서 const가 들어갈 수 있는 위치.

const int* const ptr = &num;


포인터 선언 앞 부분에 const가 삽입될 수도 있고, 포인터 변수의 이름 앞에 const가 삽입될 수도 있다.

이 둘은 다른 의미를 지닌다.

필요하다면, 두 군데 모두에 const를 삽입 할 수도 있다. 


ⓐ 포인터 앞에 있는 const의 의미.

 int num = 5;

 const int* ptr = &num;


이렇게 선언이 되면 포인터 ptr은 다음과 같이 가리키는 대상에 대한 값의 변경이 허용되지 않는다.

*ptr = 20; //컴파일 에러 발생.




즉, const 선언이 앞에 있을 경우 포인터를 이용한 값의 변경을 허용하지 않겠다는 뜻이 된다.

※ 포인터를 이용한 값의 변경만을 허용하지 않을 뿐이다. 변수가 상수가 되는 건 아님.


한마디로, 포인터의 선언 앞에 const가 붙으면, 이 포인터를 이용해서는 값의 참조만 가능할 뿐 변경은 불가능!




ⓑ 변수 이름 앞에 있는 const의 의미.

int num = 10;

int* const ptr = &num;


이 선언은 포인터 변수 ptr을 상수화시킨다는 의미이다. 즉 포인터 변수 ptr에 저장된 값의 변경이 불가능해지는 것이다. 포인터 ptr은 끝까지 변수 num만 봐라보며 가리킨다. 해바라기




포인터와 관련된 두가지 형태의 const 선언은 각각 별개의 선언이다. 서로 별개의 선언이기 때문에 다음과 같이 선언하는 것도 가능하다.


int num = 10;

const int* const ptr = &num;

이렇게 선언된 포인터 변수 ptr은 두 가지 제약사항이 동시에 생기게 된다.


ⓐ 포인터 ptr을 이용해서는 ptr이 가리키는 변수의 저장된 값을 변경할 수 없다.

ⓑ 포인터 ptr은 끝까지 변수 num만 가리켜야 한다. ptr상수화


Posted by scii
:

한번 할당된 메모리 공간은 그 크기를 확장할 수 없다. 이는 모든 영역의 메모리 공간에 해당하는 말이다. 

하지만, 그 영역이 힙이라면, 그리고 realloc 함수를 사용한다면 이러한 일이 가능해진다.


realloc 함수 원형

#include <stdlib.h>

void* realloc(void* ptr, size_t size);

- 성공 시 새로 할당된 메모리의 주소 값, 실패 시 NULL 반환.


첫 번째 전달인자로 확장하고자 하는 힙 메모리의 시작 주소 값을 전달한다.

두 번째 전달인자로 확장하고자 하는 메모리의 전체 크기를 전달한다.


※ realloc 함수는 필요에 따라서 요구하는 크기의 메모리 공간을 별도로 할당하여, 기존 메모리 공간에 저장된 값을 복사하기도 한다. 때문에 메모리의 주소 값을 반환하는 것이다. 


다음 예제는 이 사실을 증명해 보이고 있다.




calloc 함수로 할당된, 그리고 realloc 함수로 확장된 메모리의 주소 값이 다르다. 이를 통해서 realloc 함수가 별도의 메모리 공간을 할당했다는 사실을 알 수 있다.

그리고, realloc 함수가 기존 메모리 공간에 저장된 값을 복사한다는 사실을 입증한다.


※ realloc 함수는 메모리 공간을 축소하는 역할로도 사용할 수 있다.


: 필요하다면 메모리 공간을 축소하는 용도로도 사용이 가능하다. realloc 함수 호출 시 두 번째 인자로 전달하는 메모리 공간의 크기 정보를 기존 메모리 공간의 크기보다 작은 값으로 전달하면, 메모리 공간은 그 크기에 맞춰서 축소가 된다.

그리고 이때에는 새로이 메모리 공간을 할당하지 않고 기존에 할당된 메모리 공간의 크기를 줄이기만 한다.

'Programming > C' 카테고리의 다른 글

volatile 과 restict  (0) 2013.02.15
const 키워드  (0) 2013.02.15
typedef 를 이용한 이차원 배열 주소 값 반환  (0) 2013.02.15
Bit Mask (비트 마스크)  (0) 2013.02.15
비트 쉬프트 연산자  (0) 2013.02.15
Posted by scii
:

가로 세로의 길이가 각각 3인 int형 2차원 배열의 주소 값을 두 개 전달 받아서, 배열에 저장된 값의 총 합이 더 큰 배열의 주소 값을 반환하는 함수를 정의하고, main함수에서는 반환된 주소 값을 이용하여 총 합이 더 큰 배열에 저장된 모든 정수들을 출력하는 프로그램.


이 프로그램의 중요한 점!!


: 함수의 리턴자료형을 2차원 배열의 주소 값에 해당하는 반환형을 정의하는 것은 불가능하다.

예를 들어, int (*ptr)[3] Compare_Num(int**, int**);


그래서 typedef 선언을 통해서 배열의 이름에 해당하는 포인터 형을 선언하면, 이를 함수의 반환형에 사용할 수 있다.




모든 프로젝트에서는 typedef로 자료형을 정의하는 것이 일반적이다.

왜냐하면, 만약 어느 컴파일러에서 int형 자료형이 2바이트로 표현이 된다면 모든 소스코드들을 찾아서 4바이트 자료형으로 바꿔주는 작업을 해야 한다.

하지만, typedef로 자료형을 선언해주고 그 자료형으로 대체해서 소스코드를 작성한다면 문제가 있을 시 typedef 정의만 바꾸어 주면 된다.

그래서 대부분의 프로젝트에서는 typedef 선언으로 자료형과 포인터 형을 정의해서 프로젝트를 진행한다.


※ int나 double과 같은 기본 자료형의 이름을 사용하지 않는다는 뜻은 아니다. 어느 시스템에서나 그 크기가 동일해야 하는 변수를 선언할 경우에 typedef를 유용하게 사용할 수 있다는 뜻이다.


'Programming > C' 카테고리의 다른 글

const 키워드  (0) 2013.02.15
힙에 할당된 메모리의 확장 시 호출하는 realloc 함수.  (0) 2013.02.15
Bit Mask (비트 마스크)  (0) 2013.02.15
비트 쉬프트 연산자  (0) 2013.02.15
printf 함수의 서식문자 조합.  (0) 2013.02.15
Posted by scii
:

비트 마스크 활용의 예2


비트 마스크를 추가해서 다시 한번 짜본 비트 마스크 활용 예.


#include<stdio.h>


#define ADD 1                //00000000 00000001   //status를 씌우기 위한

#define MIN 1<<1           //00000000 00000010   //비트 마스크들.

#define MUL 1<<2          //00000000 00000100

#define DIV 1<<3            //00000000 00001000

#define KIND 1<<4          //00000000 00010000


int status;


void Set_Status(int);

void Show_Result(int, int);


int main(void)

{

      int n1, n2;


       fputs("연산할 정수 2개 입력: ", stdout);

       scanf("%d %d", &n1, &n2);


       Set_Status(ADD | MIN | MUL | DIV | KIND);

       Show_Result(n1, n2);


       Set_Status(MUL | DIV);     //KIND, MUL, DIV 설정 안함.

       Show_Result(n1, n2);


       return 0;

}


void Set_Status(int ste)

{

       if(KIND & ste)     //KIND가 있으면

             puts("\n구어체로 설정되었습니다.\n");


       else

             puts("\n수식으로 설정되었습니다.\n");


      status = ste;         //전역변수 status에 ste의 값을 씌움.

}                               //MUL | DIV를 인자로 받으면 00001100 이 됨.


void Show_Result(int n1, int n2)

{

       if(status & KIND)     //KIND있으면 구어체로

       {

            if(status & ADD)

                   printf("%d 더하기 %d 의 값: %d \n", n1, n2, n1+n2);


             if(status & MIN)

                   printf("%d 빼  기 %d 의 값: %d \n", n1, n2, n1-n2);


             if(status & MUL)

                   printf("%d 곱하기 %d 의 값: %d \n", n1, n2, n1*n2);


             if(status & DIV)

                  printf("%d 나누기 %d 의 값: %d \n", n1, n2, n1/n2);

       }


       else         //KIND없으면 수식으로

       {

            if(status & ADD)

                   printf("%d + %d = %d \n", n1, n2, n1+n2);

             if(status & MIN)

                   printf("%d - %d = %d \n", n1, n2, n1-n2);

             if(status & MUL)

                   printf("%d * %d = %d \n", n1, n2, n1*n2);

             if(status & DIV)

                  printf("%d / %d = %d \n", n1, n2, n1/n2);

       }

}




한 번의 함수 호출로 둘 이상의 특성을 설정할 수 있어서 Show_Result 함수의 동작방식을 지정하기가 수월하다.

이는 Show_Result 함수의 실행에 영향을 미치는 설정 정보가 열 개가 되건, 스무 개가 되건, 설정 정보를 저장할 변수 하나와 정보를 변경하는 기능의 함수 하나만 있으면 된다는 사실을 의미한다. (다만, 비트 마스크의 개수가 늘어 날 뿐이다.)


※ "설정해야 할 정보가 많이 필요한 상황에서는 비트 마스크 방식을 선택하자."


비트 마스크를 가지고 소스코드를 작성 안했다면 너무 난잡해 졌을 것이다. 총 다섯개의 변수를 선언해야 하고, 다섯개의 함수도 정의해야 한다.


그런데, 비트 마스크를 활용함으로써 코드가 가독성있고 깔끔하게 작성됐다.

또한, 위의 장점에 비하면 미미한 장점이지만 메모리 공간도 절약되었다.


비트 연산자와 비트 마스크를 잘만 활용하면 정말 편리하겠는데~!! 애용해야겠다.


----------------------------------------------------------------------------------



int 형 데이터는 32비트를 차지한다. int 형 데이터의 하위 16비트 형태를 출력하는 함수를 비트마스크를 이용하여 작성.



비트 마스크는 변수 또는 수식의 원하는 비트를 추출하기 위해 사용하며 특정 한 비트만을 1로 가지도록 사용된다.
예를 들면, int형 자료의 오른쪽에서부터 5번째 비트가 0인지 1인지를 알아보기 위한 비트 마스크는 16진수 상수 0x10이다. 
위에서는 우측에서 16번째 비트만이 1인 값 즉, 0x8000을 bitmask의 초기값으로 사용하고 있다. 따라서 비트별로 논리곱을 구하는 연산자 &를 이용할 수 있다.


마스크 사용 비트 처리

#define BIT0  01
#define BIT1  02
#define BIT2  04
int flg:
   flg |= BIT0;                  //(1)
   if((flg & BIT1) == 0)       //(2)

(1)은 변수 flg 내의 비트들에 대하여 BIT0에 대응하는 1비트 필드를 1로 만든다. 

(2)의 if문은 변수 flg 내의 BIT1에 대응하는 1비트의 필드가 0인지 아닌지를 평가하는 문장이다.

Posted by scii
:


비트 연산자는 정수일때만 해당사항!!! 실수 안됨.


<< 연산자 : 비트를 왼쪽으로 이동. (( <<쪽으로 이동할수록 값의 크기가 2배씩 증가.))

>> 연산자 : 비트를 오른쪽으로 이동. (( >>쪽으로 이동할수록 값의 크기가 2배씩 감소. 단, 홀수이면 나머지값 버려짐.))


 unsigned 형으로 표현되는 데이터는 >> 연산 시 왼쪽의 빈 공간을 무조건 0으로 채운다.

예를들어 unsigned int 형 변수에 저장된 값의 경우, 부호를 결정짓는 MSB가 존재하지 않는다. 따라서

가장 왼쪽에 존재하는 비트가 1로 채워지는 경우는 어떠한 환경을 불문하고 발생하지 않는다.



음의 정수를 양의 정수로 바꿔주는 프로그램. Not비트 연산자 이용.

2의 보수를 취하여 음수를 양수로 만들어준다.




------------------------------------------------------------------------------------------------------------------------------------------------------



정수를 입력받아 그것을 비트열로 출력하는 프로그램.



bits-1-i 에서 -1은  >>로 밀었을 때 num이 가지고 있는 첫번째  비트를 연산하게 끔 하기 위하여 -1을 하였다.

그리고 -i는 차츰 차츰 >> 연산자의 수를 줄여나가면서 비트연산을 하게끔 한다.

'Programming > C' 카테고리의 다른 글

typedef 를 이용한 이차원 배열 주소 값 반환  (0) 2013.02.15
Bit Mask (비트 마스크)  (0) 2013.02.15
printf 함수의 서식문자 조합.  (0) 2013.02.15
isalpha 함수  (0) 2013.02.15
isdigit함수  (0) 2013.02.15
Posted by scii
: