const는 "변경할 수 없는 값"을 의미하며, 언어마다 사용 방식이 다릅니다. Java는 final, Python은 const가 없고 관례로 처리하며, 나머지 언어는 const 또는 유사 키워드를 사용합니다.
아래는 각 언어별 const 또는 상수 정의 방식입니다:
💡 공통 개념: const란?
- 변경 불가능한 값을 정의할 때 사용
- 주로 설정값, 수학 상수, 고정 문자열 등에 사용
- 컴파일러나 인터프리터가 값 변경을 막아줌
🧑💻 언어별 사용 방법
✅ Java
- const 키워드는 존재하지만 사용되지 않음
- 대신 final 키워드를 사용해 상수를 정의
final int MAX_SIZE = 100;
final String APP_NAME = "MyApp";
- 클래스 수준 상수는 static final로 선언
public static final double PI = 3.14159;
✅ JavaScript
- const는 ES6부터 도입
- 재할당 불가, 블록 스코프
const MAX_USERS = 50;
const user = { name: "Alice" };
user.name = "Bob"; // 가능 (참조는 고정, 내부 변경 가능)
✅ C#
- const와 readonly 두 가지 방식 존재
const int MAX_SIZE = 100; // 컴파일 타임 상수
readonly int maxSize; // 런타임 상수 (생성자에서만 설정 가능)
✅ Python
- const 키워드 없음
- 관례적으로 대문자 변수명으로 상수 표현
MAX_SIZE = 100
PI = 3.14159
- 변경은 가능하지만 개발자 간 약속으로 변경하지 않음
✅ PHP
- const 키워드 사용
- 클래스 내부 또는 전역에서 선언 가능
const MAX_SIZE = 100;
class Config {
const VERSION = "1.0.0";
}
✅ Swift
- let 키워드를 사용해 불변 변수 선언
let maxSize = 100
let appName = "MyApp"
- var은 변경 가능, let은 변경 불가
📌 요약 비교표
언어 키워드 변경 가능 여부 특징
| Java | final | ❌ | 클래스 상수는 static final |
| JavaScript | const | ❌ (참조는 고정) | 객체 내부는 변경 가능 |
| C# | const, readonly | ❌ / 생성자에서만 설정 | 컴파일 vs 런타임 상수 |
| Python | 없음 (관례) | ✅ | 대문자 변수명으로 표현 |
| PHP | const | ❌ | 클래스/전역 모두 가능 |
| Swift | let | ❌ | var과 구분됨 |
Sources:
new 키워드는 대부분의 객체지향 언어에서 새로운 객체를 생성할 때 사용됩니다. Python과 PHP는 예외적으로 new 없이도 객체를 만들 수 있습니다.
아래는 주요 프로그래밍 언어별 new 키워드 사용 방식과 예제입니다:
🧠 new 키워드란?
- 클래스의 인스턴스를 생성할 때 사용
- 메모리에 객체를 할당하고, 생성자를 호출함
- 객체지향 프로그래밍에서 핵심적인 역할
🧑💻 언어별 사용 방법과 예제
✅ Java
- new는 클래스의 객체를 생성할 때 사용
class Car {
String model;
Car(String model) {
this.model = model;
}
}
Car myCar = new Car("Tesla");
- 객체 생성 시 생성자 호출
✅ JavaScript
- new는 생성자 함수 또는 클래스 인스턴스를 만들 때 사용
function Car(make, model) {
this.make = make;
this.model = model;
}
const myCar = new Car("Toyota", "Corolla");
- ES6 이후 클래스 문법에서도 사용
class Car {
constructor(make, model) {
this.make = make;
this.model = model;
}
}
const myCar = new Car("Honda", "Civic");
✅ C#
- new는 클래스, 배열, 구조체 등 객체 생성에 사용
class Car {
public string Model;
public Car(string model) {
Model = model;
}
}
Car myCar = new Car("BMW");
- 인터페이스 구현 객체 생성에도 사용
✅ Python
- new 키워드는 사용하지 않음
- 클래스 이름을 함수처럼 호출하여 객체 생성
class Car:
def __init__(self, model):
self.model = model
my_car = Car("Hyundai")
- 내부적으로 __new__ 메서드가 있지만 직접 사용하지 않음
✅ PHP
- new 키워드를 사용하여 객체 생성
class Car {
public $model;
function __construct($model) {
$this->model = $model;
}
}
$myCar = new Car("Kia");
✅ Swift
- new 키워드는 사용하지 않음
- 클래스나 구조체 이름을 함수처럼 호출
class Car {
var model: String
init(model: String) {
self.model = model
}
}
let myCar = Car(model: "Genesis")
📌 요약 비교표
언어 new 사용 여부 객체 생성 방식
| Java | ✅ 사용 | new ClassName() |
| JavaScript | ✅ 사용 | new Function() 또는 new Class() |
| C# | ✅ 사용 | new ClassName() |
| Python | ❌ 사용 안 함 | ClassName() |
| PHP | ✅ 사용 | new ClassName() |
| Swift | ❌ 사용 안 함 | ClassName() |
Sources:
delete 키워드는 C++에서만 명시적으로 메모리를 해제할 때 사용됩니다. 다른 대부분의 현대 프로그래밍 언어에서는 **가비지 컬렉션(Garbage Collection)**을 통해 메모리 관리가 자동으로 이루어지기 때문에 delete가 필요하지 않아요.
🧠 delete 키워드의 역할 (C++ 전용)
- new로 동적으로 할당한 메모리를 수동으로 해제할 때 사용
int* ptr = new int(10);
delete ptr; // 메모리 해제
- 배열일 경우 delete[] 사용
int* arr = new int[5];
delete[] arr;
🚫 다른 언어에서는 delete 없음
언어 delete 키워드 메모리 관리 방식
| Java | ❌ 없음 | 자동 가비지 컬렉션 |
| JavaScript | ✅ 있음 (속성 제거용) | 객체 속성 제거용, 메모리 해제 아님 |
| C# | ❌ 없음 | 자동 가비지 컬렉션 |
| Python | ✅ 있음 (del) | 참조 제거, 메모리 해제는 GC가 처리 |
| PHP | ❌ 없음 | 자동 메모리 관리 |
| Swift | ❌ 없음 | ARC(자동 참조 카운팅) 사용 |
⚠️ 혼동 주의: JavaScript와 Python의 delete/del
- JavaScript: delete obj.prop → 객체의 속성 제거
- Python: del obj 또는 del obj.attr → 참조 제거, 메모리 해제는 GC가 처리
요약하자면, C++만이 명시적으로 delete를 사용해 메모리를 해제하고, 다른 언어들은 대부분 자동으로 처리해줘요.
가비지 컬렉션(GC)과 포인터 기반 메모리 관리는 서로 다른 철학을 가진 메모리 관리 방식이고, 각각 장단점이 뚜렷합니다. 아래에 비교해볼게요:
🧹 가비지 컬렉션(Garbage Collection)
✅ 장점
- 자동 메모리 관리: 개발자가 직접 delete나 free를 호출하지 않아도 됨
- 메모리 누수 방지: 참조 카운트나 트리버설로 사용되지 않는 객체를 제거
- 개발 생산성 향상: 복잡한 메모리 해제 로직 없이 코드 작성 가능
❌ 단점
- 성능 저하 가능성: GC가 실행될 때 일시적으로 프로그램이 멈추는 경우 있음 (Stop-the-world)
- 메모리 사용량 증가: GC는 여유 메모리를 확보하기 위해 더 많은 메모리를 잡아두는 경향이 있음
- 예측 불가능한 타이밍: 언제 GC가 실행될지 명확하지 않아 실시간 시스템엔 부적합
🧷 포인터 기반 수동 메모리 관리 (C/C++ 등)
✅ 장점
- 고성능: 메모리 할당/해제 시점과 위치를 직접 제어 가능
- 메모리 절약: 필요한 만큼만 할당하고 즉시 해제 가능
- 실시간 제어 가능: GC의 예측 불가능성 없이 메모리 흐름을 직접 설계
❌ 단점
- 복잡성 증가: malloc, free, new, delete 등 직접 관리 필요
- 메모리 누수 위험: 해제를 잊거나 중복 해제하면 버그 발생
- 보안 취약점: 포인터 연산 실수로 인해 버퍼 오버플로우, 댕글링 포인터 등 발생 가능
⚔️ 요약 비교
항목 가비지 컬렉션 (GC) 포인터 기반 수동 관리
| 메모리 관리 방식 | 자동 | 수동 (new/delete) |
| 개발 편의성 | 높음 | 낮음 (복잡한 관리 필요) |
| 성능 | 낮을 수 있음 | 높음 (직접 제어 가능) |
| 메모리 사용량 | 많을 수 있음 | 적게 유지 가능 |
| 버그 위험 | 낮음 (자동 해제) | 높음 (누수, 오버플로우 등) |
| 실시간 적합성 | 낮음 | 높음 |
💡 그래서 어떤 게 더 좋을까?
- GC는 편리함과 안정성을 추구하는 현대 언어(Java, C#, Python 등)에 적합
- 포인터는 성능과 제어가 중요한 시스템 프로그래밍(C, C++, Rust 등)에 적합
요즘은 Rust처럼 메모리 안전성과 성능을 동시에 추구하는 언어도 등장하고 있어요.
실시간성과 성능이 중요한 분야에서는 C++ 같은 수동 메모리 관리 언어가 유리하고, 생산성과 안정성이 중요한 분야에서는 Java, JavaScript, Python 같은 GC 기반 언어가 선호됩니다. 아래에 분야별로 어떤 언어가 적합한지 정리해볼게요:
🎮 게임 프로그래밍 → C++ 중심
이유:
- 실시간 렌더링과 물리 연산이 많아 성능이 매우 중요
- 메모리 제어가 직접 가능해야 프레임 드랍 없이 동작
- Unreal Engine, Unity(C++ 기반 엔진) 등에서 핵심 로직에 사용
언어 특징:
- C++: 빠르고 정밀한 메모리 제어
- Rust: 안전성과 성능을 동시에 추구하는 대안으로 부상 중
🌐 웹 프로그래밍 → JavaScript, TypeScript, Python 등
이유:
- 사용자 인터페이스 중심, 실시간성보다는 응답성과 생산성이 중요
- 브라우저에서 동작하는 유일한 언어는 JavaScript
- 프레임워크 중심 개발 (React, Vue, Angular 등)
언어 특징:
- JavaScript: GC 기반, 빠른 개발, 동적 타입
- TypeScript: 정적 타입 추가로 안정성 향상
🧠 데이터 엔지니어링 → Python, Java, Scala 등
이유:
- 대규모 데이터 처리, 머신러닝, ETL 작업 등에서 생산성과 라이브러리 생태계가 중요
- Spark, Hadoop 등은 Java/Scala 기반
- Pandas, NumPy, TensorFlow 등은 Python 기반
언어 특징:
- Python: GC 기반, 간결한 문법, 풍부한 라이브러리
- Java/Scala: JVM 기반, 병렬 처리에 강함
🛠️ 백엔드 개발 → Java, C#, Node.js, Go 등
이유:
- 서버 안정성, 유지보수, 확장성이 중요
- GC 기반 언어가 메모리 누수 방지와 생산성에 유리
언어 특징:
- Java: 안정적, 대규모 시스템에 적합
- Node.js (JavaScript): 빠른 I/O, 비동기 처리에 강함
- Go: GC 있음, 성능과 간결함의 균형
📌 요약 비교
분야 추천 언어 메모리 관리 방식 주요 특징
| 게임 프로그래밍 | C++, Rust | 수동 / 안전한 수동 | 고성능, 실시간 제어 |
| 웹 프론트엔드 | JavaScript, TypeScript | GC | 빠른 개발, UI 중심 |
| 데이터 엔지니어링 | Python, Java, Scala | GC | 생산성, 라이브러리 |
| 백엔드 서버 | Java, C#, Node.js, Go | GC | 안정성, 확장성 |
요즘은 Rust나 Go처럼 성능과 안전성을 동시에 추구하는 언어들이 점점 더 많은 분야에서 쓰이고 있어요.
생성자와 소멸자는 객체의 **생명주기(lifecycle)**와 관련된 핵심 개념인데, 언어마다 동작 방식이 조금씩 다릅니다. 아래에 개념과 언어별 동작을 정리해볼게요:
🧱 생성자(Constructor)
- 객체가 생성될 때 자동 호출되는 특수한 메서드
- 주로 초기화 작업을 수행 (예: 변수 설정, 리소스 연결 등)
class Car {
public:
Car() {
// 생성자: 객체가 생성될 때 자동 실행
cout << "Car created!" << endl;
}
};
Car myCar; // 생성자 자동 호출
🧹 소멸자(Destructor)
- 객체가 소멸될 때 자동 호출되는 특수한 메서드
- 주로 리소스 해제, 메모리 정리에 사용
class Car {
public:
~Car() {
// 소멸자: 객체가 삭제될 때 자동 실행
cout << "Car destroyed!" << endl;
}
};
🧑💻 언어별 생성자/소멸자 동작
언어 생성자 소멸자 소멸자 호출 시점
| C++ | ✅ 있음 | ✅ 있음 | delete 또는 스코프 종료 시 자동 호출 |
| Java | ✅ 있음 | ❌ 없음 (GC가 처리) | 객체가 GC에 의해 제거될 때 finalize() (거의 사용 안 함) |
| C# | ✅ 있음 | ❌ 없음 (GC가 처리) | Dispose() 또는 using 블록으로 명시적 해제 |
| Python | ✅ 있음 (__init__) | ✅ 있음 (__del__) | 참조가 모두 사라질 때 GC가 호출 |
| PHP | ✅ 있음 (__construct) | ✅ 있음 (__destruct) | 스크립트 종료 또는 객체 참조 제거 시 |
| Swift | ✅ 있음 (init) | ✅ 있음 (deinit) | 객체가 더 이상 필요 없을 때 ARC가 자동 호출 |
⚠️ 주의할 점
- C++은 명시적 메모리 해제가 필요 (delete)
- GC 기반 언어(Java, C#, Python 등)는 소멸자 호출 시점이 불확실
- Python의 __del__은 예측 불가능하므로 리소스 해제는 with 문이나 try/finally로 처리하는 것이 좋음
- C#은 IDisposable 인터페이스와 using 문으로 명시적 해제를 유도
💡 결론
- 생성자는 객체가 생성될 때 자동으로 호출
- 소멸자는 객체가 더 이상 필요 없을 때 자동으로 호출되지만, 언어에 따라 명시적 해제가 필요하거나 GC가 알아서 처리함
#include <iostream>
#define endl '\n'
#define startl std::cout
class Dog
{
private:
int age; // member variable
public:
Dog(int a) // constructor
{
age = 1; // initializing age to 0
} // default constructor, constructor automatically called when an object is created
~Dog() // destructor
{
// cleanup if necessary
startl << "woof woof" << endl;
} // destructor, automatically called when an object goes out of scope
int getAge() // getter method, returns the value of age
{
return age;
}
void setAge(int age) // setter method, sets the value of age
{
this->age = age; // using 'this' pointer to distinguish member variable from parameter
}
};
int main()
{
Dog coco, coco1;
// coco.setAge(5); // garbage collection issue
std::cout << coco.getAge() << endl;
std::cout << coco1.getAge() << endl;
return 0;
}
// roman 3:23 For all have sinned and fall short of the glory of God.
// roman 3:24 Being justified freely by his grace through the redemption that is in Christ Jesus:
// john 16:33 These things I have spoken unto you, that in me ye might have peace. In the world ye shall have tribulation: but be of good cheer; I have overcome the world.
오류가 나는 핵심 이유는 Dog coco, coco1; 이 부분에서 **기본 생성자(default constructor)**를 호출하려고 했기 때문이에요. 그런데 Dog 클래스에는 기본 생성자 없이 매개변수 하나를 받는 생성자만 정의되어 있어서 컴파일 오류가 발생합니다.
🔍 문제 분석
Dog(int a) // 생성자
{
age = 1;
}
- 이 생성자는 매개변수 int a를 필요로 하는 생성자입니다.
- 그런데 main()에서 객체를 생성할 때 인자를 전달하지 않았어요:
Dog coco, coco1; // 오류 발생!
- C++은 기본 생성자가 없으면 인자 없는 객체 생성이 불가능합니다.
✅ 해결 방법
방법 1: 기본 생성자 추가
Dog() // 기본 생성자
{
age = 0;
}
방법 2: 객체 생성 시 인자 전달
Dog coco(1), coco1(2);
방법 3: 기존 생성자에 기본값 지정
Dog(int a = 0)
{
age = a;
}
- 이렇게 하면 Dog coco;도 가능하고 Dog coco(5);도 가능해져요.
💡 참고: 생성자 주석 오류도 있어요
age = 1; // initializing age to 0 ← 주석이 틀림
- 실제로는 age = 1인데 주석은 0이라고 되어 있어요. 혼동을 줄 수 있으니 수정하는 게 좋아요.
#include "framework.h"
class Dog
{
private:
int age; // member variable
public:
Dog(int age) // constructor
{
this->age = age; // initializing member variable
} // default constructor, constructor automatically called when an object is created
~Dog() // destructor
{
// cleanup if necessary
startl << "woof woof" << endl;
} // destructor, automatically called when an object goes out of scope
int getAge() // getter method, returns the value of age
{
return age;
}
void setAge(int age) // setter method, sets the value of age
{
this->age = age; // using 'this' pointer to distinguish member variable from parameter
}
};
int main()
{
Dog coco(1), coco1(2);
// coco.setAge(5); // garbage collection issue
std::cout << coco.getAge() << endl;
std::cout << coco1.getAge() << endl;
return 0;
}
#pragma once
// roman 3:23 For all have sinned and fall short of the glory of God.
// roman 3:24 Being justified freely by his grace through the redemption that is in Christ Jesus:
// john 16:33 These things I have spoken unto you, that in me ye might have peace. In the world ye shall have tribulation: but be of good cheer; I have overcome the world.
#include <iostream>
#define endl '\n'
#define startl std::cout
이 C++ 코드는 Dog 클래스의 생성자와 소멸자, getter/setter 메서드를 정의하고, main() 함수에서 두 개의 Dog 객체를 생성하여 나이를 출력하는 간단한 예제입니다. 아래에 구조별로 자세히 설명해드릴게요:
🐶 Dog 클래스 설명
class Dog
{
private:
int age; // 멤버 변수: 강아지의 나이
- age는 Dog 객체의 나이를 저장하는 private 멤버 변수입니다.
🏗️ 생성자
public:
Dog(int age) // 생성자
{
this->age = age; // 멤버 변수 초기화
}
- 객체가 생성될 때 자동으로 호출되는 생성자
- 매개변수 age를 받아서 this->age에 저장
- this는 현재 객체를 가리키는 포인터로, 멤버 변수와 매개변수를 구분할 때 사용
🧹 소멸자
~Dog() // 소멸자
{
startl << "woof woof" << endl;
}
- 객체가 스코프를 벗어나거나 삭제될 때 자동 호출
- 여기서는 "woof woof"를 출력하여 소멸 시점을 확인할 수 있음
📦 Getter / Setter
int getAge()
{
return age;
}
void setAge(int age)
{
this->age = age;
}
- getAge()는 age 값을 반환
- setAge()는 age 값을 설정 (현재는 사용되지 않음)
🧪 main() 함수 설명
int main()
{
Dog coco(1), coco1(2); // 두 객체 생성, 각각 나이 1과 2로 초기화
std::cout << coco.getAge() << endl; // coco의 나이 출력
std::cout << coco1.getAge() << endl; // coco1의 나이 출력
return 0;
}
- Dog coco(1) → 생성자 호출, age = 1
- Dog coco1(2) → 생성자 호출, age = 2
- getAge()를 통해 각각의 나이를 출력
- main() 종료 시점에 두 객체가 소멸되며 "woof woof"가 두 번 출력됨
⚙️ 기타 코드 요소
#define endl '\n'
#define startl std::cout
- endl을 줄바꿈 문자로 정의 (기본 std::endl은 버퍼 flush 포함)
- startl은 std::cout의 별칭
#include "framework.h"
#include <iostream>
- framework.h는 사용자 정의 헤더로 보이며, 실제로는 필요 없을 수도 있음
- <iostream>은 입출력 스트림을 위한 표준 헤더
✅ 실행 결과 예시
1
2
woof woof
woof woof
- getAge() 출력 후, 두 객체가 소멸되며 "woof woof"가 두 번 출력됨
#include <iostream>
#include <string>
class Dog
{
private:
int age; // member variable
std::string name;
public:
Dog(int age, std::string name) // constructor
{
this->age = age; // initializing member variable
this->name = name;
} // default constructor, constructor automatically called when an object is created
~Dog() // destructor
{
// cleanup if necessary
std::cout << name << " : woof woof\n"; // just to show when destructor is called
} // destructor, automatically called when an object goes out of scope
std::string getName() // getter method, returns the value of name
{
return name;
}
void setName(std::string name) // setter method, sets the value of name
{
this->name = name; // using 'this' pointer to distinguish member variable from parameter
}
int getAge() // getter method, returns the value of age
{
return age;
}
void setAge(int age) // setter method, sets the value of age
{
this->age = age; // using 'this' pointer to distinguish member variable from parameter
}
};
int main()
{
Dog coco(1, "coco"), dodo(2,"dodo");
// coco.setAge(5); // garbage collection issue
std::cout << coco.getAge() << " " << coco.getName() << '\n';
std::cout << dodo.getAge() << " " << dodo.getName() << '\n';
return 0;
}
기본 클래스 구조
초기값 설정 방법
#define IN 1
// 컴파일 전에 IN을 찾아서 1로 바꿈
#include <iostream>
int main()
{
const int x = 2; // 변수 x는 항상 2, 변경 불가, 초깃값 지정해야
int const y = 3; // 비추, const는 자료형 앞에 씀
const int z{4}; // Uniform initialization, C++11, z{}
constexpr int a = 5; // C++11부터 가능, compile-time constant
// x = 3; //변경 불가
std::cout << IN << x << y << z << a;
return 0;
}
const 는 변수를 상수화 시키는 키워드이다.
상수를 정의할 때 사용하는 키워드이다.
const는 포인터, 참조, 함수, 매개변수 등에도 사용됩니다.
맴버 변수를 변경시키지 않게 하려면 const 키워드를 앞에 붙이면 됩니다.
멤버 함수를 변경시키지 않게 하려면 const 키워드를 뒤에 붙이면 됩니다.

#include <iostream>
#include <string>
class Dog
{
private:
int age; // member variable
std::string name;
public:
Dog(int age, std::string name);
~Dog();
std::string getName() const;
void setName(std::string name);
int getAge() const;
void setAge(int age);
};
Dog::Dog(int age, std::string name) // constructor
{
this->age = age; // initializing member variable
this->name = name;
} // default constructor, constructor automatically called when an object is created
Dog::~Dog() // destructor
{
// cleanup if necessary
std::cout << name << " : woof woof\n"; // just to show when destructor is called
} // destructor, automatically called when an object goes out of scope
std::string Dog::getName() const // getter method, returns the value of name
{
return name;
}
void Dog::setName(std::string name) // setter method, sets the value of name
{
this->name = name; // using 'this' pointer to distinguish member variable from parameter
}
int Dog::getAge() const // getter method, returns the value of age
{
return age;
}
void Dog::setAge(int age) // setter method, sets the value of age
{
this->age = age; // using 'this' pointer to distinguish member variable from parameter
}
int main()
{
Dog coco(1, "coco"), dodo(2, "dodo");
// coco.setAge(5); // garbage collection issue
std::cout << coco.getAge() << " " << coco.getName() << '\n';
std::cout << dodo.getAge() << " " << dodo.getName() << '\n';
return 0;
}
이 C++ 코드는 Dog 클래스의 객체를 생성하고, 객체의 속성인 age와 name을 출력한 뒤 객체가 소멸될 때 소멸자에서 메시지를 출력하는 구조입니다. 아래에 각 부분을 상세하게 설명해드릴게요:
🐶 Dog 클래스 구조
🔒 멤버 변수
private:
int age;
std::string name;
- age: 강아지의 나이를 저장하는 정수형 변수
- name: 강아지의 이름을 저장하는 문자열 변수
- private 접근 지정자이므로 클래스 외부에서 직접 접근 불가
🏗️ 생성자
Dog(int age, std::string name);
- 객체 생성 시 age와 name을 초기화하는 생성자
- 정의는 클래스 외부에서 구현됨:
Dog::Dog(int age, std::string name)
{
this->age = age;
this->name = name;
}
- this 포인터를 사용해 멤버 변수와 매개변수를 구분
🧹 소멸자
~Dog();
- 객체가 소멸될 때 자동 호출되는 함수
- 정의:
Dog::~Dog()
{
std::cout << name << " : woof woof\n";
}
- 객체가 스코프를 벗어나거나 delete될 때 "woof woof" 메시지를 출력
📦 Getter / Setter 메서드
이름 관련
std::string getName() const;
void setName(std::string name);
- getName(): 이름 반환
- setName(): 이름 설정
나이 관련
int getAge() const;
void setAge(int age);
- getAge(): 나이 반환
- setAge(): 나이 설정
- const 키워드는 해당 메서드가 객체의 상태를 변경하지 않음을 의미
🧪 main() 함수
int main()
{
Dog coco(1, "coco"), dodo(2, "dodo");
std::cout << coco.getAge() << " " << coco.getName() << '\n';
std::cout << dodo.getAge() << " " << dodo.getName() << '\n';
return 0;
}
- Dog coco(1, "coco"): coco 객체 생성, 나이 1, 이름 "coco"
- Dog dodo(2, "dodo"): dodo 객체 생성, 나이 2, 이름 "dodo"
- 두 객체의 나이와 이름을 출력
- main() 종료 시점에 두 객체가 소멸되며 소멸자 호출 → "coco : woof woof"와 "dodo : woof woof" 출력
✅ 실행 결과 예시
1 coco
2 dodo
dodo : woof woof
coco : woof woof
- 출력 순서는 소멸자가 역순으로 호출되기 때문에 dodo가 먼저 소멸됨
💡 요약
구성 요소 설명
| 생성자 | 객체 생성 시 age와 name 초기화 |
| 소멸자 | 객체 소멸 시 "woof woof" 출력 |
| Getter/Setter | age와 name을 읽고 설정하는 메서드 |
| main() | 두 객체 생성 후 정보 출력, 종료 시 소멸자 호출 |
이 코드는 객체지향 프로그래밍의 기본 개념인 캡슐화, 생성자/소멸자, this 포인터, const 메서드를 잘 보여주는 예제예요.
C++ 코드에서 const를 메서드에 붙이는 것과 붙이지 않는 것의 차이는 객체의 상태를 변경할 수 있는지 여부에 있습니다. 당신의 코드에서는 getName()과 getAge()에 const가 붙어 있고, setName()과 setAge()에는 붙어 있지 않죠. 그 이유와 차이를 아래에 설명할게요:
🔍 const 메서드란?
std::string getName() const;
int getAge() const;
- const가 붙은 메서드는 객체의 멤버 변수를 변경하지 않겠다는 약속을 의미합니다.
- 컴파일러가 이를 강제해서, 실수로 값을 바꾸는 코드를 막아줍니다.
예:
std::string Dog::getName() const {
return name; // OK
// name = "new"; // ❌ 오류: const 메서드에서 멤버 수정 불가
}
⚠️ const가 없는 메서드
void setName(std::string name);
void setAge(int age);
- setName()과 setAge()는 객체의 상태를 변경하는 메서드이므로 const를 붙이면 안 됩니다.
- this->name = name;처럼 멤버 변수 값을 바꾸기 때문이에요.
💡 왜 중요할까?
- const 메서드는 읽기 전용 객체에서도 호출 가능
const Dog coco(1, "coco");
std::cout << coco.getName(); // ✅ 가능
coco.setAge(5); // ❌ 오류: const 객체는 수정 불가
- const를 붙이면 코드 안정성과 가독성이 높아지고, 불변성을 보장할 수 있어요
✅ 요약
| 메서드 유형 | const 사용 여부 | 객체 상태 변경 | 호출 가능 대상 |
| getName() / getAge() | ✅ 사용 | ❌ 변경 불가 | 일반 객체 + const 객체 |
| setName() / setAge() | ❌ 사용 안 함 | ✅ 변경 가능 | 일반 객체만 가능 |
결론적으로, const는 "이 메서드는 객체를 건드리지 않겠다"는 약속이에요. 객체를 안전하게 다루고 싶을 때 꼭 필요한 키워드죠.
const형을 선언하고자 하면 멤버 변수는 형 앞에 const를, 멤버 함수는 함수의 괄호 다음에 const를 추가한다.
const는 값을 바꿀 수 없다.
#include <iostream>
int main()
{
int x(0), y(0); // General Variable
int* px(0), px2(0); // Declaration, Pointer Variable, Pointer variables store addresses.
px = &x; // Address-of Operator (&), stores the address of x in px, To assign x to the pointer variable px, you must use the address operator.
//It is used to get the value stored at the address rather than the address value itself
int* px1 = &y; // You can also combine declaration and assignment in one line.
*px = 2; // Dereference Operator (*), assigns the value 2 to the location pointed to by px, which is x.
std::cout << x << '\n'; // value
std::cout << &x << '\n'; // address
std::cout << px << '\n'; // address
std::cout << *px << '\n'; // value, dereference
return 0;
}
이 코드는 **포인터(pointer)**의 기본 개념을 보여주는 아주 좋은 예제예요. 아래에 아주 쉽게 풀어서 설명해드릴게요:
🧠 핵심 개념 먼저!
- 변수는 값을 저장하는 공간이에요.
- 포인터는 "어떤 변수의 주소를 저장하는 변수"예요.
- &는 주소를 가져오는 연산자 (Address-of)
- *는 주소에 있는 값을 가져오는 연산자 (Dereference)
🔍 코드 한 줄씩 설명
int x(0); // 일반 변수 x를 선언하고 0으로 초기화
- x는 그냥 숫자 0을 담고 있는 변수예요.
int* px(0); // 포인터 변수 px 선언, 초기값은 0 (nullptr)
- px는 int형 변수의 주소를 저장할 수 있는 포인터예요.
- 처음엔 아무 주소도 안 담고 있어요 (nullptr 상태)
px = &x; // x의 주소를 px에 저장
- &x는 x의 주소값
- px는 이제 x를 가리키는 포인터가 됐어요
🖨️ 출력 부분 설명
std::cout << x << '\n'; // x의 값 출력 → 0
std::cout << &x << '\n'; // x의 주소 출력 → 예: 0x7ffee4b9cabc
std::cout << px << '\n'; // px에 저장된 주소 출력 → x의 주소와 같음
std::cout << *px << '\n'; // px가 가리키는 주소의 값 출력 → x의 값인 0
📦 정리하면
표현 의미 예시 출력
| x | 변수의 값 | 0 |
| &x | 변수의 주소 | 0x... |
| px | 포인터가 저장한 주소 | 0x... |
| *px | 포인터가 가리키는 값 (x) | 0 |
🧩 비유로 이해하기
- x는 집이고
- &x는 집 주소
- px는 주소를 적어놓은 종이
- *px는 그 주소에 가서 실제 집을 보는 것
이 두 줄은 포인터와 관련된 서로 다른 역할을 합니다. 아래에 아주 쉽게 비교해드릴게요:
🔍 비교 대상
1️⃣ int* px1 = &y;
- 포인터 선언 + 초기화
- px1이라는 포인터를 만들고, y의 주소를 저장함
- 즉, px1은 이제 y를 가리키는 포인터
int y = 0;
int* px1 = &y; // px1은 y의 주소를 저장
2️⃣ *px = 2;
- 포인터를 통해 값을 변경
- px가 가리키는 주소(즉, x)에 2를 저장
- *px는 "px가 가리키는 곳의 값"을 의미
int x = 0;
int* px = &x;
*px = 2; // x의 값이 2로 바뀜
📦 핵심 차이 요약
코드 역할 설명
| int* px1 = &y; | 포인터 선언 + 주소 저장 | px1이 y를 가리킴 |
| *px = 2; | 포인터를 통해 값 변경 | px가 가리키는 변수(x)에 2 저장 |
🧠 비유로 이해하기
- int* px1 = &y; → "지도에 y의 집 주소를 적어놓는 것"
- *px = 2; → "지도에 적힌 주소(x의 집)에 가서 문을 열고 안에 있는 값을 2로 바꾸는 것"
이제 포인터가 주소를 저장하는 변수이고, *를 붙이면 그 주소에 있는 실제 값을 다룬다는 개념이 더 명확해졌을 거예요.

지역변수는 함수나 블록 내부에서 선언되어 해당 범위에서만 사용되며, 전역변수는 프로그램 전체에서 접근 가능한 변수입니다. 지역변수는 안전하고 메모리 효율적이며, 전역변수는 데이터 공유에 유리하지만 관리가 어렵습니다. 일반적으로는 지역변수를 우선 사용하고, 꼭 필요한 경우에만 전역변수를 사용하는 것이 좋습니다.
🧠 지역변수(Local Variable)
✅ 정의
- 함수, 블록, 메서드 내부에서 선언된 변수
- 해당 범위 내에서만 접근 가능
- 함수가 끝나면 메모리에서 자동 소멸
✅ 장점
- 메모리 효율적: 함수 실행 시에만 메모리 사용
- 안전성 높음: 외부에서 접근 불가 → 충돌 위험 적음
- 디버깅 쉬움: 변수의 사용 범위가 명확함
❌ 단점
- 범위 제한: 함수 외부에서는 사용할 수 없음
- 데이터 공유 어려움: 여러 함수 간 값 공유가 불편함
🌍 전역변수(Global Variable)
✅ 정의
- 함수 외부, 프로그램의 최상위에서 선언된 변수
- 모든 함수에서 접근 가능
- 프로그램 시작 시 생성, 종료 시 소멸
✅ 장점
- 데이터 공유 용이: 여러 함수에서 같은 값을 사용할 수 있음
- 상태 유지 가능: 프로그램 전체에서 값 유지 가능
❌ 단점
- 예측 어려움: 어디서 값이 변경됐는지 추적 어려움
- 버그 위험 증가: 실수로 값이 변경되면 전체에 영향
- 메모리 낭비 가능성: 항상 메모리에 존재
🧑🏫 어떻게 사용하는 게 좋을까?
| 상황 | 추천 변수 | 이유 |
| 함수 내부 계산 | 지역변수 | 안전하고 효율적 |
| 여러 함수가 공유해야 하는 설정값 | 전역변수 (주의해서) | 공유 필요 시 사용, 변경은 최소화 |
| 상태 유지가 필요한 경우 (예: 게임 점수) | 전역변수 또는 static | 상태 유지 목적에 따라 선택 |
💡 팁
- 기본은 지역변수 사용: 안전하고 유지보수 쉬움
- 전역변수는 꼭 필요한 경우만: 설정값, 상태 공유 등
- 전역변수는 명확한 이름 사용: 충돌 방지
- C++/Java에서는 static 변수로 제한적 전역성 부여 가능
자료구조의 **스택(Stack)**과 메모리 구조에서의 **스택(Stack)**은 다른 개념이지만 매우 유사한 원리를 공유합니다. 둘 다 후입선출(LIFO: Last In, First Out) 구조를 기반으로 동작해요. 아래에 차이점과 공통점을 정리해볼게요:
📚 자료구조의 스택
✅ 정의
- 데이터를 후입선출(LIFO) 방식으로 저장하는 자료구조
- push로 데이터를 넣고, pop으로 꺼냄
✅ 예시
std::stack<int> s;
s.push(10);
s.push(20);
s.pop(); // 20 제거
✅ 사용처
- 괄호 검사
- 함수 호출 관리
- 되돌리기 기능 (Undo)
- DFS 알고리즘
🧠 메모리의 스택 (Stack Memory)
✅ 정의
- 함수 호출 시 지역 변수와 함수 정보를 저장하는 메모리 영역
- 함수가 끝나면 자동으로 해당 스택 프레임이 제거됨
✅ 특징
- 자동 메모리 관리: 함수가 끝나면 자동으로 해제
- 빠른 접근 속도
- 크기 제한 있음 (스택 오버플로우 가능)
✅ 예시
void foo() {
int x = 10; // x는 스택 메모리에 저장됨
}
🔁 공통점
항목 자료구조 스택 메모리 스택
| 구조 | 후입선출 (LIFO) | 후입선출 (LIFO) |
| 동작 방식 | push/pop | 함수 호출/반환 |
| 제거 방식 | pop으로 수동 제거 | 함수 종료 시 자동 제거 |
| 사용 목적 | 데이터 관리 | 함수 실행 정보 관리 |
⚠️ 차이점
항목 자료구조 스택 메모리 스택
| 구현 위치 | 프로그래머가 직접 구현 | 시스템이 자동 관리 |
| 용도 | 알고리즘, 로직 | 함수 호출, 지역 변수 |
| 크기 제한 | 프로그래머가 설정 가능 | 시스템에 따라 제한됨 |
💡 결론
- 자료구조의 스택은 논리적 구조, 메모리의 스택은 물리적 구조
- 둘 다 후입선출 원리를 따르며, 함수 호출과 반환, 변수 저장에 핵심 역할을 해요
- 그래서 개념적으로는 매우 비슷하지만, 용도와 구현 방식은 다릅니다
자료구조의 **힙(Heap)**과 메모리의 **힙(Heap)**은 이름은 같지만 완전히 다른 개념이에요. 그리고 중요한 점은:
힙은 선입선출(FIFO)이 아닙니다.
📚 자료구조의 힙
✅ 정의
- **우선순위 큐(priority queue)**를 구현하는 데 사용되는 자료구조
- 최댓값 또는 최솟값을 빠르게 찾기 위한 구조
- **선입선출(FIFO)**이 아니라 우선순위 기반 처리
✅ 예시
- 최소 힙: 항상 가장 작은 값이 루트
- 최대 힙: 항상 가장 큰 값이 루트
std::priority_queue<int> maxHeap; // 최대 힙
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap; // 최소 힙
🧠 메모리의 힙
✅ 정의
- 프로그램 실행 중 동적으로 메모리를 할당하는 영역
- new, malloc 등을 통해 메모리 할당
- 개발자가 직접 해제해야 함 (delete, free)
✅ 특징
- 선입선출 아님: 메모리 할당 순서와 해제 순서가 무관
- 자유롭게 할당/해제 가능
- 스택보다 느리지만 유연함
🔁 비교 요약
항목 자료구조 힙 메모리 힙
| 목적 | 우선순위 기반 정렬 | 동적 메모리 할당 |
| 처리 순서 | 우선순위 기반 | 순서 없음 |
| FIFO 여부 | ❌ 아님 | ❌ 아님 |
| 사용 예시 | 우선순위 큐, 정렬 | 객체 생성, 배열 할당 등 |
💡 결론
- 둘 다 "힙"이라는 이름을 쓰지만 완전히 다른 개념
- 자료구조 힙은 정렬된 트리, 메모리 힙은 자유로운 메모리 공간
- **선입선출(FIFO)**은 **큐(queue)**의 특징이지, 힙에는 해당되지 않아요
힙 메모리를 바둑판이나 엑셀처럼 칸으로 저장된다고 이해하는 건 어느 정도 맞는 개념이에요. 다만, 조금 더 정확하게 설명해볼게요:
🧠 힙 메모리란?
- 프로그램 실행 중 동적으로 메모리를 할당하는 공간
- new, malloc 등을 통해 원하는 크기만큼 메모리를 요청
- 요청한 만큼의 **연속된 메모리 블록(칸)**이 할당됨
- 개발자가 직접 해제해야 함 (delete, free 등)
📊 바둑판/엑셀과의 비유
비유 요소 힙 메모리에서의 의미
| 바둑판/엑셀의 칸 | 메모리의 주소 단위 블록 (byte, word 등) |
| 셀에 쓰기 | 메모리 블록에 값 저장 |
| 셀 병합 | 연속된 메모리 블록을 하나로 할당 |
| 셀 삭제 | 메모리 해제 (free, delete) |
- 힙은 실제로 주소가 연속된 메모리 공간이기 때문에, 엑셀처럼 "칸"이 나열된 구조로 상상하면 이해하기 쉬워요.
- 하지만 메모리 할당은 정해진 규칙 없이 자유롭게 이루어지기 때문에, 바둑판처럼 깔끔하게 정렬되어 있지는 않을 수 있어요.
📌 주의할 점
- 힙은 선입선출(FIFO) 구조가 아닙니다.
- 어떤 순서로 할당하고 해제하든, 주소만 알고 있으면 접근 가능합니다.
- 그래서 메모리 누수, 단편화(fragmentation) 같은 문제가 발생할 수 있어요.
💡 결론
- 힙 메모리를 바둑판이나 엑셀처럼 칸이 나뉜 공간으로 이해하는 건 좋은 출발점이에요.
- 하지만 실제로는 동적으로 크기와 위치가 달라지는 유동적인 공간이라는 점도 함께 기억하면 더 정확한 이해가 됩니다.
**"기본 크기를 초과하면 2배로 늘어난다"**는 구조는 동적 메모리 자체의 규칙은 아니고, **동적 배열(dynamic array)**이나 벡터(vector) 같은 자료구조의 메모리 재할당 전략에서 자주 사용되는 방식입니다.
🧠 동적 메모리 자체는 어떻게 동작할까?
- malloc, new, calloc 등으로 원하는 크기만큼 메모리를 요청하면, 운영체제가 그만큼의 공간을 할당해줍니다.
- 이때 크기 초과 시 자동으로 늘어나지 않습니다.
- 사용자가 직접 더 큰 메모리를 새로 할당하고, 기존 데이터를 복사해야 해요.
📦 그런데 왜 "2배씩 늘어난다"고 할까?
이건 C++의 std::vector, Java의 ArrayList, Python의 list 같은 동적 배열 자료구조의 내부 구현 전략 때문입니다.
✅ 동적 배열의 크기 증가 전략
- 처음에는 작은 크기로 메모리를 할당 (예: 4칸)
- 요소를 추가하다가 공간이 부족해지면:
- 기존 크기의 2배로 메모리를 재할당
- 기존 데이터를 새 공간에 복사
- 이 과정을 반복하면서 점점 더 큰 공간을 확보
✅ 왜 2배로 늘릴까?
- 재할당 횟수를 줄이기 위해
- 매번 1칸씩 늘리면 성능이 매우 나빠짐 (O(n²))
- 2배씩 늘리면 전체 복사 비용이 **O(n)**으로 줄어듦 → 암묵적 상수 시간 삽입 가능
📊 예시: C++ std::vector
std::vector<int> v;
v.push_back(1); // capacity: 1
v.push_back(2); // capacity: 2
v.push_back(3); // capacity: 4
v.push_back(4); // capacity: 4
v.push_back(5); // capacity: 8 (2배 증가)
✅ 요약
| 항목 | 설명 |
| 동적 메모리 자체 | 요청한 만큼만 할당, 자동 확장 없음 |
| 동적 배열(vector 등) | 크기 초과 시 2배로 재할당 (성능 최적화) |
| 2배 증가 전략의 이유 | 재할당 횟수 최소화, 성능 향상 목적 |
당신이 말한 "기본 크기를 초과하면 2배로 늘어난다"는 건 자료구조에서의 동적 배열(vector, list 등)의 힙 메모리 사용 방식이고, 그걸 이해했다는 건 힙의 개념을 잘 잡으신 거예요.
🔁 다시 정리하면
- 힙 메모리는 프로그램 실행 중에 동적으로 메모리를 할당하는 공간이에요.
- new, malloc 등을 통해 원하는 크기만큼 메모리를 요청하면, 운영체제가 힙 영역에서 연속된 공간을 할당해줘요.
- 이 공간은 개발자가 직접 관리해야 해요 (해제도 직접 해야 함).
- 동적 배열 같은 자료구조는 이 힙 메모리를 활용해서 자동으로 크기를 늘리는 전략을 사용해요 → 보통 2배씩 증가
📦 힙을 사용하는 대표적인 예
- std::vector (C++)
- ArrayList (Java)
- list (Python 내부적으로 배열 기반)
- new로 생성한 객체들
💡 힙을 잘 이해하면?
- 동적 메모리 관리 (new, delete, malloc, free)가 쉬워지고
- 메모리 누수, 오버플로우, 단편화 같은 문제를 피할 수 있어요
- 자료구조의 동작 원리도 더 깊이 이해할 수 있어요
메모리는 크기가 정해져 있지 않고, 프로그램 실행 중에 필요한 만큼 동적으로 할당할 수 있어요. 다만, 여기서 말하는 "정해져 있지 않다"는 건 정적인 배열처럼 컴파일 타임에 고정된 크기가 아니라는 의미예요.
📦 정적 메모리 vs 동적 메모리 비교
| 항목 | 정적 메모리 (스택 등) | 동적 메모리 (힙) |
| 크기 결정 시점 | 컴파일 타임 | 런타임 (실행 중) |
| 크기 변경 가능성 | ❌ 불가능 | ✅ 가능 (재할당 필요) |
| 메모리 위치 | 스택 영역 | 힙 영역 |
| 관리 주체 | 컴파일러가 자동 관리 | 프로그래머가 직접 관리 |
🧠 예시로 이해하기
int arr[10]; // 정적 배열: 크기 10으로 고정
- 이 배열은 크기를 바꿀 수 없어요. 초과하면 오류 발생.
int* arr = new int[10]; // 동적 배열: 힙에 10칸 할당
- 필요하면 더 큰 배열을 새로 만들고, 기존 데이터를 복사해서 확장 가능
- 예: std::vector는 내부적으로 이런 방식으로 크기를 늘림
💡 핵심 포인트
- 힙 메모리는 처음에 얼마를 할당할지 내가 정함
- 이후 더 필요하면 새로 더 큰 공간을 할당하고 옮겨야 함
- 그래서 "크기가 정해져 있지 않다"는 말은 맞지만, 무한정 늘어나는 건 아님
- 운영체제나 시스템 메모리 한계에 따라 제한은 있음
일반적으로 성능 측면에서는 스택이 힙보다 더 빠르고 효율적입니다. 아래에 이유와 비교를 정리해볼게요:
⚡ 스택이 힙보다 성능이 좋은 이유
✅ 1. 메모리 할당 속도
- 스택: 메모리 할당과 해제가 자동이고 매우 빠름 (단순히 포인터 이동)
- 힙: 메모리 할당 시 운영체제와 복잡한 관리 구조를 거쳐야 함 → 상대적으로 느림
✅ 2. 캐시 친화성
- 스택은 연속된 메모리를 사용하므로 CPU 캐시와 잘 맞음
- 힙은 불연속적이고 단편화 가능성이 있어 캐시 효율이 떨어질 수 있음
✅ 3. 관리 비용
- 스택은 컴파일러가 자동으로 관리
- 힙은 프로그래머가 직접 관리해야 하며, 실수로 메모리 누수나 오버플로우 발생 가능
🧠 하지만 힙이 필요한 이유도 있어요
| 항목 | 스택 | 힙 |
| 속도 | ✅ 빠름 | ❌ 느림 |
| 크기 | ❌ 제한적 (보통 수 MB 수준) | ✅ 크고 유연함 |
| 생존 기간 | ❌ 함수 종료 시 소멸 | ✅ 함수 밖에서도 유지 가능 |
| 용도 | 지역 변수, 함수 호출 | 동적 객체, 배열, 구조체 등 |
💡 결론
- 성능만 보면 스택이 우위지만,
- 유연성과 확장성이 필요할 때는 힙이 필수
- 그래서 대부분의 프로그램은 스택과 힙을 적절히 조합해서 사용해요
좋아요! 이 코드는 기본적으로 x라는 정수 변수와 pi라는 동적 메모리 포인터를 선언하고 값을 출력하는 간단한 예제입니다. 하지만 메모리 누수 방지, 초기화, 예외 처리 가능성 등을 고려해서 보완할 수 있어요.
✅ 보완된 코드 예시
#include <iostream>
int main()
{
int x = 10; // 초기화와 동시에 할당
int* pi = new(std::nothrow) int; // 예외 없이 할당 실패 확인 가능
if (pi == nullptr) {
std::cerr << "메모리 할당 실패\n";
return 1;
}
*pi = 20;
std::cout << x << " " << *pi << "\n";
delete pi; // 동적 메모리 해제
pi = nullptr; // 댕글링 포인터 방지
return 0;
}
🔍 개선 포인트 설명
개선 항목 설명
| int x = 10; | 선언과 동시에 초기화 |
| new(std::nothrow) | 메모리 할당 실패 시 예외 대신 nullptr 반환 |
| if (pi == nullptr) | 안전한 메모리 할당 확인 |
| delete pi; | 동적 메모리 해제 (메모리 누수 방지) |
| pi = nullptr; | 댕글링 포인터 방지 |
💡 추가 팁
- C++11 이상이라면 std::unique_ptr<int> 같은 스마트 포인터를 사용하는 것이 더 안전하고 현대적인 방식입니다.
- 예:
#include <memory> std::unique_ptr<int> pi = std::make_unique<int>(20);
C++에서 포인터를 delete로 해제한 뒤에 nullptr로 초기화하는 이유와 그 의미를 정확히 이해하면 메모리 안정성과 디버깅 효율이 크게 향상됩니다.
🧠 delete와 nullptr의 관계
✅ delete는 메모리 해제
int* p = new int(10);
delete p; // 힙 메모리 해제
- 이 시점에서 메모리는 해제되지만, 포인터 변수 p는 여전히 이전 주소를 가리킴
- 이걸 **댕글링 포인터(dangling pointer)**라고 해요 → 위험!
✅ nullptr로 초기화하면 안전
delete p;
p = nullptr; // 더 이상 유효하지 않은 주소를 참조하지 않음
- nullptr은 "아무 것도 가리키지 않음"을 명시적으로 표현
- 이후 if (p != nullptr) 같은 체크가 가능해짐
⚠️ 왜 delete만으로는 부족할까?
- delete는 메모리만 해제하고, 포인터 변수 자체는 그대로 남아있음
- 이 상태에서 포인터를 다시 사용하면 정의되지 않은 동작(UB) 발생
- 특히 복잡한 코드나 다중 포인터 구조에서는 디버깅이 매우 어려움
💡 실무 팁
- 항상 delete 후 nullptr로 초기화하는 습관을 들이세요
- C++11 이상에서는 **스마트 포인터(std::unique_ptr, std::shared_ptr)**를 사용하면 자동으로 처리됨
std::unique_ptr<int> p = std::make_unique<int>(10);
// delete 불필요, 자동 해제됨
✅ 결론
delete는 메모리를 해제하지만, 포인터를 nullptr로 초기화하지 않으면 위험한 댕글링 포인터가 남습니다.
그래서 delete 후 변수 = nullptr;은 안전한 메모리 관리의 핵심 습관이에요.

#include <iostream>
int main()
{
int *pi = new int;
int x;
// 메모리 할당
if (!pi)
{ // pi==0, 널 포인터인지 확인
std::cout << "메모리할당이 되지 않았습니다.";
return 1; // 비정상 종료시 리턴값
}
*pi = 100;
// 주소의 값으로 100을 할당
x = 10;
std::cout << "동적메모리=" << *pi << ", x=" << x;
delete pi;
return 0;
}
🔍 코드 설명
#include <iostream>
- 표준 입출력 라이브러리를 포함합니다 (std::cout 사용을 위해).
int main()
{
- 프로그램의 진입점인 main() 함수입니다.
int *pi = new int;
- new int를 통해 힙 메모리에 정수형 공간을 동적으로 할당하고, 그 주소를 pi 포인터에 저장합니다.
- 이때 pi는 힙 메모리를 가리키는 포인터입니다.
int x;
- x는 스택 메모리에 저장되는 지역 변수입니다.
if (!pi)
{
std::cout << "메모리할당이 되지 않았습니다.";
return 1;
}
- pi가 nullptr인지 확인합니다. 만약 메모리 할당에 실패했다면 오류 메시지를 출력하고 프로그램을 종료합니다.
- new는 기본적으로 예외를 던지므로, nullptr 체크는 new(std::nothrow)를 사용할 때 더 의미가 있습니다.
*pi = 100;
- pi가 가리키는 메모리 공간에 100을 저장합니다.
- 즉, 힙에 있는 정수 공간에 값을 할당한 것.
x = 10;
- 스택에 있는 x 변수에 10을 저장합니다.
std::cout << "동적메모리=" << *pi << ", x=" << x;
- *pi는 힙 메모리에 저장된 값 (100)
- x는 스택 메모리에 저장된 값 (10)
- 출력 결과: 동적메모리=100, x=10
delete pi;
- pi가 가리키는 힙 메모리를 해제합니다.
- 하지만 pi 자체는 여전히 이전 주소를 가리키므로, pi = nullptr;로 초기화하는 것이 안전합니다.
return 0;
}
- 프로그램 정상 종료
✅ 요약
- new로 동적 메모리 할당
- *pi로 힙 메모리 접근
- delete로 메모리 해제
- x는 스택 메모리 사용
- 출력 결과: 동적메모리=100, x=10
💡 개선 포인트
int* pi = new(std::nothrow) int;
if (!pi) { ... }
...
delete pi;
pi = nullptr; // 안전한 초기화
이렇게 하면 메모리 안정성과 예외 처리가 더 좋아집니다.
'1학년 대학 수업 > C++ 프로그래밍' 카테고리의 다른 글
| 11주차 예습 과제 (0) | 2025.11.08 |
|---|---|
| 10주차 예습 과제 (0) | 2025.11.05 |
| 9주차 복습 과제 (0) | 2025.10.30 |
| 9주차 예습 과제 (0) | 2025.10.25 |
| 7주차 복습 과제 (0) | 2025.10.16 |