간단 지식/C

02. 포인터, 동적메모리할당함수, 포인터배열

납작한돌맹이 2020. 9. 12. 04:58
반응형

포인터는 변수의 주소를 의미한다. 포인터 변수는 포인터를 저장한 변수, 즉 변수의 주소를 저장하는 변수를 말한다. 따라서 포인터 변수에 대입할 수 있는 것은 주소뿐이다.

포인터 변수 선언방식은 다음과 같다.

type *포인터변수;  (*은 변수 명 앞에 붙이는 간접 연산자이다.)

아래 예제를 보면 한 번에 포인터에 대해 이해할 수 있다.

void main() {
	int a = 10;
	int *p;		//포인터변수p 선언
	p = &a;		//포인터변수p에 주소 저장

	printf("%d \n", p);	//포인터변수p에 저장된 값(주소)을 출력한다.
	printf("%d \n", *p);	//포인터변수p에 저장된 값이 가리키는 곳의 값을 출력한다.

	*p = *p + 10;		//포인터 변수p에 저장된 값이 가리키는 곳의 값에 10을 더한다.

	printf("%d \n", *p);
	printf("%d \n", p);
}

이렇게 헷갈리는 포인터 변수를 이용하는 이유가 무엇일까? 바로 동적으로 할당되는 메모리 영역인 heap에 접근하기 위해서이다. 따라서 c언어는 주소 제어에 탁월한 기능이 있다고 말할 수 있다.

 

포인터는 변수의 주소라고 했다. 그렇다면 포인터에 배열을 저장할 수 도 있다는 말이 된다. 아래 코드를 보자.

void main() {
	char str[6] = "happy";
	char *ptr;
	ptr = str;	

	for (int i = 0; i < 6; i++) {
		printf("%c \n", *(ptr+i));
	}
}

포인터변수 ptr에 배열의 이름을 저장한다는 의미는 배열의 첫 index의 주소를 저장한다는 의미와 같다. 즉 위 코드에서  배열 str와 포인터 ptr의 관계는 아래 그림과 같다.

(참고로 문자형(1byte) 배열이므로 주소값 (p+0)이 1000이라면 주소값 (p+1)은 1001이다.)


포인터와 밀접관 관련을 갖는 함수가 하나 있다. 바로 malloc()으로 메모리 할당 함수이다. 포인터는 일반 배열처럼 값들이 메모리에 밀집되어 있지 않아도 되게하는 이점이 있다. 그 이점을 더욱 살려서 메모리 공간의 낭비를 방지하는게 바로 malloc()이다. 메모리 낭비는 프로그램 실행 시 필요한 메모리의 크기가 모두 다르기 때문에 발생한다. 메모리 낭비가 발행하면 프로그램이 느리게 실행되게나 아예 실행되지 않는 문제가 생긴다. 그러니 어느 정도 이상의 규모의 프로그램을 구성한다면 malloc()을 잊지 말자.

malloc을 사용하기 위해서는 헤더파일 malloc.h을 꼭 선언해 줘야한다. 기본 사용 형식은 다음과 같다.(p = 포인터 변수)

p = (p의 data type*)malloc(sizeof(p) * 필요한 size);

빨간 색의 부분을 cast 연산자라고 부른다. 

아래 예시는 사용자에게 입력받은 값을 모두 곱하는 코드이다.

#include <stdio.h>
#include <malloc.h>

void main() {
	int count = 0;
	int *p;
	int sum;

	printf("저장할 수들은 모두 몇 개 인가요: ");
	scanf_s("%d", &count);
	p = (int*)malloc(sizeof(p)*count);     ------------------(1)
	
	printf("저장할 수들을 차례로 입력해주세요 \n");
	for (int i = 0; i < count; i++) {
		printf("%d번째 수: ", i+1);
		scanf_s("%d", &*(p + i));
	}

	printf("=== 수들을 모두 곱해봅니다. ====\n");
	for (int i = 0; i < count; i++) {
    		sum = 1;
		sum *= *(p + i);
	}
	printf("%d \n", sum);

	free(p);       ------------------------------------------(2)
}

코드라인(1)로 인해 포인터 변수 p는 4byte x count 만큼의 메모리 공간을 할당받는다. malloc()으로 할당받은 메모리는 초기화되지 않는 공간이므로 출력해보면 이상한 값이 나온다. scanf_s로 값들을 입력받고 작업을 마친 후 코드라인(2)처럼 free()를 이용해 메모리 해제를 해야 동적메모리할당의 완성이다. free()의 정확한 의미는 포인터 변수에 null값을 저장한다는 의미이다.

 

malloc()과 유사하게 calloc(), realloc()이 있다. calloc()은 malloc()과 사용형식도 똑같고 그 의미도 같지만, 할당받은 메모리가 0으로 초기화된 상태이다. realloc()은 메모리 크기를 실시간으로 변경하는 함수로, 사용형식은 다음과 같다.

p = (p의 data type*)realloc(p, sizeof(p)*필요한 size);


포인터의 이점을 이용한 또다른 개념이 있다. 바로 이차원 배열이다. 우리가 아는 가장 기본적인 이차원 배열은 아래와 같은 구조를 가진다.

h e l l o \0  
m o n d a y \0
i c e \0      

미리 할당받은 고정된 열과 행에 문자열들을 저장하고 나면 빈 공간이 꽤 많다. 그러나 포인터 배열을 이용하면 남는 공간을 최대한으로 줄일 수 있다. 아래 예제가 바로 그런 점을 이용한 이차원 배열이다.

#include <stdio.h>
#include <malloc.h>
#include <string.h>

void main() {
	char a[100];	--------------(1)
	char *p[3];     --------------(2)	
	int size;

	for (int i = 0; i < 3; i++) {
		printf("%d번째 문자열을 입력해 주세요: ", i+1);
		gets_s(a, 100);
		
		size = strlen(a);
		p[i] = (char*)malloc(sizeof(char)*size + 1);   ----------(3)
		strcpy_s(p[i], size+1, a);
	}

	printf("문자열들을 출력하겠습니다. \n");
	printf("%s \n", p[0]);	//또는 *(p+0)
	printf("%s \n", p[1]);
	printf("%s \n", p[2]);

	for (int i = 0; i < 3; i++) {
		free(p[i]);
	}
}

코드라인 (1)은 임시로 사용하는 일차원 배열이다. for문을 통해 이 일차원 배열에는 이차원 포인터 배열의 각 행에 저장할 문자열을 저장할 것이다. 코드라인 (2)는 이차원 배열에서 행의 개수가 3이라는 의미이다. 즉 문자열 3개를 입력할 수 있다는 뜻이다. 코드라인 (3)에서 size+1은 문자열 마지막의 \0을 고려해야하기 때문이다. for문을 통해 문자열 하나하나를 할당된 메모리에 저장함으로써 메모리 낭비가 없어진다.

 

 

 

 

(이 글이 도움이 됐다면 광고 한번씩만 클릭 해주시면 감사드립니다, 더 좋은 정보글 작성하도록 노력하겠습니다 :) )

반응형