본문 바로가기

개발공부

2024 -12 - 23 c++ 자원 관리

 

 

메모리의 공간은 한정되어있습니다.

메모리가 쌓이면 

 

결국 문제가 생길 것입니다.

 

그래서 공간을 효율적으로 사용해야합니다.,

 

스택 메모리

 

일반 변수들은 대부분 스택 메모리공간을 차지합니다.

특징은 변수의 생존 주기가 끝나면 변수 선언시 할당되었던메모리고 저절로

회수 된다는 것입니다.

사용자가 따로 메모리를 관리해줄 필요가 없습니다.!!

중괄호를 벗어나면 사라집니다.{   }

 

대부분의 지역 변수는 스택메모리에서 관리됨.

 

단점

 

 

  • 메모리 영역 자체가 크지않습니다.
  • 메모리생존 영역을 벗어나면 자동 해지됩니다.

 

힙 메모리

 

스택의 단점은 힙메모리로 해결 합니다.

 

스택 처럼 자동해지 되지 않습니다.

생존주기가 선언한 순간부터 해지하기 전까지입니다.

 

new - delete

순으로 생성되었으면 해지할때까지 계속 살아있습니다.

 

 #include <iostream>
 using namespace std;
 
 void testfunc()
 {
     //포인터는 힙 메모리를 사용합니다.
     int* ptr = new int(10);
     cout << "ptr : " << *ptr << endl;
     delete ptr; // 메모리 해제 합니다.
 }
 
 int main(){
 	testfunc();
    return 0;
}

 

 

정적 배열은 크기를 컴파일 타임에 정해주고 스택에 할당됩니다.
동적 배열은 크기를 런타임에 정해주고 힙에 할당됩니다, 이때 포인터를 사용하여 배열의 첫 번째 요소를 가리키게 됩니다.
이렇게 포인터를 사용하면 배열을 동적으로 생성하고, 필요에 따라 크기를 조절할 수 있는 유연성을 제공합니다. 

 

#include <iostream>
using namespace std;



