본문으로 건너뛰기 C++ new vs malloc | 생성자·타입 안전성·예외 처리 완벽 비교

C++ new vs malloc | 생성자·타입 안전성·예외 처리 완벽 비교

C++ new vs malloc | 생성자·타입 안전성·예외 처리 완벽 비교

이 글의 핵심

C++ new vs malloc 차이점. 생성자·소멸자, 타입 안전성, 실패 시 예외 vs nullptr. 성능은 거의 비슷하지만 C++ 객체에는 new·delete를 쓰는 것이 맞는 이유와 실전 선택을 정리합니다.

들어가며

C++는 newmalloc 두 가지 메모리 할당 방법을 제공합니다. malloc은 C에서 물려받은 함수이며, new는 C++ 전용 연산자입니다. 비유로 말씀드리면, malloc빈 방만 열어 주는 것이으며, new가구를 조립해 놓은 방을 넘겨주는 것에 가깝습니다. C++ 객체에는 생성자·소멸자가 있으므로 “방만”이 아니라 초기화까지 맞추려면 new/delete 쪽이 맞습니다.

이 글을 읽으면

  • new와 malloc의 7가지 차이점을 이해합니다
  • 생성자 호출, 타입 안전성, 예외 처리의 차이를 파악합니다
  • 성능 비교와 실무 선택 기준을 익힙니다
  • 혼용 시 주의사항과 C 라이브러리 연동 패턴을 확인합니다

new vs malloc 7가지 차이

비교표

항목newmalloc
언어C++ 연산자C 함수
생성자 호출✅ 호출함❌ 호출 안 함
타입 안전성✅ 안전 (캐스팅 불필요)❌ 불안전 (캐스팅 필요)
크기 계산자동수동 (sizeof)
실패 시예외 (bad_alloc)nullptr 반환
해제deletefree
배열 할당new[]malloc + 크기
오버로드가능 (operator new)불가능

실전 구현

1) 기본 타입 할당

malloc

#include <cstdlib>
#include <iostream>
int main() {
    // malloc: 캐스팅 필요
    int* ptr = (int*)malloc(sizeof(int));
    
    if (ptr == nullptr) {  // nullptr 체크 필요
        std::cerr << "할당 실패" << std::endl;
        return 1;
    }
    
    *ptr = 42;
    std::cout << *ptr << std::endl;
    
    free(ptr);
    
    return 0;
}

new

#include <iostream>
int main() {
    // new: 캐스팅 불필요, 초기화 동시에
    int* ptr = new int(42);
    
    std::cout << *ptr << std::endl;
    
    delete ptr;
    
    return 0;
}

2) 클래스 할당

#include <iostream>
class MyClass {
private:
    int x_;
    
public:
    MyClass() : x_(0) {
        std::cout << "생성자 호출" << std::endl;
    }
    
    ~MyClass() {
        std::cout << "소멸자 호출" << std::endl;
    }
    
    void setValue(int x) { x_ = x; }
    int getValue() const { return x_; }
};
int main() {
    // ❌ malloc: 생성자 호출 안 됨
    MyClass* obj1 = (MyClass*)malloc(sizeof(MyClass));
    // "생성자 호출" 출력 안 됨
    // obj1->x_는 쓰레기 값
    free(obj1);  // 소멸자 호출 안 됨
    
    // ✅ new: 생성자 호출
    MyClass* obj2 = new MyClass();
    // "생성자 호출" 출력
    obj2->setValue(42);
    std::cout << obj2->getValue() << std::endl;
    delete obj2;  // "소멸자 호출" 출력
    
    return 0;
}

중요: 클래스 객체는 반드시 new를 사용해야 합니다.

3) 배열 할당

malloc

// malloc: 크기 수동 계산
int* arr1 = (int*)malloc(10 * sizeof(int));
for (int i = 0; i < 10; ++i) {
    arr1[i] = i;
}
free(arr1);

new

// new: 크기 자동 계산
int* arr2 = new int[10];
for (int i = 0; i < 10; ++i) {
    arr2[i] = i;
}
delete[] arr2;  // 배열은 delete[]

4) 예외 처리

malloc: nullptr 반환

#include <cstdlib>
#include <iostream>
int main() {
    int* ptr = (int*)malloc(1000000000000);  // 1TB 할당 (실패)
    
    if (ptr == nullptr) {  // 수동 체크 필요
        std::cerr << "할당 실패" << std::endl;
        return 1;
    }
    
    free(ptr);
    
    return 0;
}

new: 예외 던짐

#include <iostream>
int main() {
    try {
        int* ptr = new int[1000000000000];  // 1TB 할당 (실패)
        delete[] ptr;
    } catch (const std::bad_alloc& e) {
        std::cerr << "할당 실패: " << e.what() << std::endl;
    }
    
    return 0;
}

new (std::nothrow)

#include <new>
#include <iostream>
int main() {
    int* ptr = new (std::nothrow) int[1000000000000];
    
    if (ptr == nullptr) {
        std::cerr << "할당 실패" << std::endl;
        return 1;
    }
    
    delete[] ptr;
    
    return 0;
}

