배움 저장소

[코테용C++ 핵심 정리] 챕터4. 메모리 본문

Programming Language/C++

[코테용C++ 핵심 정리] 챕터4. 메모리

시옷지읏 2023. 11. 18. 07:17

포인터


ampersand &

int a = 1;
cout << &a << endl;

int *b = &a;

 

deference operator *

자료형이 정해져있으므로 b 주소에서 값을 가져올 수 있다.

cout << *b << endl;
*b = 123;

 

 

포인터의 초기값

nullptr 혹은 0을 사용한다.

double *c = 0; // nullptr;

 

포인터의 크기

포인터의 크기는 동일하다. 

집 크기가 크다고 해서 주소가 더 길어지는 건 아니다.

cout << sizeof(int*) << endl;    // 8byte
cout << sizeof(double*) << endl; // 8byte

 

포인터 디버깅

16진수인 포인터를 확인하기위해 10진수로 변환하자. size_t로 형변환해주면 된다.

int* b = 0;
cout << size_t(b) << endl;

 

포인터 연산과 배열

포인터 + 1의 연산포인터 주소 + 자료형 크기와 동일하다

int* b = 0;
cout << size_t(b) << endl;   // 0
cout << size_t(b+1) << endl; // 4
cout << size_t(b+2) << endl; // 8

 

double* c = 0;
cout << size_t(c) << endl;   // 0
cout << size_t(c+1) << endl; // 8
cout << size_t(c+2) << endl; // 16

 

- 포인터를 배열처럼 사용할 수 있다.

- 배열의 이름은 포인터이다. 배열의 이름은 배열의 첫 번째 원소의 주소를 가리킨다.

-  배열의 인덱싱은 배열의 첫 번째 원소로부터 몇 번째 떨어져있는가를 가리킨다.

 

배열과 포인터

배열은 포인터다. 따라서 ampersand (address of) 연산자를 붙일 필요가 없다.

char strs[] = {'h', 'e', 'l', 'l', 'o'};
char *ptr = strs; // no ampersand
cout << *(strs+3) << endl;

 

포인터를 배열처럼 사용하기

cout << *(ptr + 4) << endl;
cout << *(strs + 4) << endl;


cout << ptr[4] << endl;

 

함수


함수의 반환값은 하나이므로 포인터를 활용하여 함수에서 여러 개의 반환값을 받은 것처럼 사용할 수 있다.

void Add(int a, int b, int *c, int *d)
{
    *c = a + b;
    *d = a - b;
}

int main()
{
    int sum;
    int sub;
    Add(1,2, &sum, &sub);
    
    cout << sum << endl;
    cout << sub << endl;
}

 

문자열 비교


const

c++ keyword로 변하지 않는 값을 지정한다. 상수라고 한다.

const int kMaxStr = 100; // 상수 전역 변수

 

함수 IsEqual은 매개변수 str1과 str2를 가진다. 이 때 두 매개변수는 포인터이다. 따라서 배열의 길이를 모른다.

bool IsEqual(const char str1[], const char str2[]);// 동일
bool IsEqual(const char *str1, const char *str2);  // 동일

 

쓰레기값 예외처리

bool IsEqual(const char *str1, const char *str2)
{
    for (int i = 0; i < kMaxStr; ++i)
    {
        if (str1[i] != str2[i])
            return false;

        if (str1[i] == '\0')
            return true;
    }

	return true;
}

int main()
{
    const char str1[kMaxStr] = "stop";

    while (1)
    {
        char input[kMaxStr]; 
        cin >> input;		// 남은 공간은 쓰레기값이 들어있다.
 
        if(IsEqual(str1, input))
        {
            break;
        }
    }
 
    return 0;
}

 

동적할당


문자열 복사

서로 다른 포인터에게 아래와 같은 방법으로 해당 주소의 값을 복사하라고 명령할 수 없다.

const int kMaxStr = 100;

int main()
{
    char str1[] = "Hello, World!";
    char str2[kMaxStr];
    
    str2 = str1; // Error!
}

위의 결과값으로 str2에 str1의 주소값을 넣어주게 된다.

 

const int kMaxStr = 100;

size_t Min(int a, int b) // sizeof의 결과값은 size_t이다.
{
    return a < b ? a : b;
}

int main()
{
    char str1[] = "Hello, World!";
    char str2[kMaxStr];
    
    //     dest, src ,         복사할 메모리 크기
    memcpy(str2, str1, Min(sizeof(str1), sizeof(str2)));
    cout << str2 << endl; // "Hello, World!"

}

 

정적할당(Static Allocation) VS 동적할당(Dynamic Allocation)

  Static Allocation Dynamic Allocation
큰 용량의 저장공간을 할당받을 수 있나? X O
메모리 사용 용량의 변화가 가능한가? X O

 

동적할당(Dynamic Allocation)

"new" keyword를 사용하여 사용하고 싶은 저장공간의 크기를 할당받는다.

char *dynamic_array = new char[kMaxStr];

OS는 비어있는 공간을 찾아 해당 메모리(저장공간)의 첫 주소를 반환한다.

 

메모리 해제

사용이 끝난 메모리는 반드시 돌려주자.

메모리 해제에 저장공간의 크기를 설정하지 않는 이유

OS는 이미 알고있다.

delete[] dynamic_array; // 배열 삭제시 []

 

동적할당을 활용한 문자열 복사

// 문자열 복사
char str1[] = "Hello, World!";

char *dynamic_array = new char[kMaxStr];

// 주의: 동적할당 메모리는 변수 사이즈가 포인터 사이즈임 (배열이 아님)
memcpy(dynamic_array, str1, Min(sizeof(str1), sizeof(kMaxStr)));
// memcpy(dynamic_array, str2, kMaxStr);
cout << dynamic_array << endl;

 

동적할당 메모리의 주소

동적할당 메모리의 주소값이 크다. 메모리 레이아웃에서 정적할당은 Stack 동적할당은 Heap에 위치한다.

cout << size_t(str1) << endl;           // 480006108968
cout << size_t(str2) << endl;           // 480006109024
cout << size_t(dynamic_array) << endl;  // 2461430842080

 

해제되지 않은 메모리의 재할당

(1) 메모리 할당을 받고 주소값을 dynamic_array에 저장하였다.

(2) 위 (1)에서 할당받은 동적 메모리를 해제하지 않고 새로운 동적 메모리를 할당받았다.

이 때 (1)에서 할당받은 메모리는 접근할 수 없다. 해제할 수 없다.

따라서 메모리를 동일한 포인터에 재할당 받고 싶다면 기존에 부여받은 동적할당 저장공간을 해제해주자

Comments