포인터란 메모리 주소를 저장하는 변수
int int_value = 123;
char char_value = 'A';
int *int_pointer_value = &int_value;
char *char_pointer_value = &char_value;
위와 같이 주소연산자 &를 이용하여 일반변수의 주소를 읽어와 포인터 변수에 저장한다.


변수도 일반 변수와 같이 연속되는 메모리상의 8byte공간을 차지한다. 포인터는 어처피 메모리의 주소만을 저장하므로 데이터 형식과는 관련이 없다. 그 이유로 모든 포인터 변수의 크기가 8byte로 동일한 것이다. 그런데도 왜 포인터 변수를 선언할 때 데이터 형식을 지정하는가에 대한 이유는 해당 포인터가 가리키는 데이터의 형식을 명시하기 위해서이다.
이렇게 함으로써 포인터를 대상으로 연산할 때, 일관성을 유지하고 오류를 방지하는 데에 도움이 된다.
포인터 변수가 가리키는 데이터에 접근할 때에는 역참조 연산자*를 사용하여 데이터에 접근하면 된다.
포인터를 사용할 때 쓰는 *와 포인터에 접근할 때 사용하는 *는 다른 용도임으로 기억해야 한다.
*int_pointer_value = 321;
*char_pointer_value = 'Z';
와 같이 포인터 변수가 가리키는 데이터에 직접 접근하여 변수의 값을 변경할 수도 있다. 위와 같이 변경하면 원래 메모리 주소의 이름으로 사용하던 int_value, char_value의 값이 바뀐다.
다중 포인터란 결국 포인터 또한 차지하는 공간의 주소이기 때문에 그 포인터 변수의 주소를 저장하는 또 다른 포인터를 만드는 것을 의미한다.
int int_value = 123;
int *int_pt_value = &int_value;
int **int_pt_pt_value = &int_pt_value;
int ***int_pt_pt_pt_value = &int_pt_pt_value;

위와 같이 사용하고 역참조 연산자를 중첩해서 사용하여야 변수의 값에 접근할 수 있다.
int lotto[8] = { 1, 2, 3, 4, 5, 6, 7, 8}
//처음 배열 선언할 때 []안에 넣는 숫자는 ~개(크기)지 이것또한 0부터 센다고 생각하면 안됨
이런 식으로 사용되며 메모리 상태를 아래와 같이 표현하면 아래와 같다.
배열 변수의 이름인 lotto는 첫 번째 원소의 주소인 &lotto[0]을 가리키는데 이 이유로 lotto + 0(포인터 연산이라고 한다) 을 출력하면 &lotto[0]이 나온다. (값이 아니라 주소, 값을 가지고 오고 싶으면 역참조 연산자를 사용해서 *lotto + 0이런식으로 가져와야 함)포인터 연산에서 덧셈은 자료형의 크기를 곱한 만큼 더해지는데 lotto가 int형이므로 덧셈할 숫자의 4byte가 곱해져 더해지는 것이다.
이러면 이런 생각을 할 수 있다. "배열이랑 포인터는 같은 거 아니야?" 하지만 아니다.
int array[5] = { 1,2,3,4,5 };
int* pointer_array = array;
std::cout << "array: " << array << std::endl;
std::cout << "pointer_array: " << pointer_array << std::endl;
std::cout << "sizeof(array):" << sizeof(array) << std::endl;
std::cout << "sjzeof(pointer_array): " << sizeof(pointer_array) << std::endl;
출력결과
array: 000000639F50FA18
pointer_array: 000000639F50FA18
sizeof(array):20
sjzeof(pointer_array): 8
이처럼 명백히 array는 int[5]형식이므로 크기가 20byte이고 pointer_array는 포인터 변수이므로 크기가 8byte이다.
char char_array[10];
int int_array[500];
float float_array[1000];
이런 식으로 정해진 크기의 고정 배열을 사용하면 배열의 크기가 고정되다 보니 더 많은 원소가 필요할 때는 처리할 수 없고, 반대로 너무 큰 배열을 선언하면 그만큼 메모리가 낭비되거나 프로그램이 강제 종료 될 수 있다. 그래서 동적 메모리 할당과 해제가 필요한 것이다.
주의할 점! 동적 메모리 할당은 "크기가 바뀌는 메모리"가 아니라, '필요한 만큼 메모리를 직접 생성하고 해제'하는 것이다.
동적 메모리 할당
지료형 *변수_이름 = new 자료형;
동적 메모리 해제
delete 변수_이름;
배열 형태의 동적 메모리 할당과 해제는 아래와 같이 한다.
자료형 *변수이름 = new 자료형[크기];
delete[] 변수이름;
유의할 점은 배열 형태로 메모리를 할당했다면 똑같이 배열 형태로 메모리를 해제해야 한다는 것이다. 그렇지 않으면 해제되지 않는 메모리 때문에 메모리 누수가 발생한다.
따라서 동적 메모리를 사용할 때에는 메모리 해제를 철저히 한다.
예제
#include<iostream>
#include<string>
int main() {
int customer_num = 0;
std::cout << "방문한 손님 수: ";
std::cin >> customer_num;
std::string *bread_num = new std::string[customer_num];
for (int i = 0; i < customer_num; i++) {
bread_num[i] = "빵" + std::to_string(i + 1);
}
std::cout << std::endl << "--생산된 빵--" << std::endl;
for (int i = 0; i < customer_num; i++) {
std::cout << *(bread_num + i) << std::endl;
}
delete[] bread_num;
return 0;
}
출력결과
방문한 손님 수: 5
\n
--생산된 빵--
빵1
빵2
빵3
빵4
빵5