본문 바로가기

C언어

9강 - Pointer Application

1. 배열과 포인터

int arr[5] = {2, 4, 6, 8, 22};

1) 기본기: 배열의 이름으로 만들어진 변수의 값 = 배열 첫 번째 원소의 주소 값

1> *arr == arr[0] => 2

2> arr == &arr[0] => 6487600

2) 포인터 변수(p)가 배열(arr)을 참조하면 포인터 변수를 배열처럼 사용할 수 있다.

int *p = arr;
// 1> int *p;
// 2> p = arr;

=>int *p = arr; =>arr == p : p(포인터 변수)를 배열(arr)처럼 사용할 수 있다.

1> p[0] == arr[0], ... , p[4] == arr[4]

arr[0]

arr[1]

arr[2]

arr[3]

arr[4]

p[0]

p[1]

p[2]

p[3]

p[4]

2

4

6

8

22

2> 배열 중간 부분의 주소(원소 X)를 pointer 변수에 할당할 수도 있다.

p = &arr[1]

arr[0]

arr[1]

arr[2]

arr[3]

arr[4]

 

p[-1]

p[0]

p[1]

p[2]

p[3]

p[4]

2

4

6

8

22

 

3) 포인터 변수(p)와 달리**배열의 이름(arr)은 주소 값의 변경이 불가능하다.**

배열 이름 arr은 배열의 시작 주소를 가지도록 고정되어 있기 때문에 arr의 값을 변경할 수 없다.

1> arr++ => 오류

2> p++=> arr[1] = 4를 가리킨다. (다음 내용에서 구체적으로 설명한다.)


2. pointer 계산과 arrays

int arr[5] = {2, 4, 6, 8, 22};

1) +-연산

(포인터 + n) == (포인터의 주소값 + n*포인터가 가리키는 자료형의 크기)

을 값을 가진다.

arr == 100; 이라면

=> 배열의 이름이자 포인터인 arr는 arr[0]을 가리키며 arr[0]은 정수형이니 4씩 주소값이 증가한다.

1> 배열과 배열의 주소

a

100

->

a[0]

*a

2

a + 1

100 + 1*4

->

a[1]

*(a+1)

4

a + 2

100 + 2*4

->

a[2]

*(a+2)

6

a + 3 

100 + 3*4

->

 

 

8

a + 4

100 + 4*4

->

 

 

22

2> 포인터 변수 p가 배열의 다른 원소를 참조하도록 초기화 하는 경우

int *p = a;
p = &a[1] // => p == a+1

a

 

100

->

a[0] / p[-1]

*a / *(p-1)

2

a + 1

p

100 + 1*4

->

a[1] / p[0]

*(a+1) / *p

4

a + 2

p + 1

100 + 2*4

->

a[2] / p[1]

*(a+2) / *(p+1)

6

a + 3

p + 2

100 + 3*4

->

a[3] / p[2]

*(a+3) / *(p+2)

8

a + 4

p + 3

100 + 4*4

->

a[4] / p[3]

*(a+4) / *(p+3)

22

3> 결론

a[i] == *(a+i)

a + n == &a + (n*data type size)

2) 관계연산자

1> 양쪽의 피연산자가 모두 포인터인 경우에만 사용가능하다.

2> 관계연산자를 사용하는 가장 일반적인 경우는 포인터와 NULL상수를 비교하는 것

Long Form

Short Form

if (ptr == NULL)

if (!ptr)

if (ptr != NULL)

if (ptr)

3) 이차원 arrays와 pointer

table[i][j]

== *(table+i)[j]

== *(*(table+i)+j)

== *(table[i]+j)


3. 포인터를 이용한 함수의 배열 전달

1) 기본: arr[]가 사실상 포인터이다.

'int arr[] == int* p'

'arr == &arr[0]'

 

코드

의미

함수 선언 

void func (int arr[])

void func (int* p);

포인터(주소 값)를 parameter로 받을 것이다.

함수 호출 (주소)

func(arr);

func(&arr[0]);

그래서 parameter에 변수의 주소값을 사용

함수 정의

void func (int arr[]); { arr[3] = }

void func (int* p); { *p = }

포인터(주소 값) x를 callee에 넣어서

