사람들이 C언어는 무진장 어렵다고 했다. 나도 파이썬과 JS 같은 스크립트 언어만 쓰다가 C언어를 접하는 것이었기에 처음에 겁부터 먹고 책을 폈다. 그런데, 생각보다 다른 언어들과 비슷한 점이 많았다. for, while, if, switch-case, 여러 연산자, 함수와 인자 등. 물론 내부적으로 돌아가는 과정은 상이하겠지만 (최소한 컴파일러 혹은 인터프리터 간의 차이는 있을듯) 일단 확실한건, C언어가 태어난 중심적인 배경은 대부분의 현대적인 언어들과 같다는 것이다. 기본적으로 명령형 언어이다.
그러나 확실한 차이점이 있고, 그건 포인터라고 생각한다. (c++도 포인터를 쓴다)
주솟값
결국 포인터는 메모리 주소값을 저장하는 변수다. 주소값을 이해하는게 곧 포인터를 이해하는거라고 생각한다.
주솟값은 해당 데이터가 저장된 메모리의 시작 주소.
C언어에선 주솟값을 1 바이트 크기의 메모리 공간으로 나누어 저장한다.
e.g. int
타입 데이터는 4바이트지만, int 데이터의 주소값은 그 4바이트 덩어리의 시작 주소인 1바이트 공간에 대한 주소값만을 가리킨다.
주소값 또한 다른 데이터 타입처럼 가지고 놀 수 있다는걸 이해하면 포인터에 대한 이해가 거의 끝났다고 생각한다.
pointer
주소값에 대한 이해가 끝났다면 남은건 그 주소값을 저장하고 꺼내오는 문법을 배우는 것.
일단 *
, &
연산자만 알면 된다. &
는 address of 연산자라고 한다. 즉 변수의 주소값을 얻을 수 있다. *
는 두가지 용도가 있는데, 첫번째는 pointer 변수 선언에 쓰이는 거고 두번째는 원래 포인터 변수 값, 즉 주소값에 담겨있는 값에 접근하는 접근 연산자.
int *p; // 포인터를 정의할 때 쓰는 * 연산자
int month = 3;
p = &month; // 변수의 주소값을 가리키는 & 연산자
// p = month가 있는 주소값
// *p = month 값 (주소값에 담긴 값). 값을 가리키는 연산자
*(&month) == month // true
*(&month) == *p // true
*p == 3 // true
포인터를 쓰는 이유
- array 등의 연속된 데이터에 대한 접근
- 동적할당된 메모리 영역(heap)에 접근
- 메모리 공간 효율
- call by reference
C언어에서 배열은 포인터 기반이다. 그리고 string은 ascii 문자(char
)들의 배열이다.
함수를 다른 함수의 인자로 넣을 때에도 포인터를 쓴다.
call by reference
C언어의 함수 인자는 함수 내에서만 값이 copy 된 것이고, 호출할 때 쓴 인자의 값에는 변화가 생기지 않는다.
void swap_fake(int x, int y){
int temp = x;
x = y;
y = temp;
}
void swap_real(int *x, int *y){
int temp = *x;
*x = *y;
*y = temp;
}
// ...
int a=1, b=9;
swap_fake(a,b) // a,b의 값에는 변화 없음
swap_real(&a,&b) // a,b의 값은 서로 바뀜
scanf
는 그래서 주솟값을 쓰는거다. 사용자의 입력에 따라 call by reference로 변수 값을 바꾸기 위해.
scanf(&input);
이중 포인터…
결국 포인터도 하나의 변수인데, 그렇다면 포인터의 주솟값을 가리키는 포인터(이중 포인터)를 인자로 받도록 하면 포인터 값도 스왑할 수 있다.
void Swappointer(int **ptr1, int **ptr2){
int *temp = *ptr1;
*ptr1 = *ptr2;
*ptr2 = temp;
}
Swappointer(&ptr1, &ptr2);
포인터 연산과 연산자 우선순위
포인터는 가리키는 데이터 타입에 따라 같은 값 더하기라도 다르게 생각한다. int
가 4바이트이고 어떤 변수가 int pointer 타입일 때, 그 포인터를 1증가시킨다는 것은 메모리 공간상의 주소를 4바이트만큼 이동시킨다는 것. 반면 char
에선 포인터값 1 증가가 곧 1바이트만큼 이동이다.
그러므로, 포인터 변수를 정의할 땐 확실히 데이터 타입 명시를 해야 한다.
/* 1. pointer declaration
* every pointer have constraint to point to a specific data type. */
int *ip; // 4 바이트 (일반적으로)
double *dp;
float *fp;
char *ch; // 1 바이트
그리고 포인터 연산은 +, -를 주로 쓴다. (비교 연산도 있지만 어디서 쓰려나..?)
연산자 우선순위는 학교 시험문제 외에는 딱히 쓸 곳이 있을까 싶다. 가독성을 위해선 연산자 우선순위를 잘 알고 있더라도 괄호를 써서 구분지어줘야 한다.
동적 메모리 할당
malloc
, calloc
등 동적 메모리 할당의 return 값은 void 포인터다. 그래서 보통 return값에 형변환을 해서 원하는 데이터 타입의 포인터로 만들어준다.
free
는 동적 할당된 변수의 메모리 해제를 위해 쓰이는데, 이때도 포인터가 쓰인다.
int *pi = (int *) malloc(sizeof(int)); // int가 쓸 만큼(4바이트) 동적 메모리 할당
free(pi) // 메모리 해제
dangling pointer : free
로 할당 해제를 했다고 하더라도, deallocate만 할 뿐 memory block은 유효하게 된다. 그래서 한번 free
된 변수의 포인터를 갖고 뭘 할려고 하면 에러가 발생한다.
이렇게 쓸 수 없게 된 메모리 공간을 보고 garbage가 생성되었다고 한다.
'language' 카테고리의 다른 글
제어자 (modifier) (0) | 2023.03.24 |
---|---|
OOP (0) | 2023.03.24 |
c언어. 구조체 (0) | 2022.12.21 |
JS 스코프와 scope chain (0) | 2022.12.03 |
JS 실행 컨텍스트와 콜 스택 (0) | 2022.12.03 |