void 형 포인터
선언 :
void *vp; |
이렇게 선언하면 vp포인터 변수의 대상체는 void형이 되며 이는 곧 대상체가 정해져 있지 않다는 뜻이다.
void 형은 함수와 포인터 변수에게만 적용되는 타입으로 일반 변수에는 쓸 수 없다.
void 형 포인터의 특징
1. 임의의 대상체를 가리킬 수 있다.
void형 포인터는 어떠한 대상체라도 가리킬 수 있다.
vp = pi; vp = pd; |
좌변이 void형 포인터일 때는 우변에 임의의 포인터형이 모두 올 수 있다.
반대로 임의의 포인터에 void형 포인터를 대입할 떄는 반드시 캐스팅을 해야한다.
캐스팅 ex)
pi = (int * ) vp; pd = (double *)vp; |
2. *연산자를 쓸 수 없다.
void형 포인터는 임의의 대상체에 대해 번지값만을 저장하며 어떤 값이 들어 있는지는 알지 못한다.
{ int i = 1234; void *vp;
vp = &i; printf("%d\n","*vp); } |
result)
vp는 대상체가 정수형 변수라는 것을 모르기 때문에 *vp로 이 번지에 들어있는 값을 읽을 수는 없다.
description)
vp가 정수형이라는 것을 캐스팅을 통해 printf 문을 수정해야한다.
캐스트 연산자 : vp를 잠시 정수형 포인터로 바꾼다.
printf("%d\n",*(int *)vp);
3. 증감연산자를 쓸 수 없다.
+ 1 이 몇 바이트 뒤인지를 결정하지 못하기 떄문이다.
void 형 포인터의 활용
void 형은 함수를 정의할 때 유용하게 사용된다.
함수 예시
void *memset(void*s, int c, size_t n); |
첫 번째 인수 s 가 void형 포인터로 되어있기 때문에 정수형 , 실수형 , 문자열을 구분하지 않고 모두 인수로 받아들일 수 있다.
void형이 없다면 memsetint , memsetchar , memsetdouble 같은 함수를 따로 만들어야 할 것이다.
NULL 포인터
NULL 포인터는 0 으로 정의되어 있는 포인터 상수값이다.
0이라는 상수보다는 좀 더 쉽게 구분되고 의미를 명확히 표현할 수 있는 NULL 이라는 명칭의 매크로 상수를 쓰는 것이 좋다.
변수가 NULL 값을 가지고 있다면 이 포인터는 0번지를 가리키고 있는 것이다.
응용프로그램이 이 번지에 어떤 값을 저장하거나 읽을 수 없도록 보호되어있다.
그래서 포인터를 리턴하는 거의 대부분의 함수는 에러가 발생했을 때 NULL값을 리턴한다.
NULL 의 활용 - 에러처리
함수의 리턴값이 NULL인지 아닌지 점검해 보고 NULL이 아닐 때만 원하는 작업을 하며 에러 발생시 적절하게 에러 처리해야 한다.
if ( func()==NULL) {
// 에러 처리
} else {
// 하고 싶은일
}
ex)
{ char str[] = " korea " ; char *p; p = strchr(str,'r'); if ( p != NULL ) { *p = 's'; } puts(str); }
|
description)
"korea"라는 문자열에서 'r'을 찾아 's'로 변경하되 'r'이 발견되지 않으면 아무 것도 하지 않도록 했다.
만약 NULL 점검을 하지않고 'z'를 찾아 's' 로 바 꾸려고 한다면 0 번지를 잘못 건드리는 오작동을 할 가능성이있다.
동적 메모리 할당
할당의 필요성
프로그램이 실행되기 위해서는 메모리가 필요하다.
정적 할당 ?
int Score ; = 4byte 할당
double Rate; = 8byte 할당
이런 식으로 프로그램을 작성할 때 미리 메모리 필요량을 알려주는 할당을 정적 할당이라고 한다.
동적 할당 ?
프로그램을 작성할 때 메모리 필요량을 지정하는 정적 할당과는 달리 실행 중에(Run time) 필요한 만큼 메모리를 할당하는 기법이다.
어떤 경우에 동적할당이 필요할까?
메모리 필요량을 프로그램 작성시에 전혀 예측할 수 없는 경우가 있다.
임의의 학교에서 학생관리 프로그램일 경우 학생수가 학교마다 각각 다를 수 있다.
그냥 상수로 여유있게 배열크기를 지정할 수 있지만 이럴 경우 효율성이 떨어진다.( 메모리 낭비가 된다. )
int stNum; printf("학생수를 입력해 주세요."); scanf("%d",&stNum); int arScore[stNum]; |
description)
프로그램 시작 직후에 학생수를 질문하여 stNum에 입력받았다.
그리고 입력된 stNum 크기만큼 arScore 배열을 선언했다.
하지만 !!
이 코드는 에러가 발생한다.
배열 선언문의 크기값은 변수로 지정할 수 없고 반드시 상수로만 지정해야 하기 떄문이다.
그래서 동적할당을 사용해야한다!!..
메모리 관리 원칙
1. 메모리 관리의 주체는 운영체제이다 .
반드시 운영체제를 통해서만 메모리를 할당받을 수 있다.
2. 운영체제는 메모리가 있는 한은 할당 요청을 거절하지 않는다.
메모리가 없을 경우에만 메모리가 부족하다는 에러를 리턴한다.
3. 한 번 할당된 메모리 공간은 절대로 다른 목적을 위해 재할당되지 않는다.
운영체제는 메모리를 반납하기 전에는 응용프로그램이 이 공간을 독점적으로 사용할 수 있도록 보장한다.
4. 응용 프로그램이 할당된 메모리를 해제하면 운영체제는 이 공간을 빈 영역으로 인식하고 다른 목적을 위 해 사용할 수 있도록한다.
메모리 관리 원칙은 한마디로 '중앙 집중적 신고제' 라고 할 수있다.
# 허가제가 아니라 신고제 이다.
할당 및 해제
메모리를 동적으로 할당 및 해제할 때는 다음 두 함수를 사용한다.
malloc 과 free 함수의 프로토타입
void *malloc(size_t , size ) ; void free(void * memblock ) ; |
먼저 malloc (엠얼룩) 함수는
인수로 필요한 메모리양을 바이트 단위로 전달하면 요청한만큼 할당한다.
size_t 는 메모리의 양을 나타내는 단위인데 _t로 끝나는 사용자 정의 타입은 표준에 의해 반드시 정의하도록 되어 있으므로 기본 타입과 거의 대등한 자격을 가진다.
10바이트가 필요하면 malloc(10)이라고 호출하고 1000바이트가 필요하면 malloc(1000)이라고 호출하면된다.
** 실행중에 할당하는 것이므로 malloc(Num)과 같이 변수도 사용할 수 있다.
할당한 메모리를 어떤 목적에 사용할지는 알 수 없으므로 malloc 은 void *형을 리턴하며 받는 쪽에서 원하는 타입으로 캐스팅 해야한다.
free 함수는 동적으로 할당한 메모리를 해제한다.
반드시 메모리를 다 사용한 후에 free 함수를 호출하여 메모리를 해제해야 한다.
그래야 이 영역이 다른 프로그램을 위해 재활용될 수 있다.
ex ) 정수형 변수 10개를 담을 수 있는 메모리를 할당하는 예이다.
int *ar; ar = ( int *)malloc(10*sizeof(int)); // ar 사용 free(ar); |
description)
malloc은 인수로 전달된 크기 만큼의 메모리를 할당하고 그 시작 번지를 리턴하되 리턴 타입이 void*형 이므로 이 포인터를 변수에 대입할 때는 반드시 원하는 타입으로 캐스팅할 필요가있다.
이렇게 할당하면 ar 번지 이후 40바이트를 응용프로그램이 배타적으로 소유하게 된다.
즉 ar[10]과 같이 정수형 배열과 같아지며 메모리 내에서 실제 모양과 용도 , 적용되는 문법도 배열과 동일하다. 마치 정적 할당된 배열처럼 이 메모리를 활용할 수 있다.
물론 사용한 후에 반드시 free 를 사용하여 해제 해야한다.
ex) 동적할당을 사용한 성적처리프로그램
{ int *arScore; int i,stNum; int sum; printf("학생수를 입력하세요 : "); scanf("%d",&stNum); arScore=(int *)malloc(stNum*sizeof(int)); if (arScore == NULL ) { printf("메모리가 부족합니다.\n"); exit(0); } for ( i =0 ; i < stNum; i ++ ) { printf("%d번 학생의 성적을 입력하세요 : ", i + 1 ); scanf("%d",&arScore[i]); } sum = 0; for ( i = 0 ; < stNum; i ++ ) { sum+=arScore[i]; } printf("\n총점은 %d점이고 평균은 %d 점입니다. \n", sum,sum/stNum); free(arScore); } |
재할당 - realloc
malloc 함수와 마찬가지로 메모리를 할당하되 필요한 메모리양을 지정하는 방법만 다르다.
calloc 함수의 프로토타입 = void *calloc ( size_t num , size_t size _);
malloc은 필요한 메모리를 바이트 단위 하나로만 전달받지만 calloc은 두 개의 값으로 나누어 전달받는 점이 다르다.
malloc = " 몇 바이트 할당해주세요 "
calloc = " 몇 바이트짜리 몇 개 할당해 주세요 "
calloc(10*sizeof(int),2) = 정수형 (4바이트 ) 10 개 짜리 메모리를 2개 할당해 주세요.
malloc 은 메모리 할당만 함으로 메모리에는 초기에 쓰레기값이 들어있지만,
calloc으로 할당하면 할당 후 모든 메모리를 0으로 채운다.
realloc 함수의 프로토 타입 : void *realloc ( void *memblock, size_t size );
첫 번째 인수로 malloc 이나 calloc으로 할당한 메모리의 시작 번지를 주고 두 번쨰 인수로 재할당할 크기를 전달한다. 만약 첫 번째 인수가 NULL 일 경우 , 즉 할당되어 있지 않을 경우는 새로 메모리를 할당하므로 realloc의 동작은 malloc 과 같아진다.
두 번쨰 인수 size 가 0 일 경우는 할당을 취소하는 얘기이므로 free와 같아진다.
ex ) realloc
{ int *ar; ar = ( int *)malloc(5*sizeof(int)); ar[4] = 1234; ar=(int *)realloc(ar,10*sizeof(int)); ar[9]=5678; printf("ar[4]=%d, ar[9]=%d\n",ar[4],ar[9]); free(ar); } |
result)
ar[4]=1234. ar[9]=5678
동적 할당이 필요한 이유는 컴파일할 시점에 필요한 메모리양을 모를 때가 있기 때문이다.
실행중에라도 필요한 메모리양을 가늠할 수 없을 때가 빈번하게 존재한다.
예를 들어,
네트워크를 통해 파일을 전송하는 프로그램을 작성한다고 해보자. 네트워크의 반대편에서 보내는 파일을 받아야 하는데 이 파일의 크기는 다 받아 보기 전에는 알 수 없는 상황이다.
이럴 때는 최초 적당한 크기로 버퍼를 할당한다. 가령 1M 정도만 할당한 채로 네트워크로 들어오는 패킷을 이 버퍼에 누적시키고 총량이 1M가 넘을경우 메모리를 재할당하여 2M로 늘린다.
이런식으로 패킷을 다 받을 때 까지 재할당하면 된다.
이런 상황은 훨씬 더 자주 발생하는데 압축을 해제한다거나 DB 쿼리를 실행할 때도 재할당이 필요하다.
'C,C++' 카테고리의 다른 글
[C,C++] 배열과 포인터 (0) | 2019.02.28 |
---|---|
[C,C++] 이중 포인터 (0) | 2019.02.25 |
[C,C++] 포인터 연산 (0) | 2019.02.25 |
[C,C++] 포인터의 정의 , 포인터 란? (1) | 2019.02.25 |
[C,C++] 프로그래머의 동기 (0) | 2019.02.25 |