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 |