1학년 대학 수업/C++ 프로그래밍

10주차 복습 과제

hawon6691 2025. 11. 6. 14:06
728x90

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; // 안전한 초기화

이렇게 하면 메모리 안정성과 예외 처리가 더 좋아집니다.

 

728x90

'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