오늘은 C++에서 메모리 할당과 관련된 malloc, new, operator new의 차이를 정리해 보려고 한다.
지금은 보통 new나 스마트 포인터, vector, string 같은 RAII를 이용해 자원을 관리하지만,
예전 C 스타일 코드에서는 malloc / free를 사용하는 경우가 많았다.
실제로 직접 malloc을 자주 쓰지는 않더라도, new가 내부적으로 어떤 과정을 거치는지,
그리고 malloc과 무엇이 다른지 정도는 알고 있는 게 중요하다고 생각했다.
이번 글에서는 malloc과 new의 차이, new의 동작 방식,new 표현식과 operator new의 차이에 대해 정리해보았다.
malloc과 new의 차이
malloc과 new의 차이를 정리하면 다음과 같다.
malloc
- 지정한 바이트 수만큼 메모리를 할당하고
void*를 반환한다. void*이므로 원하는 타입 포인터로 캐스팅이 필요하다.- 생성자를 호출하지 않는다.
- 메모리를 초기화하지 않는다.
- 실패 시
nullptr를 반환한다.(C 스타일은 NULL, C++은 nullptr) free로 해제한다.- 메모리 확보 함수이다.
new
- 메모리를 확보한 뒤 객체를 생성 / 초기화하고, 타입에 맞는 포인터를 반환한다.
- 클래스 타입이면 생성자를 호출한다.
- 실패 시 기본적으로
std::bad_alloc예외를 던진다. new(std::nothrow)를 사용하면 예외 대신nullptr를 반환한다.delete로 해제한다.- 함수가 아니라 C++의 표현식이다.
가장 큰 차이는 malloc은 단순히 메모리만 할당하고,new는 메모리 확보 + 객체 생성 / 초기화까지 수행한다는 점이다.
new의 동작 방식
new는 단순히 메모리만 잡는 것이 아니라 대략 다음 과정을 거친다.
- 객체 크기에 맞는 메모리를 확보한다.
- 클래스 타입이면 생성자를 호출해서 객체를 초기화한다.
- 객체 타입에 맞는 포인터를 반환한다.
컴파일러는 컴파일 타임에
해당 타입이 class / struct / union 같은 사용자 정의 타입인지 확인하고,
필요하다면 생성자 호출 코드를 넣는다.
기본형(int, double 등)은 생성자가 없으므로
이 경우에는 생성자 호출 대신 초기화 규칙이 적용된다.
malloc으로 확보한 메모리는 어떻게 초기화할까?
malloc은 생성자를 호출하지 않고 메모리도 초기화하지 않는다.
그래서 malloc으로 확보한 메모리를 사용할 때는 직접 초기화가 필요하다.
대표적으로는 다음과 같은 방식이 있다.
memsetcalloc
예를 들어 calloc은 메모리를 할당하면서 0으로 초기화해준다.
하지만 메모리를 0으로 채우는 것과 객체를 생성하는 것은 다른 개념이다.
trivial 타입의 malloc할당은 memset으로 초기화할 수 있다
모든 타입에 memset을 써도 되는 것은 아니다.
보통 trivial한 구조체 / 클래스처럼 단순한 데이터 묶음에 가까운 타입은malloc으로 메모리를 확보했다면 바이트 단위 초기화를 사용할 수 있다.
예를 들면 아래처럼 단순한 구조체가 있다고 가정하자
struct Point {
int x;
int y;
};
이런 trivial 타입은 malloc으로 메모리를 확보했다면
memset을 이용해 0으로 초기화할 수 있다.
Point* p = (Point*)std::malloc(sizeof(Point));
std::memset(p, 0, sizeof(Point));
이 방식은 생성자를 호출하는 것이 아니라,
확보한 메모리의 바이트 값을 직접 0으로 채우는 것이다.
하지만 생성자 / 소멸자 / 가상 함수 / std::string 같은 멤버를 가진
일반적인 C++ 객체에는 memset을 사용하는 것이 안전하지 않다.
즉, 다음과 같이 구분해서 생각하는 것이 좋다.
- trivial 타입 →
malloc후memset같은 바이트 초기화 가능 - 일반적인 C++ 객체 → 생성자와 정상적인 초기화 사용
만약 raw memory 위에 일반적인 객체를 생성해야 한다면
placement new를 사용하는 방법을 고려할 수 있다.
new 표현식과 operator new의 차이
평소에 보통 new를 이렇게 사용한다.
A* ptr = new A(10, 20);
이건 흔히 그냥 new라고 부르지만, 정확히는 new 표현식(new expression) 이다.
이 new 표현식은 다음을 수행한다.
- 적절한
operator new를 호출해서 메모리를 확보 - 확보한 메모리 위에 객체 생성
- 타입에 맞는 포인터 반환
반면 operator new는 메모리 확보만 담당하는 함수이다.
정리하면 아래와 같다
new 표현식= 메모리 확보 + 객체 생성 + 포인터 반환operator new= 메모리 확보만 담당하는 함수
operator new를 직접 호출하면?
예를 들어 A::operator new(sizeof(A))처럼 직접 호출하면
메모리만 확보되고, 생성자는 호출되지 않는다.
이 시점의 메모리는 객체가 생성된 상태가 아니라 raw memory 상태이다.
그래서 이 메모리를 객체처럼 바로 사용하는 것은 위험하다.
new / delete 연산자 오버로딩으로 동작 확인하기
new 표현식은 적절한 operator new를 호출해 메모리를 확보한 뒤
객체를 생성 / 초기화한다.
delete 표현식은 객체를 파괴한 뒤 적절한 operator delete를 호출한다.
이 과정을 직접 확인하기 위해operator new, operator delete를 오버로딩해볼 수 있다.
형식은 대략 다음과 같다.
static void* operator new(std::size_t size)static void operator delete(void* ptr)
static은 생략해도 정적 멤버 함수로 처리되지만,
붙여두는 편이 의미가 더 분명하게 보인다.
예제 코드
아래 예제는
new 표현식operator new직접 호출placement new
을 확인할 수 있는 코드이다.
#include <iostream>
using namespace std;
class A {
public:
A(int a, int b) : x(a), y(b) {
cout << "생성자 호출\n";
}
~A() {
cout << "소멸자 호출\n";
}
static void* operator new(size_t size) {
cout << "new 연산자 호출\n";
if (void* p = std::malloc(size)) {
return p;
}
throw std::bad_alloc();
}
// placement new
static void* operator new(size_t, void* ptr) {
cout << "placement new 연산자 호출\n";
return ptr;
}
static void operator delete(void* p) {
cout << "delete 연산자 호출\n";
std::free(p);
}
// placement delete
// placement new 에서 예외 발생 시 대응 용도
static void operator delete(void*, void*) {
cout << "placement delete 연산자 호출\n";
}
int x;
int y;
};
int main()
{
// new 표현식
A* ptr1 = new A(10, 20);
cout << ptr1->x << ", " << ptr1->y << endl;
delete ptr1;
cout << endl;
// operator new 직접 호출
// 메모리만 확보하고 객체의 생성은 하지 않은 상태
A* ptr2 = (A*)A::operator new(sizeof(A));
// 생성되지 않은 raw memory 에 접근
// 실제로는 정의되지 않은 동작이므로 위험함
cout << ptr2->x << ", " << ptr2->y << endl;
A::operator delete((void*)ptr2);
cout << endl;
// placement new
void* mem = ::operator new(sizeof(A));
A* ptr3 = new(mem) A(30, 2);
cout << ptr3->x << ", " << ptr3->y << endl;
// placement new 로 만들면 delete 하면 안됨
// 소멸자 직접 호출하고
ptr3->~A();
// 메모리를 반납해야함
::operator delete(ptr3);
}
출력 결과
new 연산자 호출
생성자 호출
10, 20
소멸자 호출
delete 연산자 호출
new 연산자 호출
-842150451, -842150451
delete 연산자 호출
placement new 연산자 호출
생성자 호출
30, 2
소멸자 호출
마무리
malloc은 메모리만 확보하고, new는 메모리 확보 후 객체까지 생성한다.
모던 C++에선 RAII를 사용하는 것이 안전하지만 기존 코드나 C++의 내부 동작을 이해하려면malloc, new, operator new의 차이는 알아둘 필요가 있다고 생각한다.
이번 정리를 통해 new가 단순한 함수가 아니라
객체 생성을 포함한 표현식이라는 점을 확실히 이해할 수 있었다.
'C++, CS' 카테고리의 다른 글
| [C++] RTTI와 RAII 정리 (0) | 2026.04.24 |
|---|---|
| [C++] vtable 정리 (1) | 2026.04.23 |
| [C++] 객체지향 프로그래밍(OOP) 정리 (0) | 2026.04.22 |
| [C++] 포인터와 레퍼런스의 차이 정리 (0) | 2026.04.21 |
| [C++] class와 struct의 차이 정리 (0) | 2026.04.17 |