고급 활용

1) placement new

#include <new>
#include <iostream>
int main() {
    // 메모리 할당
    char buffer[sizeof(MyClass)];
    
    // placement new: 기존 메모리에 객체 생성
    MyClass* obj = new (buffer) MyClass();
    
    obj->setValue(42);
    std::cout << obj->getValue() << std::endl;
    
    // 소멸자 수동 호출
    obj->~MyClass();
    
    return 0;
}

2) 커스텀 operator new

#include <iostream>
#include <cstdlib>
void* operator new(size_t size) {
    std::cout << "커스텀 new: " << size << " bytes" << std::endl;
    void* ptr = malloc(size);
    if (!ptr) throw std::bad_alloc();
    return ptr;
}
void operator delete(void* ptr) noexcept {
    std::cout << "커스텀 delete" << std::endl;
    free(ptr);
}
int main() {
    int* ptr = new int(42);
    delete ptr;
    
    return 0;
}

3) C 라이브러리 래핑

#include <memory>
#include <cstdlib>
extern "C" {
    char* c_function() {
        return (char*)malloc(100);
    }
}
std::unique_ptr<char, decltype(&free)> wrap_c_function() {
    char* ptr = c_function();
    return {ptr, free};  // 커스텀 삭제자
}
int main() {
    auto ptr = wrap_c_function();
    // 자동으로 free 호출
    
    return 0;
}

성능 비교

기본 타입 할당

테스트: 100만 번 할당/해제

#include <chrono>
#include <iostream>
void benchMalloc() {
    for (int i = 0; i < 1000000; ++i) {
        int* ptr = (int*)malloc(sizeof(int));
        *ptr = i;
        free(ptr);
    }
}
void benchNew() {
    for (int i = 0; i < 1000000; ++i) {
        int* ptr = new int(i);
        delete ptr;
    }
}
int main() {
    auto start1 = std::chrono::high_resolution_clock::now();
    benchMalloc();
    auto end1 = std::chrono::high_resolution_clock::now();
    auto time1 = std::chrono::duration_cast<std::chrono::milliseconds>(end1 - start1).count();
    
    auto start2 = std::chrono::high_resolution_clock::now();
    benchNew();
    auto end2 = std::chrono::high_resolution_clock::now();
    auto time2 = std::chrono::duration_cast<std::chrono::milliseconds>(end2 - start2).count();
    
    std::cout << "malloc/free: " << time1 << "ms" << std::endl;
    std::cout << "new/delete: " << time2 << "ms" << std::endl;
    
    return 0;
}

결과:

방법시간상대 속도
malloc/free850ms1.0x
new/delete860ms1.01x
결론: 성능 차이는 거의 없음

실무 사례

사례 1: 게임 엔진 - 객체 풀

#include <vector>
#include <iostream>
class GameObject {
private:
    int id_;
    bool active_;
    
public:
    GameObject() : id_(0), active_(false) {
        std::cout << "GameObject 생성" << std::endl;
    }
    
    ~GameObject() {
        std::cout << "GameObject 소멸" << std::endl;
    }
    
    void activate(int id) {
        id_ = id;
        active_ = true;
    }
    
    void deactivate() {
        active_ = false;
    }
};
class ObjectPool {
private:
    std::vector<GameObject*> pool_;
    
public:
    ObjectPool(size_t size) {
        for (size_t i = 0; i < size; ++i) {
            pool_.push_back(new GameObject());  // new 사용
        }
    }
    
    ~ObjectPool() {
        for (auto* obj : pool_) {
            delete obj;  // 소멸자 호출
        }
    }
    
    GameObject* acquire() {
        if (pool_.empty()) return nullptr;
        auto* obj = pool_.back();
        pool_.pop_back();
        return obj;
    }
    
    void release(GameObject* obj) {
        obj->deactivate();
        pool_.push_back(obj);
    }
};
int main() {
    ObjectPool pool(10);
    
    auto* obj = pool.acquire();
    obj->activate(1);
    
    pool.release(obj);
    
    return 0;
}

사례 2: C 라이브러리 연동

#include <memory>
#include <cstdlib>
#include <cstring>
extern "C" {
    struct CData {
        int id;
        char name[100];
    };
    
    CData* create_data(int id, const char* name) {
        CData* data = (CData*)malloc(sizeof(CData));
        data->id = id;
        strncpy(data->name, name, 99);
        data->name[99] = '\0';
        return data;
    }
    
    void destroy_data(CData* data) {
        free(data);
    }
}
std::unique_ptr<CData, decltype(&destroy_data)> wrap_data(int id, const char* name) {
    CData* data = create_data(id, name);
    return {data, destroy_data};
}
int main() {
    auto data = wrap_data(1, "test");
    std::cout << data->id << ", " << data->name << std::endl;
    // 자동으로 destroy_data 호출
    
    return 0;
}

사례 3: 메모리 풀 - placement new

#include <new>
#include <vector>
#include <iostream>
class MemoryPool {
private:
    char* buffer_;
    size_t size_;
    size_t offset_;
    
public:
    MemoryPool(size_t size) : size_(size), offset_(0) {
        buffer_ = (char*)malloc(size);  // malloc으로 버퍼 할당
    }
    
