소개
이 랩에서는 C++ 에서 동적 메모리 할당을 처리하는 방법을 배우게 됩니다. 먼저 new 연산자를 사용하여 메모리를 할당한 다음, delete 연산자를 사용하여 메모리를 해제합니다. 또한 동적 배열 생성, shared_ptr 및 unique_ptr과 같은 스마트 포인터 사용, 메모리 누수 확인도 탐구합니다. 이러한 기술은 C++ 프로그래밍에서 효과적인 메모리 관리에 필수적입니다.
이 랩은 다음 주요 단계를 다룹니다: new 연산자를 사용한 메모리 할당, delete 연산자를 사용한 메모리 해제, new[]를 사용한 동적 배열 생성, delete[]를 사용한 배열 삭제, shared_ptr 스마트 포인터 구현, 독점 소유권을 위한 unique_ptr 사용, 메모리 누수 확인, 그리고 메모리 할당 실패 처리.
new 연산자를 사용하여 메모리 할당
이 단계에서는 C++ 에서 런타임에 new 연산자를 사용하여 동적으로 메모리를 할당하는 방법을 배우게 됩니다. 동적 메모리 할당을 사용하면 프로그램 실행 중에 크기가 결정되는 변수와 배열을 생성할 수 있어 정적 메모리 할당보다 더 많은 유연성을 제공합니다.
WebIDE 를 열고 ~/project 디렉토리에 dynamic_memory.cpp라는 새 파일을 생성합니다.
touch ~/project/dynamic_memory.cpp
dynamic_memory.cpp에 다음 코드를 추가합니다.
#include <iostream>
int main() {
// Dynamically allocate an integer using new
int* dynamicInteger = new int;
// Assign a value to the dynamically allocated integer
*dynamicInteger = 42;
// Print the value of the dynamically allocated integer
std::cout << "Dynamically allocated integer value: " << *dynamicInteger << std::endl;
// Print the memory address of the dynamically allocated integer
std::cout << "Memory address: " << dynamicInteger << std::endl;
return 0;
}
핵심 개념을 자세히 살펴보겠습니다.
int* dynamicInteger = new int;:new연산자는 정수를 위한 메모리를 할당합니다.- 할당된 메모리에 대한 포인터를 반환합니다.
- 스택이 아닌 힙에 변수를 생성합니다.
*dynamicInteger = 42;:- 역참조 연산자
*를 사용하여 값을 할당합니다. - 동적으로 할당된 메모리에 값 42 를 저장합니다.
- 역참조 연산자
프로그램을 컴파일하고 실행합니다.
g++ dynamic_memory.cpp -o dynamic_memory
./dynamic_memory
예시 출력:
Dynamically allocated integer value: 42
Memory address: 0x55f4e8a042a0
new 연산자에 대한 주요 사항:
- 런타임 중에 동적으로 메모리를 할당합니다.
- 할당된 메모리에 대한 포인터를 반환합니다.
- 메모리는 힙에 할당됩니다.
- 크기는 런타임에 결정될 수 있습니다.
- 보다 유연한 메모리 관리를 허용합니다.
참고: 다음 단계에서는 메모리 누수를 방지하기 위해 이 동적으로 할당된 메모리를 적절하게 해제하는 방법을 배우게 됩니다.
delete 연산자로 메모리 해제
이 단계에서는 C++ 에서 delete 연산자를 사용하여 동적으로 할당된 메모리를 적절하게 해제하는 방법을 배우게 됩니다. 메모리를 해제하지 못하면 메모리 누수가 발생할 수 있으며, 이는 프로그램의 성능 문제를 일으킬 수 있습니다.
WebIDE 를 열고 ~/project 디렉토리에 memory_release.cpp라는 새 파일을 생성합니다.
touch ~/project/memory_release.cpp
memory_release.cpp에 다음 코드를 추가합니다.
#include <iostream>
int main() {
// Dynamically allocate an integer
int* dynamicInteger = new int;
// Assign a value to the dynamically allocated integer
*dynamicInteger = 42;
// Print the value before releasing memory
std::cout << "Value before release: " << *dynamicInteger << std::endl;
// Release the dynamically allocated memory using delete
delete dynamicInteger;
// Set the pointer to nullptr after deletion
dynamicInteger = nullptr;
// Demonstrate safe pointer usage
if (dynamicInteger == nullptr) {
std::cout << "Memory has been successfully released" << std::endl;
}
return 0;
}
delete 연산자에 대한 주요 사항:
delete dynamicInteger;:- 이전에
new로 할당된 메모리를 해제합니다. - 메모리 누수를 방지합니다.
- 메모리를 시스템에 반환합니다.
- 이전에
dynamicInteger = nullptr;:- 삭제 후 포인터를
nullptr로 설정합니다. - 해제된 메모리의 우발적인 사용을 방지하는 데 도움이 됩니다.
- 추가적인 안전 점검을 제공합니다.
- 삭제 후 포인터를
프로그램을 컴파일하고 실행합니다.
g++ memory_release.cpp -o memory_release
./memory_release
예시 출력:
Value before release: 42
Memory has been successfully released
중요한 메모리 관리 규칙:
new로 할당된 메모리에는 항상delete를 사용합니다.- 삭제 후 포인터를
nullptr로 설정합니다. - 삭제된 후에는 포인터를 사용하지 마십시오.
- 각
new에는 해당delete가 있어야 합니다.
new[] 를 사용하여 동적 배열 생성
이 단계에서는 C++ 에서 new[] 연산자를 사용하여 동적 배열을 만드는 방법을 배우게 됩니다. 동적 배열을 사용하면 런타임에 여러 요소에 대한 메모리를 할당할 수 있어 정적 배열보다 더 많은 유연성을 제공합니다.
WebIDE 를 열고 ~/project 디렉토리에 dynamic_array.cpp라는 새 파일을 생성합니다.
touch ~/project/dynamic_array.cpp
dynamic_array.cpp에 다음 코드를 추가합니다.
#include <iostream>
int main() {
// Declare the size of the dynamic array
int arraySize = 5;
// Create a dynamic integer array using new[]
int* dynamicArray = new int[arraySize];
// Initialize array elements
for (int i = 0; i < arraySize; ++i) {
dynamicArray[i] = i * 10;
}
// Print array elements
std::cout << "Dynamic Array Contents:" << std::endl;
for (int i = 0; i < arraySize; ++i) {
std::cout << "Element " << i << ": " << dynamicArray[i] << std::endl;
}
return 0;
}
동적 배열에 대한 주요 사항:
int* dynamicArray = new int[arraySize];:- 정수 배열에 대한 메모리를 할당합니다.
arraySize는 요소의 수를 결정합니다.- 메모리는 힙에 할당됩니다.
- 크기는 런타임에 결정될 수 있습니다.
배열 초기화 및 접근:
- 표준 배열 인덱싱
dynamicArray[i]를 사용합니다. - 일반 배열처럼 조작할 수 있습니다.
- 런타임 조건에 따라 동적 크기 조정을 허용합니다.
- 표준 배열 인덱싱
프로그램을 컴파일하고 실행합니다.
g++ dynamic_array.cpp -o dynamic_array
./dynamic_array
예시 출력:
Dynamic Array Contents:
Element 0: 0
Element 1: 10
Element 2: 20
Element 3: 30
Element 4: 40
중요 고려 사항:
- 동적 배열은 메모리 할당의 유연성을 제공합니다.
- 크기는 런타임에 결정될 수 있습니다.
- 할당에는
new[]를 사용하고, 할당 해제에는delete[]를 사용하는 것을 잊지 마십시오. - 항상 할당 및 할당 해제 방법을 일치시키십시오.
delete[] 를 사용하여 배열 삭제
이 단계에서는 delete[] 연산자를 사용하여 동적 배열에 할당된 메모리를 적절하게 해제하는 방법을 배우게 됩니다. 적절한 메모리 관리는 메모리 누수를 방지하고 효율적인 리소스 활용을 보장하는 데 매우 중요합니다.
WebIDE 를 열고 ~/project 디렉토리에 delete_array.cpp라는 새 파일을 생성합니다.
touch ~/project/delete_array.cpp
delete_array.cpp에 다음 코드를 추가합니다.
#include <iostream>
int main() {
// Declare the size of the dynamic array
int arraySize = 5;
// Create a dynamic integer array using new[]
int* dynamicArray = new int[arraySize];
// Initialize array elements
for (int i = 0; i < arraySize; ++i) {
dynamicArray[i] = i * 10;
}
// Print array elements before deletion
std::cout << "Array Contents Before Deletion:" << std::endl;
for (int i = 0; i < arraySize; ++i) {
std::cout << "Element " << i << ": " << dynamicArray[i] << std::endl;
}
// Release the dynamically allocated array using delete[]
delete[] dynamicArray;
// Set the pointer to nullptr after deletion
dynamicArray = nullptr;
// Demonstrate safe pointer usage
if (dynamicArray == nullptr) {
std::cout << "Dynamic array has been successfully deleted" << std::endl;
}
return 0;
}
동적 배열 삭제에 대한 주요 사항:
delete[] dynamicArray;:- 전체 배열에 대한 메모리를 적절하게 해제합니다.
new[]로 할당된 배열에는 반드시delete[]를 사용해야 합니다.- 메모리 누수를 방지합니다.
- 각 배열 요소에 대해 소멸자를 호출합니다.
dynamicArray = nullptr;:- 삭제 후 포인터를
nullptr로 설정합니다. - 해제된 메모리에 대한 우발적인 접근을 방지합니다.
- 추가적인 안전 점검을 제공합니다.
- 삭제 후 포인터를
프로그램을 컴파일하고 실행합니다.
g++ delete_array.cpp -o delete_array
./delete_array
예시 출력:
Array Contents Before Deletion:
Element 0: 0
Element 1: 10
Element 2: 20
Element 3: 30
Element 4: 40
Dynamic array has been successfully deleted
중요한 메모리 관리 규칙:
new[]로 할당된 배열에는 항상delete[]를 사용합니다.delete와delete[]를 혼용하지 마십시오.- 삭제 후 포인터를
nullptr로 설정합니다. - 각
new[]에는 해당delete[]가 있어야 합니다.
스마트 포인터 shared_ptr 구현
이 단계에서는 자동 메모리 관리 및 참조 카운팅을 제공하는 C++ 표준 라이브러리의 스마트 포인터인 shared_ptr에 대해 배우게 됩니다. 스마트 포인터는 메모리 누수를 방지하고 메모리 관리를 단순화하는 데 도움이 됩니다.
WebIDE 를 열고 ~/project 디렉토리에 shared_pointer.cpp라는 새 파일을 생성합니다.
touch ~/project/shared_pointer.cpp
shared_pointer.cpp에 다음 코드를 추가합니다.
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass(int value) : data(value) {
std::cout << "Constructor called. Value: " << data << std::endl;
}
~MyClass() {
std::cout << "Destructor called. Value: " << data << std::endl;
}
int getData() const { return data; }
private:
int data;
};
int main() {
// Create a shared_ptr to MyClass
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(42);
// Create another shared_ptr pointing to the same object
std::shared_ptr<MyClass> ptr2 = ptr1;
// Print reference count
std::cout << "Reference Count: " << ptr1.use_count() << std::endl;
// Access object through shared_ptr
std::cout << "Data from ptr1: " << ptr1->getData() << std::endl;
std::cout << "Data from ptr2: " << ptr2->getData() << std::endl;
// Pointers will automatically free memory when they go out of scope
return 0;
}
shared_ptr에 대한 주요 사항:
std::make_shared<MyClass>(42):- 동적 메모리 할당으로 shared pointer 를 생성합니다.
- 생성자 인수로 객체를 초기화합니다.
- 별도의
new및shared_ptr생성보다 효율적입니다.
ptr1.use_count():- 동일한 객체를 가리키는 shared_ptr 인스턴스의 수를 반환합니다.
- 객체의 참조 횟수를 추적하는 데 도움이 됩니다.
자동 메모리 관리:
- shared_ptr 이 객체를 참조하지 않을 때 메모리가 자동으로 해제됩니다.
- 수동 메모리 관리 오류를 방지합니다.
프로그램을 컴파일하고 실행합니다.
g++ -std=c++11 shared_pointer.cpp -o shared_pointer
./shared_pointer
예시 출력:
Constructor called. Value: 42
Reference Count: 2
Data from ptr1: 42
Data from ptr2: 42
Destructor called. Value: 42
중요한 shared_ptr 특징:
- 자동 메모리 관리
- 참조 카운팅
- 메모리 누수 방지
- 스레드 안전한 참조 카운팅
독점 소유권을 위한 unique_ptr 사용
이 단계에서는 동적으로 할당된 리소스에 대한 독점적인 소유권을 제공하는 스마트 포인터인 unique_ptr에 대해 배우게 됩니다. shared_ptr과 달리 unique_ptr은 한 번에 하나의 포인터만 리소스를 소유할 수 있도록 보장합니다.
WebIDE 를 열고 ~/project 디렉토리에 unique_pointer.cpp라는 새 파일을 생성합니다.
touch ~/project/unique_pointer.cpp
unique_pointer.cpp에 다음 코드를 추가합니다.
#include <iostream>
#include <memory>
class Resource {
public:
Resource(int value) : data(value) {
std::cout << "Resource created. Value: " << data << std::endl;
}
~Resource() {
std::cout << "Resource destroyed. Value: " << data << std::endl;
}
int getData() const { return data; }
private:
int data;
};
void processResource(std::unique_ptr<Resource> resource) {
std::cout << "Processing resource with value: " << resource->getData() << std::endl;
// Resource will be automatically deleted when function ends
}
int main() {
// Create a unique_ptr
std::unique_ptr<Resource> ptr1 = std::make_unique<Resource>(42);
// Print resource data
std::cout << "Resource value: " << ptr1->getData() << std::endl;
// Transfer ownership using std::move
std::unique_ptr<Resource> ptr2 = std::move(ptr1);
// ptr1 is now nullptr
if (ptr1 == nullptr) {
std::cout << "ptr1 is now null after moving ownership" << std::endl;
}
// Pass unique_ptr to a function (ownership is transferred)
processResource(std::move(ptr2));
return 0;
}
unique_ptr에 대한 주요 사항:
std::make_unique<Resource>(42):- 동적 메모리 할당으로 unique pointer 를 생성합니다.
- 리소스에 대한 독점적인 소유권을 보장합니다.
std::move(ptr1):- 리소스의 소유권을 이전합니다.
- 원래 포인터는
nullptr이 됩니다. - 여러 포인터가 동일한 리소스를 소유하는 것을 방지합니다.
자동 메모리 관리:
- unique_ptr 이 범위를 벗어날 때 리소스가 자동으로 삭제됩니다.
- 수동 메모리 관리가 필요하지 않습니다.
프로그램을 컴파일하고 실행합니다.
g++ -std=c++14 unique_pointer.cpp -o unique_pointer
./unique_pointer
예시 출력:
Resource created. Value: 42
Resource value: 42
ptr1 is now null after moving ownership
Processing resource with value: 42
Resource destroyed. Value: 42
중요한 unique_ptr 특징:
- 독점적인 소유권
- 자동 메모리 관리
- 복사할 수 없고, 이동만 가능
- 리소스 누수 방지
메모리 누수 확인
이 단계에서는 강력한 메모리 디버깅 도구인 Valgrind 를 사용하여 C++ 에서 메모리 누수를 감지하고 방지하는 방법을 배우게 됩니다. 메모리 누수는 동적으로 할당된 메모리가 제대로 해제되지 않아 프로그램이 점점 더 많은 메모리를 소비하게 될 때 발생합니다.
먼저 터미널에서 Valgrind 를 설치합니다.
sudo apt update
sudo apt install -y valgrind
WebIDE 에서 ~/project 디렉토리에 memory_leak.cpp를 생성하여 메모리 누수 예제를 만듭니다.
touch ~/project/memory_leak.cpp
memory_leak.cpp에 다음 코드를 추가합니다.
#include <iostream>
class Resource {
public:
Resource(int value) : data(value) {
std::cout << "Resource created: " << data << std::endl;
}
~Resource() {
std::cout << "Resource destroyed: " << data << std::endl;
}
private:
int data;
};
void createMemoryLeak() {
// This creates a memory leak because the resource is not deleted
Resource* leak = new Resource(42);
// Missing: delete leak;
}
int main() {
// Simulate multiple memory leaks
for (int i = 0; i < 3; ++i) {
createMemoryLeak();
}
return 0;
}
디버깅 심볼과 함께 프로그램을 컴파일합니다.
g++ -g memory_leak.cpp -o memory_leak
Valgrind 를 실행하여 메모리 누수를 감지합니다.
valgrind --leak-check=full ./memory_leak
Valgrind 출력 예시:
==1988== Memcheck, a memory error detector
==1988== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==1988== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==1988== Command: ./memory_leak
==1988==
Resource created: 42
Resource created: 42
Resource created: 42
==1988==
==1988== HEAP SUMMARY:
==1988== in use at exit: 12 bytes in 3 blocks
==1988== total heap usage: 5 allocs, 2 frees, 73,740 bytes allocated
==1988==
==1988== 12 bytes in 3 blocks are definitely lost in loss record 1 of 1
==1988== at 0x4849013: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==1988== by 0x109241: createMemoryLeak() (memory_leak.cpp:19)
==1988== by 0x109299: main (memory_leak.cpp:26)
==1988==
==1988== LEAK SUMMARY:
==1988== definitely lost: 12 bytes in 3 blocks
==1988== indirectly lost: 0 bytes in 0 blocks
==1988== possibly lost: 0 bytes in 0 blocks
==1988== still reachable: 0 bytes in 0 blocks
==1988== suppressed: 0 bytes in 0 blocks
==1988==
==1988== For lists of detected and suppressed errors, rerun with: -s
==1988== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
보시다시피 Valgrind 는 3 개의 블록에서 12 바이트의 메모리 누수를 감지했습니다. createMemoryLeak()에서 생성된 Resource 객체가 삭제되지 않아 메모리 누수가 발생했습니다.
메모리 누수를 수정하려면 코드를 수정하여 리소스를 제대로 삭제합니다.
void createMemoryLeak() {
// Properly delete the dynamically allocated resource
Resource* leak = new Resource(42);
delete leak; // Add this line to prevent memory leak
}
Valgrind 로 프로그램을 다시 컴파일하고 실행하여 메모리 누수가 수정되었는지 확인합니다.
g++ -g memory_leak.cpp -o memory_leak
valgrind --leak-check=full ./memory_leak
==2347== Memcheck, a memory error detector
==2347== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2347== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==2347== Command: ./memory_leak
==2347==
Resource created: 42
Resource destroyed: 42
Resource created: 42
Resource destroyed: 42
Resource created: 42
Resource destroyed: 42
==2347==
==2347== HEAP SUMMARY:
==2347== in use at exit: 0 bytes in 0 blocks
==2347== total heap usage: 5 allocs, 5 frees, 73,740 bytes allocated
==2347==
==2347== All heap blocks were freed -- no leaks are possible
==2347==
==2347== For lists of detected and suppressed errors, rerun with: -s
==2347== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
메모리 누수에 대한 주요 사항:
- 동적으로 할당된 메모리는 항상
delete합니다. unique_ptr및shared_ptr과 같은 스마트 포인터를 사용합니다.- Valgrind 를 사용하여 메모리 누수를 감지합니다.
- 더 나은 디버깅 정보를 위해
-g플래그로 컴파일합니다.
메모리 할당 실패 처리
이 단계에서는 C++ 에서 메모리 할당 실패를 처리하는 방법을 배우게 됩니다. 동적 메모리 할당이 실패하면 new 연산자는 std::bad_alloc 예외를 발생시키며, 이를 catch 하여 적절하게 처리할 수 있습니다.
WebIDE 를 열고 ~/project 디렉토리에 memory_allocation.cpp라는 새 파일을 생성합니다.
touch ~/project/memory_allocation.cpp
memory_allocation.cpp에 다음 코드를 추가합니다.
#include <iostream>
#include <new>
#include <limits>
void demonstrateMemoryAllocation() {
try {
// Attempt to allocate a large amount of memory
const size_t largeSize = 1000000000000; // 1 trillion integers
int* largeArray = new int[largeSize];
// This line will not be reached if allocation fails
std::cout << "Memory allocation successful" << std::endl;
// Clean up allocated memory
delete[] largeArray;
}
catch (const std::bad_alloc& e) {
// Handle memory allocation failure
std::cerr << "Memory allocation failed: " << e.what() << std::endl;
}
}
void safeMemoryAllocation() {
// Use std::nothrow to prevent exception throwing
int* safeArray = new(std::nothrow) int[1000000];
if (safeArray == nullptr) {
std::cerr << "Memory allocation failed silently" << std::endl;
return;
}
// Use the allocated memory
std::cout << "Safe memory allocation successful" << std::endl;
// Clean up allocated memory
delete[] safeArray;
}
int main() {
std::cout << "Demonstrating memory allocation failure handling:" << std::endl;
// Method 1: Using exception handling
demonstrateMemoryAllocation();
// Method 2: Using std::nothrow
safeMemoryAllocation();
return 0;
}
메모리 할당 실패 처리에 대한 주요 사항:
std::bad_alloc을 사용한 예외 처리:try-catch블록은 메모리 할당 예외를 catch 합니다.- 자세한 오류 정보를 제공합니다.
- 프로그램 충돌을 방지합니다.
std::nothrow할당:- 예외 발생을 방지합니다.
- 할당 실패 시
nullptr을 반환합니다. - 조용한 실패 처리를 허용합니다.
프로그램을 컴파일하고 실행합니다.
g++ memory_allocation.cpp -o memory_allocation
./memory_allocation
예시 출력:
Demonstrating memory allocation failure handling:
Memory allocation failed: std::bad_alloc
Safe memory allocation successful
중요한 메모리 할당 전략:
- 항상 할당 실패를 확인합니다.
- 예외 처리 또는
std::nothrow를 사용합니다. - 대체 메커니즘을 구현합니다.
- 매우 큰 메모리 블록 할당을 피합니다.
요약
이 랩에서는 C++ 에서 동적 메모리 할당을 처리하는 방법을 배우게 됩니다. 먼저, 런타임에 new 연산자를 사용하여 동적으로 메모리를 할당하는 방법을 배우고, 이를 통해 정적 할당에 비해 더 유연한 메모리 관리가 가능해집니다. 그런 다음, 메모리 누수를 방지하기 위해 delete 연산자를 사용하여 이 동적으로 할당된 메모리를 적절하게 해제하는 방법을 배우게 됩니다. 또한, new[]를 사용하여 동적 배열을 생성하고 delete[]로 삭제하는 방법을 살펴봅니다. 이 랩에서는 또한 shared_ptr 및 unique_ptr과 같은 스마트 포인터를 사용하여 메모리 관리를 단순화하고 일반적인 함정을 피하는 방법을 다룹니다. 마지막으로, 메모리 할당 실패를 확인하고 처리하는 기술을 배우게 됩니다.