x가 가리키는 변수에 접근한다. (*연산자 사용)

1> 함수 선언(원소): type 함수이름 (type 배열이름[])

- type 함수이름 (type*포인터변수) == type 함수이름 (type배열이름[])

2> 함수 호출(주소): 함수이름 (배열이름)

- 함수이름 (배열이름) == 함수이름 (포인터변수) == 함수이름(주소)

3> 함수 정의(원소): type 함수이름 (type 배열이름[]) {배열이름[index] = ...}

- type 함수이름 (type*포인터변수) == type 함수이름 (type배열이름[])

배열이름[index]==*포인터변수

2) 2차원 이상의 배열을 형식 매개변수로 갖는 함수

1> 첫 번째를 제외한 나머지 차원의 배열 크기를 알고 있어야 한다.

2>'arr1d[]'으로 쓰던 걸'arrnd[][3][4]'으로 차원에 맞게 쓰면 된다.


4. memory allocation functions

0) 변수 영역의 메모리를 할당 받는 2가지 방법: static, dynamic

1> static memory allocation

- 메모리의 선언과 정의가 완벽하게 명시되어 있어야 한다.

- 실행 시간 동안에 할당된 byte 수 등이 변할 수 없다.

2> dynamic memory allocation

- 실행 중 입력되는 데이터에 맞게 기억공간이 할당되어야 한다.(메모리낭비를 최소화하려고)

- 프로그램 실행 중(run time)에 메모리 영역을 할당한다.

- 라이브러리로 구현되며, 포인터를 이용한다.

- 보통은 큐, 스택, 연결리스트, 이진트리와 같은 자료구조를 표현할 때 사용됩니다 .

1) 메모리 구조

1>정적메모리는 보통 stack area, data area을 의미합니다. 메모리 크기는 컴파일할 때 결정되며 실행 도중에 해제되지 않고, 프로그램이 종료할 때 알아서 운영 체제가 회수합니다. C 언어에서 정적 할당된 메모리는 스택에 위치하기 때문에 할당 받을 수 있는 메모리에 제약을 받습니다.

2>동적메모리는 프로그램 실행 시간(런타임) 중에 프로그래머가 동적으로 메모리를 할당 할 수 있습니다. 한마디로 내맘대로 메모리를 지정하고 사용하고 반납할 수 있다는거죠. heap area 같은 동적 메모리가 요구됩니다.

code area

프로그램의 코드

 

data area (global)

전역변수와 static 변수

(static으로 선언되는 static 변수)

 

stack area

지역변수와 매개변수

선언된 함수를 빠져나가면 소멸된다.

heap area

이외의 모든 것

프로그래머가 관리하는 메모리 공간

2) 메모리의 동적 할당에 사용되는 메모리 할당 함수

1> malloc(), calloc(), realloc(): 메모리를 할당하기 위한 함수

2> free(): 더 이상 사용하지 않는 메모리를 return하는 함수

3> 함수를 사용하기 위해서는 "stdlib.h" 헤더파일을 포함해야 한다. (#include <stdlib.h>)

3) malloc()

0>Memoryallocation

1> 할당한 메모리의 시작주소를 void pointer로 return한다.

- 해당 메모리 영역을 할당하는데 실패하면 NULL pointer를 return

2> 함수 구성

void* malloc (size_t size);

- void pointer이므로 참조하려는 type의 변수 결정 시 casting pointer로 다른 type의 변수를 참조할 수 있게 함

- size_t: typedef로 재정의해서 만들어진 사용자 정의 자료형 (unsigned int, unsigned long 형)

- size: 해당 자료의 size를 넣어야 한다. =>'sizeof(int)*n'을 보통 사용

3> 일반적인 사용 예

int* p;

p = (int *)malloc(8*sizeof(int));

4> 동적 할당에서의 원소 접근

포인터 변수가 있으면 포인터가 가리키는 영역을 array처럼 사용 가능

=> p는 시작원소를 가리킨다.

=> p == 시작 원소의 주소 값

=> *(p+i) == p[i] == i 번째 원소 접근

5> 예제

#include <stdio.h>
#include <stdlib.h>

