본문 바로가기
C++/뇌를 자극하는 C++

[C++] 챕터 16 - 동적 메모리 할당 : 보다 자유로운 저장 공간

by Minkyu Lee 2023. 6. 14.

// 16-1 : 입력 값의 평균 구하기

#include <iostream>
using namespace std;

int main()
{
    // 개수 입력받기
    int size;
    cout << "몇 개의 정수를 입력?";
    cin >> size;

    int* arr = new int[size]; // 메모리 할당
   
    // 정수 입력 받기
    cout << "\n";
    for (int i = 0; i < size; ++i)
        cin >> arr[i];

    // 평균 계산 출력
    int sum = 0;
    for (int i = 0; i < size; ++i)
    {
        sum += arr[i];
    }
    float ave = (float)sum / (float)size;
    cout << "합 : " << sum << ", 평균 : " << ave << "\n";

    delete[] arr; // 메모리 해제

    return 0;
}

/*
--- 결과 ---
몇 개의 정수를 입력?

1
2
3
합 : 6, 평균 : 2

---
입력 값의 평균 구하기.

동적으로 할당한 메모리는 반드시 스스로 해제해주어야한다.
결자해지의 법칙이다.

// 동적 메모리 할당의 기본
메모리 할당은 컴퓨터로부터 메모리 얻어오기이다.
동적과 정적의 차이는 프로그램 실행전 결정이나 도중 결정이냐이다.

프로그램 실행 후 사용자가 입력해준 값을 통해 할당하는 것이 동적의 예이다.
동적은 할당과 해제 시점이 자유롭다.

// 배열에 변수를 사용한 크기 지정
불가능하다.
상수만 사용하다. 하지만 const 속성을 가진 변수 사용은 가능하다.

// 메모리 해제는 free라고 부른다.

// 또 다른 예
그림 파일을 읽어서 화면에 보여주는 프로그램.
그림 파일의 크기만큼 메모리 할당해야한다. 동적이다.

// 동적 메모리 할당 정리
1. 메모리 할당 후, 주소를 반환한다.
2. 포인터처럼 사용한다.
3. 사용이 끝난 후 해제한다.

*/

 

// 16-2 : new, delete 사용

#include <iostream>
using namespace std;

int main()
{
    int* p = new int; // 할당
    *p = 337; // 값 넣기
    delete p; // 해제

    return 0;
}

/*
--- 결과 ---

---
new, delete, new[], delete[]
[]가 있는 것은 배열을 동적으로 할당하는 것이다.
별개의 연산자이다.

위는 new, delete 예제이다.

p는 변수를 가리키는 포인터처럼 사용하면 된다.
하지만 일반적으로 new, delete는 int와 같은 기본타입 할당에 사용하지 않는다.
어차피 변수하나만큼만 할당하여, 메모리 동적으로 결정할 필요가 없기 때문이다.
그냥 복사해 사용하는 것이 합리적이다.

대신에 구조체, 클래스에 빈번하게 사용한다.
변수 하나의 크기가 커서 복사하면 불리하다.

*/

 

// 16-3 : 해제한 메모리 또 해제하기

#include <iostream>
using namespace std;

int main()
{
    short* p = new short[100];
    cout << p << "\n"; // p에 보관한 주소 확인
    delete[] p;
    cout << p << "\n"; // 다시 확인. 메모리는 해제되었지만 값에는 변화가 없다. 실수하게 만드는 요인이다.
    delete[] p; // 중복 해제. 오류가 뜬다.
    return 0;
}

/*
--- 결과 ---
000001E0AC5CE3D0
0000000000008123

---
책과 달리 내 컴퓨터에는 오류가 뜨지 않았다.
하지만 경고는 떴다.

메모리 해제한다고 해서 포인터 변수까지 정리하는 것은 아니다.
NULL로 초기화 해주는 것은 자신의 몫이다.
아무것도 가리키지 않는 포인터는 항상 NULL이 들어있어야한다.
따라서 항상 해제 후 NULL을 넣어주어라.
*/

 

// 16-4 : NULL 사용해서 안전하게 만들기

#include <iostream>
using namespace std;

int main()
{
    short* p = new short[100];
    cout << p << "\n";
    delete[] p;
    p = NULL; // 해제 후 NULL로 초기화.

    cout << p << "\n";

    delete[] p; // 해제 후 NULL로 초기화하여 오류가 없어진다. delete는 NULL값에 안전하기 때문이다.
    p = NULL;

    return 0;
}

/*
--- 결과 ---
00000292BC21DF80
0000000000000000

---
delete, delete[] 연산자는 알아서 NULL을 처리한다.
그래서 안전하다.

*/

 

// 16-5 : 문자열을 뒤집어 복사해주는 함수

#include <iostream>
using namespace std;

char* ReverseString(const char* src, int len)
{
    char* reverse = new char[len + 1]; // 메모리 할당
    for (int i = 0; i < len; ++i) // 역순 복사
        reverse[i] = src[len - i - 1];
    reverse[len] = NULL; // 문자열 끝에 NULL 넣는다.

    return reverse;
}

int main()
{
    char original[] = "NEMODORI"; // 문자열 생성
    char* copy = ReverseString(original, 8); // 함수 호출

    // 출력
    cout << original << "\n";
    cout << copy << "\n";

    // 문자열 메모리 해제
    delete[] copy;
    copy = NULL;

    return 0;
}

/*
--- 결과 ---
NEMODORI
IRODOMEN

---
문자열 뒤집어서 복사해주는 함수.
문자열의 끝에 NULL 넣는 것 주의하라.

*/

 

// 16-6 : 배열을 사용한 실험

#include <iostream>
using namespace std;

char* ReverseString(const char* src, int len)
{
    char reverse[100]; // 충분히 큰 공간을 마련한다. (실제로 좋지 않은 방법이다.)
    for (int i = 0; i < len; ++i)
        reverse[i] = src[len - i - 1];
    reverse[len] = NULL;

    return reverse;
}

int main()
{
    char original[] = "NEMODORI";
    char* copy = ReverseString(original, 8);

    cout << original << "\n";
    cout << copy << "\n";

    return 0;
}

/*
--- 결과 ---
NEMODORI
儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆i

---
배열을 사용한 실험.

동적 메모리 할당이 아닌 배열 사용시.
결과를 보면 이상한 문자가 나온다.
이미 소멸된 공간에 접근했기 때문이다.
즉, ReverseFunction 함수가 종료되면서 reverse 배열이 같이 소멸되어 사용 공간이 아니기 때문이다.

*/

 

댓글