자료형과 포인터를 알고 있을 때, 해당 포인터에서 원하는 만큼 떨어진 포인터에 접근하는 방법인 포인터 연산과 역참조에 대해서 포스팅 해보겠습니다. 자료형이 정해진 포인터의 경우에는 바로 포인터 연산을 사용할 수 있지만, void 포인터의 경우에는 자료형의 사이즈를 알 수 없기 때문에 포인터 연산을 할 수 없습니다. 이러한 문제점을 해결하는 방법인 형변환에 대해서도 같이 포스팅 하겠습니다.
자료형이 정해진 포인터(int
형 포인터, char
형 포인터 등)의 경우에는 +
또는 -
연산자를 통해 포인터 연산을 수행할 수 있습니다. 주소값 + n
또는 주소값 - n
을 하게 되면, (해당 자료형의 사이즈 * n) 만큼 포인터 값이 증가 또는 감소하는 것이 특징입니다. 구조체 역시 동일하게 적용됩니다. 포인터 연산을 통해서 배열의 원소에 바로 접근할 수 있습니다.
int
자료형의 크기인 4byte만큼 주소값이 커집니다.
int number = 1;
int *int_ptr1, *int_ptr2, *int_ptr3;
int_ptr1 = &number; // 000000000061FE04
int_ptr2 = int_ptr1 + 1; // 000000000061FE04 + 4 = 000000000061FE08
int_ptr3 = int_ptr1 + 2; // 000000000061FE04 + 8 = 000000000061FE0C
char
자료형의 크기인 1byte만큼 주소값이 커집니다.
char *str = "abc";
char *str_ptr1, *str_ptr2, *str_ptr3;
str_ptr1 = str; // abc
str_ptr2 = str + 1; // bc
str_ptr3 = str + 2; // c
해당 구조체의 경우에는 구조체 사이즈가 8byte입니다.
struct Object {
int key;
int value;
};
해당 구조체 포인터의 연산시 구조체 포인터의 크기만큼 증가 또는 감소합니다.
struct Object a = { 1, 10 };
struct Object *ptr = &a;
struct Object *struct_ptr1, *struct_ptr2, *struct_ptr3;
struct_ptr1 = ptr // 000000000061FE10
struct_ptr2 = ptr + 1 // 000000000061FE18
struct_ptr3 = ptr + 2 // 000000000061FE20
자료형을 알 수 없는 void형 포인터의 경우에는 포인터 연산을 수행할 수 없습니다. 자료형을 알 수 없으니 주소값을 얼마나 더하고 빼야 하는지 모르기 때문입니다. 이러한 경우에 사용하는 방법이 포인터 형변환입니다. (자료형 *) 주소값
으로 활용 가능하며, 해당 void포인터에 자료형을 부여함으로써 포인터 연산을 사용할 수 있게 합니다.
void *ptr = malloc(5);
void *void_ptr1, *void_ptr2, *void_ptr3, *void_ptr4;
void_ptr1 = ptr; // 0000000000A26BC0
void_ptr2 = ptr + 1; // 에러! void포인터는 연산할 수 없음
void_ptr3 = (int *)ptr + 1; // 0000000000A26BC4
void_ptr4 = (char *)ptr + 1; // 0000000000A26BC1
free(ptr);
이중포인터는 포인터의 포인터이기 때문에 산술연산시 포인터 사이즈(64bit 시스템에서는 8byte)만큼 이동합니다. 어떤 자료형의 이중 포인터이던 동일하게 적용됩니다.
void *void_ptr = malloc(5);
void **void_ptr_ptr = &void_ptr;
void **void_ptr_ptr1, **void_ptr_ptr2, **void_ptr_ptr3, **void_ptr_ptr4;
void_ptr_ptr1 = void_ptr_ptr; // 000000000061FE10
void_ptr_ptr2 = void_ptr_ptr + 1; // 000000000061FE18
void_ptr_ptr3 = (int **)void_ptr_ptr + 1; // 000000000061FE18
void_ptr_ptr4 = (char **)void_ptr_ptr + 1; // 000000000061FE18
free(void_ptr);
포인터 연산을 사용하여 특정 메모리 주소를 찾아내고, 역참조 연산자 *
를 사용하여 메모리에 들어있는 값에 접근할 수 있습니다. 아래 예시는 구조체를 이용하여 배열을 만들었을 때와 malloc
을 사용하여 메모리에 직접 값을 할당할 때를 비교한 것입니다.
struct Object {
int key;
int value;
};
위 구초제를 이용하여 배열을 만들었을 때의 값입니다.
struct Object array[3] = { { 1, 10 }, { 2, 20 }, { 3, 30 } };
struct Object *ptr = array;
printf("%d\n", ptr->key) // 1
printf("%d\n", ptr->value) // 10
printf("%d\n", (ptr + 1)->key) // 2
printf("%d\n", (ptr + 1)->value) // 20
printf("%d\n", (ptr + 2)->key) // 3
printf("%d\n", (ptr + 2)->value) // 30
포인터 연산과 역참조를 이용해 구조체 배열과 동일하게 메모리에 값을 저장할 수 있습니다.
void *ptr = malloc(24);
*((int *)ptr) = 1;
*((int *)ptr + 1) = 10;
*((int *)ptr + 2) = 2;
*((int *)ptr + 3) = 20;
*((int *)ptr + 4) = 3;
*((int *)ptr + 5) = 30;
printf("%d\n", *((char *)ptr)); // 1
printf("%d\n", *((char *)ptr + 4)); // 10
printf("%d\n", *((char *)ptr + 8)); // 2
printf("%d\n", *((char *)ptr + 12)); // 20
printf("%d\n", *((char *)ptr + 16)); // 3
printf("%d\n", *((char *)ptr + 20)); // 30