    ~MemoryPool() {
        free(buffer_);
    }
    
    template<typename T, typename... Args>
    T* allocate(Args&&... args) {
        if (offset_ + sizeof(T) > size_) {
            throw std::bad_alloc();
        }
        
        void* ptr = buffer_ + offset_;
        offset_ += sizeof(T);
        
        return new (ptr) T(std::forward<Args>(args)...);  // placement new
    }
};
int main() {
    MemoryPool pool(1024);
    
    int* p1 = pool.allocate<int>(42);
    int* p2 = pool.allocate<int>(100);
    
    std::cout << *p1 << ", " << *p2 << std::endl;
    
    return 0;
}

트러블슈팅

문제 1: malloc-delete 혼용

증상: 크래시 또는 메모리 누수

// ❌ 미정의 동작
int* ptr1 = (int*)malloc(sizeof(int));
delete ptr1;  // ❌ malloc-delete 혼용
// ❌ 미정의 동작
int* ptr2 = new int(42);
free(ptr2);  // ❌ new-free 혼용
// ✅ 올바른 짝
int* ptr3 = (int*)malloc(sizeof(int));
free(ptr3);
int* ptr4 = new int(42);
delete ptr4;

문제 2: 생성자 호출 누락

증상: 멤버 변수가 쓰레기 값

class MyClass {
private:
    int x_;
    
public:
    MyClass() : x_(0) {}
    int getValue() const { return x_; }
};
// ❌ malloc: 생성자 호출 안 됨
MyClass* obj = (MyClass*)malloc(sizeof(MyClass));
std::cout << obj->getValue() << std::endl;  // 쓰레기 값
free(obj);
// ✅ new: 생성자 호출
MyClass* obj2 = new MyClass();
std::cout << obj2->getValue() << std::endl;  // 0
delete obj2;

문제 3: 배열 delete 누락

증상: 메모리 누수

// ❌ delete 사용 (배열은 delete[])
int* arr = new int[10];
delete arr;  // ❌ 메모리 누수 가능
// ✅ delete[] 사용
int* arr2 = new int[10];
delete[] arr2;

문제 4: 타입 크기 실수

증상: 버퍼 오버플로우

// ❌ 크기 실수
int* arr = (int*)malloc(10);  // 10바이트 (int 2.5개?)
arr[5] = 42;  // 버퍼 오버플로우
// ✅ 올바른 크기
int* arr2 = (int*)malloc(10 * sizeof(int));  // int 10개
arr2[5] = 42;
free(arr2);
// 또는 new
int* arr3 = new int[10];  // 크기 자동
arr3[5] = 42;
delete[] arr3;

마무리

C++에서는 new를 사용하세요. malloc은 생성자를 호출하지 않아 객체가 제대로 초기화되지 않습니다.

핵심 요약

  1. new vs malloc
    • new: 생성자 호출, 타입 안전, 예외
    • malloc: 메모리만 할당, 캐스팅 필요, nullptr
  2. 선택 기준
    • C++ 클래스: new (생성자 필요)
    • C 라이브러리 연동: malloc
    • 일반적인 경우: 스마트 포인터
  3. 혼용 금지
    • malloc-free, new-delete 짝 맞추기
    • 혼용 시 미정의 동작
  4. 성능
    • 거의 차이 없음
    • new는 malloc + 생성자

선택 가이드

상황권장이유
C++ 클래스new생성자 호출 필요
기본 타입new타입 안전성
C 라이브러리 연동mallocC 코드와 호환
placement newmalloc + new메모리 풀
일반적인 경우스마트 포인터자동 해제

코드 예제 치트시트

// malloc
int* ptr1 = (int*)malloc(sizeof(int));
free(ptr1);
// new
int* ptr2 = new int(42);
delete ptr2;
// 배열
int* arr1 = (int*)malloc(10 * sizeof(int));
free(arr1);
int* arr2 = new int[10];
delete[] arr2;
// 스마트 포인터
auto ptr3 = std::make_unique<int>(42);
auto arr3 = std::make_unique<int[]>(10);
// C 라이브러리 래핑
std::unique_ptr<char, decltype(&free)> ptr4(c_function(), free);

다음 단계

참고 자료

  • “Effective C++” - Scott Meyers
  • “C++ Primer” - Stanley Lippman
  • cppreference: https://en.cppreference.com/w/cpp/memory 한 줄 정리: C++에서는 생성자 호출과 타입 안전성을 위해 new를 사용하며, 가능하면 스마트 포인터로 자동 해제를 보장한다.

자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. C++ new vs malloc 차이점. 생성자·소멸자, 타입 안전성, 실패 시 예외 vs nullptr. 성능은 거의 비슷하지만 C++ 객체에는 new·delete를 쓰는 것이 맞는 이유와 실전 선택을 정리합니다. 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

C++, new, malloc, 메모리할당, 동적할당, 생성자, 타입안전성 등으로 검색하시면 이 글이 도움이 됩니다.