void testarraypointer(){
	int size = 5;
       
    int* arr = new int [size]; 
    
    for (int i = 0; i < size; ++i) {
            arr[i] = i;  // 배열 초기화
        }
        
        
    
int main() {
    testarraypointer();
    return 0;
}

 

포인터에서는 배열을 이렇게 사용할 수 있습니다.

 

일반 변수에서는 

 

int inti[] = {1,2,3};

 

이렇게 대괄호가 있지만

 

포인트에서는

 

int* arr = new int[n];

 

이렇게 대괄호 없이도 사용할 수 있습니다.

 

배열과 포인터의 동작 방식:

포인터를 사용하여 배열의 메모리 블록에 접근할 수 있는 이유는
 배열의 첫 번째 요소의 주소를 포인터에 저장하기 때문입니다. 
배열의 요소들은 연속적인 메모리 공간에 저장되므로, 포인터를 이용하여 쉽게 접근할 수 있습니다.

 

글쓴이의 생각


그러면 배열은 예로들면 맨앞 주소가 55이면 그뒤로 56 57 58 이렇게
바로 뒤에 연결 되어 있고
포인터 변수에서 배열을 만들되!!
배열의 첫번째 위치의 주소값을 저장해서
배열의 뒤에있는 값들을 찾을 수 있다는 것
포인터의 주소는 맨앞 배열 주소고
그뒤의 변수들은 맨앞 주소 바탕으로 움직인다는 것

 

더보기

여러가지 경우를 생각해보니

int* a = new int [2];

int* aa = a[2];

이렇게 하면어떻게 될까 했는데

 

이럴 경우 a[2]는 주소값이 아닌  *a[2] 하듯이 값을 전달 하게 되었다!!

 

그리고 알아본결과

 

배열은 기본적으로 포인터와 유사한 동작을 합니다. 배열의 요소에 접근할 때는 인덱스를 사용하지만, 배열 이름 자체가 첫 번째 요소의 주소를 가리키기 때문에 포인터처럼 행동합니다.

 

배열은 포인터와 유사한 방식으로 사용 되는 것 같습니다.

 

배열도 배열안에 있는 값의 주소를전달해주는 역할로 사용된 것 같습니다.

 

Dangling Pointer

 

해지(delete)된 포인터를 사용하려 하면 해당 값에 든게 없다는 오류가 나올겁니다.

b가 a의 주소를 가지고 있는상태에서 delete를 하면
a의 값이 사라진거고
b는 아직 a의 주소를 가르키고 있으니
b는 아무도 없는 주소를 가리킨다고 생각하면 됩니다.
int main(){
	int* ptr = new int(3);
    
    delete ptr;
   
   
   //이경우 오류 발생
    cout << *ptr <<endl;
    return 0;
   }

 

 

스마트 포인터

 

힙 메모리는 용량이크지만 메모리를 직접 관리해야하는 부담이있습니다.

Dangling Pointer가 발생하지않게 하기위해 c++에서 스마트 포인터를 사용할 수 있습니다.!!

 

원리는 레퍼런스 카운터입니다.

delete를 직접 하는대신

자신을 참조하고 있는 포인터의 개수가 0이 되면 자동으로 해지하는 방식입니다.

 

unique_ptr은 레퍼런스 카운터가 최대 1인 스마트 포인터 입니다.

소유권을 주는 건 가능하나 동시에 두 개 이상 소유 할 수 없습니다.

 

최대 레퍼런스 카운터가 1이기 때문에 복사 혹은 대입이 되지 않습니다. 이걸 시도하는 순간 컴파일 에러가 발생 합니다.

 

#include <iostream>
#include <memory> // unique_ptr 사용
using namespace std;


int main(){
 	unique_ptr<int> ptr1 = make_unique<int>(10);
	
    cout << *ptr <<endl;
    
    // unique_ptr은 복사가 불가능
    // unique_ptr<int> ptr2 = ptr1; // 컴파일 에러 발생
    
    return 0;
    }

 

위의 코드처럼 복사하는 방식을 하면

가리키고 있는 포인터가 2개가 되니

안되지만 소유권 이전은 가능합니다

 

.

#include <iostream>
#include <memory>
using namespace std;

int main() {
    
    unique_ptr<int> ptr1 = make_unique<int>(10);
    
    unique_ptr<int> ptr2 = move(ptr1);
    
    if(!ptr1)
    { cout <<"ptr1이 없습니다." <<endl;}
    
    cout << *ptr2 <<endl;
    return 0;
    
    }

 

추가적인 조사
기본적인 unique_ptr 선언
    std::unique_ptr<int> ptr(new int(5));

 make_unique를 사용한 선언
    std::unique_ptr<int> ptr = std::make_unique<int>(42); // C++14 이상



중요!!
int a = 42;
    
오류 발생함
    // 이 코드는 컴파일 오류가 발생합니다.
    // std::unique_ptr<int> ptr = &a; // 오류: unique_ptr는 동적 메모리 소유권을 관리해야 함


이건 가능함
// a의 값을 동적으로 할당하여 unique_ptr로 관리
    std::unique_ptr<int> ptr = std::make_unique<int>(a); // a의 값을 복사




 

즉 동일한 주소를 가지는걸 방지하는 스마트 포인터 입니다.

 

 

 

shared_ptr

레퍼런스 카운터가 N개가 될수 있는 스마트 포인터 입니다.

그렇기에 복사 혹은 대입이 됩니다.

 

#include <iostream>
#include <memory>
using namespace std;


int main(){

	shared_ptr<int> obj1 = make_shared<int>(22);
    
    shared_ptr<int> obj2 = obj1;
    
    // obj1과 obj2는 같은 주소를 가지고있음
    
     cout <<  obj1.use_count() << endl; // 출력: 2
    
    //obj 해제
    obj2.reset();
    
    cout <<  obj1.use_count() << endl; // 출력: 1

 

여러개의 포인터가 동일한 메모리 블록을 공유할 수 있도록 설계된 포인터 입니다.

 

 

처음에 봤을때는 그냥 포인터 사용하는 것과 뭐가다른건가 싶었지만 조사해본 결과!! 매우 좋습니다.

포인터랑 다르게 생성 주기의 영향을 받고

사용하고 해지하지않아도!!
함수가 끝나면 자동으로 사라지는 방식!!

생성 주기의 영향을 받는 포인터라는 점입니다.

 

본인 생각

함수에 있는 멤버함수
그냥 포인터 a
유니크 포인터 b
쉐어 포인터 c 가 있고
전부 힙에 새로운 변수를 생성시킨상태에서
함수를 벗어나면 포인터 a는 사라지지만 포인터 a가 생성한 메모리는 아무도 찾지 못하고 존재하고
b와 c는 함수에서 벗어나면서 변수자체가 사라지지만 a와 는 다르게
자동으로 delete시켜준다는 점!!

이러면 실수로 delete하지못해도 자동으로 해지된다는 점에서 스마트 포인터를 사용한다는 것을 알게 되었습니다!!

 

 

다음은 얕은 복사와 깊은 복사를 조사해보려합니다.