int main (void)
{
	int *p;
	p = (int*)malloc(1*sizeof(int)); // 4byte의 기억공간 할당
	if (p == NULL)
	{
		printf("not allocated\n");
		return;
	}
	*p = 1;
	printf("%d\n", *p);
	return 0;
}<

4) calloc()

0>Contiguous Memoryallocation (contiguous: 인접한)

1> 할당한 메모리의 시작주소를 void pointer로 return한다.

- 한 번에 여러 메모리를 만든다.

- 일반적으로 array를 위한 메모리 할당에 쓰인다.

2> 함수 구성

void* calloc (size_t element-count, size_t element_size);

- void pointer이므로 참조하려는 type의 변수 결정 시 casting pointer로 다른 type의 변수를 참조할 수 있게 함

- size_t: typedef로 재정의해서 만들어진 사용자 정의 자료형 (unsigned int, unsigned long 형)

- element-count: 몇 개의 메모리를 만들 것인지

- element-size: 해당 자료의 size를 넣어야 한다. =>'sizeof(int)*n'을 보통 사용

3> malloc과의 차이점

- 한 번에 여러 메모리를 만든다 => array를 위한 메모리 할당에 최적화

- 할당된 메모리를 0으로 초기화

4> 예제

200개의 int element를 가지는 array할당

if (!(ptr = (int*)calloc(200, sizeof(int))));
{ // no memory available
	exit(100);
}
// memory available

5) realloc()

0>Reallocation of memory (메모리 재할당)

1> 변수의 메모리를 동적으로 변경하기

-malloc 과 calloc 함수는 동적메모리 할당 후에 메모리 크기를 변경하지 못함

- 하지만 realloc으로 메모리를 동적으로 변경

2> 함수 구성

ptr이 현재 할당하고 있는 메모리의 크기를 newSize로 변경한다.

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

- realloc: 변경 이후의 주소

- ptr: 변경할 주소

- newSize: 할당할 변수의 size

6) free()

1> 동적으로 할당 받은 메모리 영역을 반환하는 함수

- 정적메모리는 우리 컴퓨터가 착하게도 알아서 메모리를 회수해준다고 했습니다.

- 하지만 동적메모리는 사용자가 직접 메모리 해제도 해야합니다. (할당만 자꾸 하다간 메모리가 부족할 수 있음)

- 해제하려는 pointer가 NULL이면 아무 일도 하지 않는다.

2> 함수 구성

해당 ptr 메모리를 해제

void free (void* ptr);

- ptr은 malloc(), calloc(), realloc() 등에 의해 이전에 할당되었던 기억 장소의 포인터이어야 한다.

cf> dangling pointer

1) (메모리가 삭제되거나 재 할당 될 때) 유효하지 않은 메모리 공간을 참조하는 포인터

2) free()로 해제된 공간을 참조하고 있는 경우, 지역변수를 참조하는 경우 발생하기 쉽다.

(지역변수 참조/ 함수종료 후 해당 변수는 stack에서 제거/ dangling pointer가 된다,)

cf> Array of pointers

2차원 array로 나타냈으면 메모리 낭비가 있었을 것을

동적할당된 pointer의 array로 나타내니 메모리 낭비가 적어집니다.

cf> pointer에서 자주 실수하는 에러

1> int *a[5] != int (*a)[5]

- int *a[5]

(원소: 정수형 pointer / 배열: 정수 pointer가 5개)

- int (*a)[5] == int a[][5]

(원소: 정수 5개짜리 array / 배열: )

int (*a)[5]는 그냥 2차원 배열을 만든다고 생각하면 된다.

2) ary == &ary[0]

3) ary[i] == *(ary + i)

4) int table []에 대하여

1> table++ (X)

2> table = ... (X)

배열 이름(포인터이기도 하다.)은 값의 변경이 불가능하다.

table + 1 이라는 표시는 가능하지만

table++ (값도 변화하는 게 문제)

table = (무언가를 대입해서 값을 변화시키는 것도 문제)

'C언어' 카테고리의 다른 글

11-2강 구조체 포인터  (0) 2020.04.09
11-1강 - 구조체 기본  (0) 2020.04.09
3강 - 함수 (function)  (0) 2020.03.02
2강 - structure of a C program  (0) 2020.03.02
1강 - Introduction to C  (0) 2020.03.02