이 게시글은
서울대학교 데이터사이언스대학원
조요한 교수님의
데이터사이언스 응용을 위한 컴퓨팅 강의를
학습을 위해 재구성하였습니다.
pointer와 reference는
관련 내용이 깊고
자세하게 설명해야할게 많아서
나누다보니 3편까지 작성해야할 것 같다
1편에서는 pointer의 정의, 사용법,
동적 메모리 할당 등에 대해서 배웠다
이버에는 포인터 변수를 이용해서
동적으로 배열을 할당하고
그걸 어떻게 이용하는지 알아보자
Dynamic Array
위 코드를 보면
int *arr = new int[3]{1, 2, 3};
이렇게 포인터 변수 arr를 동적으로 할당했다
위 코드가 무슨 뜻이냐하면
어떤 메모리 주소에
int가 3개 들어갈 수 있는
공간을 할당하겠다는 뜻이다
new로 동적 할당이 끝나면
arr는 첫번째 원소의 위치를 반환하게된다
이렇게 동적으로 arr 포인터 변수를 할당하고
array로 저장하게되면
arr[0], arr[1], arr[2]와 같은 방식으로
저장된 원소에 접근할 수 있다
또한, 동적으로 할당했기 때문에
arr[0], arr[1], arr[2]의 원소들인
1, 2, 3은 heap에 저장되고
포인터 변수 자체는
컴파일시에 메모리에 할당되어
stack공간에 할당된다
그렇다면 이제 arr를 다양하게 출력해보자
그냥 arr을 출력하면
가장 첫번째 원소의 메모리주소를 반환한다
&arr을 하게 되면
포인터변수 arr가 저장된 메모리주소를 반환한다
*arr를 하게되면
가장 첫번째 원소의 값을 반환한다
&(*arr)를 하게 되면
가장 첫번째 원소가 저장된 메모리 주소
즉, 그냥 arr과 같은 값을 반환한다
arr + 1을 하게되면
arr의 첫 번째 주소의 다음 주소를 가리키므로
arr[1]의 주소가 출력된다
arr + 2를 하게되면
arr의 첫 번째 주소에서
2번 건너뛴 주소를 반환하므로
arr[2]의 주솟값이 반환된다
앞에 *이 붙으면 해당 주솟값에있는
원소를 가리키게 되므로
arr[1] = 2
arr[2] = 3
이 출력되는 것이다
이제 이러한 동적 배열을 활용해서
2차원 배열을 만들어보자
multi-dimensional을 만들려면
pointer의 pointer가 필요하다
약간 복잡하지만
개념이 잘 잡혀있다면
이해할 수 있다
위 코드를 보면 우선 int로
nRows와 nCols를 정의해준다
2차원 배열은 행렬을 생각하면 되는데
row는 가로의 개수, col은 세로의 개수다
cin을 통해서 가로는 2, 세로는 3을 넣어준다
그리고 이중 포인터 배열인
int** arr = new int*[nRows];
를 선언해준다
int** arr는 int*가 들어있는
메모리 주소를 담고있는 포인터 변수이다
복잡하게 생각할 필요 없이
포인터변수의 메모리주소를 담고있는
또 하나의 포인터변수인것이다
아무튼 int** arr 포인터변수를
nRows의 값인 2만큼 공간을 할당해준다
이 말은 int 데이터가 담긴 메모리 주소를 갖고있는
포인터변수(8바이트)를 2개
할당해달라는 뜻이다
따라서 arr[0]과 arr[1]이
heap에 할당된다
그다음 for문을 돌면서
arr[0]과 arr[1]에 int 데이터를 nCols의 개수인
3개만큼 담을 int 배열을 할당해준다
int는 4바이트를 필요로하므로
arr[0]과 arr[1] 각각
4바이트 3개 총 12바이트가 할당된다
그래서 arr[0]에는 arr[0]의
첫번째 원소의 주솟값이 저장되고
arr[1]에는 arr[1]의
첫번째 원소의 주솟값이 저장된다
포인터변수 arr는 stack에 저장되고
나머지는 동적으로 할당해줬으므로
heap에 저장된다
Static Array
static array는 heap이 아닌
stack 공간에 할당된다
왜냐하면 compile할 때부터
size를 알기 때문에
(fixed size array)
컴파일러가 자동으로
메모리 공간을 할당해주기 때문이다
array에서 index를 주지않고
그냥 단순 변수명만 사용하는 것은
마치 첫 번째 변수를 가리키는
포인터 변수와 비슷한 역할을 한다
위 코드 예시에서
arr는 arr[0]의 주솟값과 동일하고
arr + 1은 arr[0]에서 한 칸 떨어진
arr[1]의 주솟값과 동일하다
static array에서의 이차원배열
선언법을 알아보자
동적 배열보다 단순한데
int arr[2][2] = {1, 2, 3, 4};
와 같이 선언해주면
2x2 형태의 행렬로 배열이
선언되는 것이다
값이 할당되는 순서는 차례대로
arr[0][0]과 arr[0][1]에 1과 2가
arr[1][0]과 arr[1][1]에 3과 4가
할당된다
위 ppt의 stack 메모리 구조처럼
차곡차곡 쌓이는데
1, 2, 3, 4 순서대로 쌓인다고 생각하면
이해하기 편하다
Smart Pointer
smart pointer에 대해서 알아보자
프로그래머들이 포인터를 쓰면서
가장 많이하는 실수는 바로
메모리 공간 할당 후
free를 해주지않아서
memory leak이 발생하게 하는 것이다
혹은 이미 free를 시켜주었는데
실수로 그 공간에 접근을 시도하는 경우도
종종 발생하는데 이를 Dangling Pointer라고 한다고 한다
이러한 자주 발생하는 실수를 방지하기위해
smart pointer라는 것이 나왔는데
이는 일반 raw pointer를
Encapsulate해주는 pointer로
메모리 공간을 더이상 사용하지 않을때
자동으로 free를 시켜준다고 한다
c++에서 smart pointer는
std::unique_ptr로 선언한다
unique_ptr<vector<int>> vecPtr(new vector<int>());
로 선언해주면
vector<int>의 포인터 변수를
smart pointer로 선언해주게된다
또한 unique_ptr을 사용해서
선언해줄 때는
변수명 뒤에 넘겨주는 파라미터 값으로
데이터타입에 맞는 빈 값을 할당시켜줘야한다
위 예시에선 데이터타입이 vector<int>이므로
실제 vector<int>()를 넘겨준 것이다
unique_ptr을 쓸 때
한 가지 유의해야할 점은
copy가 안된다는 점이다
이 이유는 이 변수를 copy하는 순간
다른 변수들도 vecPtr이 들고있는
메모리 주솟값에 자유롭게 접근이 가능한데
이를 방지하고자하기 위함이다
이렇게 선언된 unique_ptr은
raw pointer를 쓰듯이 사용 가능하며
main함수가 종료돼서
vecPtr이 scope에서 사라지면
자동으로 메모리 공간을 free시켜준다
unique_ptr말고
shared_ptr도 있다
shared_ptr은 unique_ptr과 다르게
copy가 가능한 smart pointer이다
위 코드와 같이
vecPtr1을 선언하고
vecPtr2 = vecPtr1;
과 같이 해주면
vecPtr2와 vecPtr1은
서로 같은 메모리 주소를 가리키기된다
따라서 vecPtr1이나 vecPtr2에
push_back을 하면
같은 공간에 값을 넣어주는 것이다
shared_ptr은
copy된 값들도 포함해서
마지막 shared_ptr이 scope에서
사라지게 될때 메모리에서 free된다
위에서 unique_ptr과 shared_ptr 모두
선언이 꽤나 길고 복잡한데
이를 간소화시켜주려고
make_unique와 make_shared가 나왔다
make_unique와 make_shared를
이용하면 위 코드와 같이
간단하게 포인터변수 선언이 가능하다
하지만 make_unique는 c++14이상
make_shared는 c++11 이상에서만
지원한다고 한다
'강의 > computer science' 카테고리의 다른 글
[ComputerScience] c++의 function overloading (2) | 2024.10.06 |
---|---|
[ComputerScience] c++의 pointer와 reference 3편 (Call by Value, Call by Reference, Reference) (1) | 2024.09.30 |
[ComputerScience] c++의 pointer와 reference 1편 (포인터의 정의, 동적 메모리 할당, 포인터 연산) (0) | 2024.09.29 |
[ComputerScience] c++의 map과 set (1) | 2024.09.23 |
[ComputerScience] c++와 list (0) | 2024.09.23 |