[2026] C++ 배열 vs vector | 어느 게 나을까? 성능과 안전성 비교

[2026] C++ 배열 vs vector | 어느 게 나을까? 성능과 안전성 비교

이 글의 핵심

C++ C 배열·std::array vs vector 비교. 고정·동적 크기, 스택·힙, 경계 검사. 최적화 빌드에서의 성능과 안전성 트레이드오프, 언제 무엇을 쓸지 실전 선택 가이드입니다.

들어가며: “배열을 써야 할까, vector를 써야 할까?"

"배열이 더 빠르다고 들었는데 vector를 쓰라고 하네요”

C++는 C 스타일 배열, std::array, std::vector 세 가지 배열 타입을 제공합니다. 각각 메모리 위치, 크기 변경 가능 여부, 안전성이 다릅니다. 비유로 말씀드리면, C 배열·std::array자리 수가 정해진 고정 좌석, vector필요하면 줄을 늘리는 가변 좌석에 가깝습니다. 크기가 런타임에 바뀌면 vector 쪽이 자연스럽습니다.

언제 고정 배열(std::array/C 배열)을, 언제 vector를 쓰나요?

관점고정 크기(스택·std::array 등)vector
성능스택 할당은 힙보다 가벼울 수 있음(작을 때)재할당·용량 관리 비용이 있으나 크기 가변
사용성크기가 컴파일 타임 상수일 때 단순push_back 등으로 동적 확장
적용 시나리오작은 버퍼, 행렬 크기 고정입력 개수를 모를 때, 컨테이너로서 STL과 연동
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// C 스타일 배열 (스택, 고정 크기)
int arr1[5] = {1, 2, 3, 4, 5};
// std::array (스택, 고정 크기, 안전)
std::array<int, 5> arr2 = {1, 2, 3, 4, 5};
// std::vector (힙, 동적 크기, 안전)
std::vector<int> vec = {1, 2, 3, 4, 5};

이 글에서 다루는 것:

  • 배열, std::array, vector의 차이
  • 성능 비교 (벤치마크)
  • 메모리 안전성
  • 상황별 선택 가이드

실무에서 마주한 현실

개발을 배울 때는 모든 게 깔끔하고 이론적입니다. 하지만 실무는 다릅니다. 레거시 코드와 씨름하고, 급한 일정에 쫓기고, 예상치 못한 버그와 마주합니다. 이 글에서 다루는 내용도 처음엔 이론으로 배웠지만, 실제 프로젝트에 적용하면서 “아, 이래서 이렇게 설계하는구나” 하고 깨달은 것들입니다. 특히 기억에 남는 건 첫 프로젝트에서 겪은 시행착오입니다. 책에서 배운 대로 했는데 왜 안 되는지 몰라 며칠을 헤맸죠. 결국 선배 개발자의 코드 리뷰를 통해 문제를 발견했고, 그 과정에서 많은 걸 배웠습니다. 이 글에서는 이론뿐 아니라 실전에서 마주칠 수 있는 함정들과 해결 방법을 함께 다루겠습니다.

목차

  1. 3가지 배열 타입 비교
  2. 성능 벤치마크
  3. 메모리 안전성
  4. 상황별 선택 가이드
  5. 정리

1. 3가지 배열 타입 비교

비교표

항목C 배열std::arraystd::vector
메모리스택스택
크기고정 (컴파일 타임)고정 (컴파일 타임)동적 (런타임)
범위 체크없음at() 제공at() 제공
크기 조회sizeof/수동size()size()
STL 호환부분적완전완전
함수 전달포인터로 decay값 또는 참조참조
안전성낮음높음높음

C 스타일 배열

아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

int arr[5] = {1, 2, 3, 4, 5};
// ❌ 범위 체크 없음
arr[10] = 99;  // 미정의 동작
// ❌ 크기 조회 번거로움
size_t size = sizeof(arr) / sizeof(arr[0]);
// ❌ 함수 전달 시 크기 정보 손실
void foo(int arr[]) {  // int* 로 decay
    // sizeof(arr)는 포인터 크기 (8바이트)
}

std::array (C++11)

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <array>
std::array<int, 5> arr = {1, 2, 3, 4, 5};
// ✅ 범위 체크
arr.at(10);  // 예외 발생: std::out_of_range
// ✅ 크기 조회
size_t size = arr.size();  // 5
// ✅ STL 알고리즘
std::sort(arr.begin(), arr.end());
// ✅ 함수 전달 (크기 정보 유지)
void foo(const std::array<int, 5>& arr) {
    std::cout << arr.size() << '\n';  // 5
}

std::vector

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <vector>
std::vector<int> vec = {1, 2, 3, 4, 5};
// ✅ 동적 크기 변경
vec.push_back(6);
vec.resize(10);
// ✅ 범위 체크
vec.at(10);  // 예외 발생
// ✅ 자동 메모리 관리
// 소멸 시 자동 해제

2. 성능 벤치마크

테스트 1: 접근 속도

아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 100만 번 접근
template <typename Container>
void benchAccess(Container& c) {
    long long sum = 0;
    for (int i = 0; i < 1000000; ++i) {
        sum += c[i % c.size()];
    }
}

결과:

타입시간상대 속도
C 배열2.1ms1.0x (기준)
std::array2.1ms1.0x (동일)
std::vector2.1ms1.0x (동일)
분석: 접근 속도는 동일 (-O2 이상).

테스트 2: 생성/소멸

아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 100만 번 생성/소멸
void benchCreation() {
    for (int i = 0; i < 1000000; ++i) {
        // 테스트 대상
    }
}

결과:

타입시간상대 속도
C 배열 (스택)5ms1.0x (기준)
std::array (스택)5ms1.0x (동일)
std::vector (힙)850ms170x (매우 느림)
분석: 빈번한 생성/소멸은 스택 할당이 유리.

테스트 3: 순회

아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

template <typename Container>
void benchIteration(const Container& c) {
    long long sum = 0;
    for (const auto& x : c) {
        sum += x;
    }
}

결과: 모두 동일 (최적화 빌드).

3. 메모리 안전성

범위 체크

다음은 cpp를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// C 배열: 범위 체크 없음
int arr[5] = {1, 2, 3, 4, 5};
int x = arr[10];  // ❌ 미정의 동작 (크래시 또는 쓰레기 값)
// std::array: at()으로 범위 체크
std::array<int, 5> arr2 = {1, 2, 3, 4, 5};
try {
    int x = arr2.at(10);  // ✅ 예외 발생
} catch (const std::out_of_range& e) {
    std::cerr << "Out of range\n";
}
// std::vector: at()으로 범위 체크
std::vector<int> vec = {1, 2, 3, 4, 5};
try {
    int x = vec.at(10);  // ✅ 예외 발생
} catch (const std::out_of_range& e) {
    std::cerr << "Out of range\n";
}

함수 전달 시 크기 정보

다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ C 배열: 크기 정보 손실
void foo(int arr[]) {  // int* 로 decay
    // sizeof(arr)는 8 (포인터 크기)
}
int arr[5] = {1, 2, 3, 4, 5};
foo(arr);
// ✅ std::array: 크기 정보 유지
void foo(const std::array<int, 5>& arr) {
    std::cout << arr.size() << '\n';  // 5
}
// ✅ std::vector: 크기 정보 유지
void foo(const std::vector<int>& vec) {
    std::cout << vec.size() << '\n';  // 5
}

일상 비유로 이해하기: 메모리를 아파트 건물로 생각해보세요. 스택은 엘리베이터 같아서 빠르지만 공간이 제한적입니다. 힙은 창고처럼 넓지만 물건을 찾는 데 시간이 걸립니다. 포인터는 “3층 302호”처럼 주소를 가리키는 메모지라고 보면 됩니다.

4. 상황별 선택 가이드

결정 트리

아래 코드는 text를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

Q1. 크기가 컴파일 타임에 고정되어 있는가?
    Yes → Q2
    No → vector
Q2. 크기가 작은가? (< 100)
    Yes → std::array
    No → vector (스택 오버플로우 방지)

상황별 권장

상황권장이유
기본 선택vector안전, 동적 크기
크기 고정 + 작음std::array스택 할당, 안전
빈번한 생성/소멸std::array힙 할당 비용 없음
큰 배열vector스택 오버플로우 방지
크기 변경 필요vector동적 크기
C API 연동C 배열호환성

실전 예제

예제 1: 고정 크기 버퍼 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 요구사항: 크기 고정, 빈번한 생성
// 권장: std::array
std::array<char, 1024> buffer;
readData(buffer.data(), buffer.size());

예제 2: 동적 크기 리스트 아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 요구사항: 크기 변경, 안전성
// 권장: vector
std::vector<int> numbers;
numbers.reserve(100);  // 재할당 방지
for (int i = 0; i < n; ++i) {
    numbers.push_back(i);
}

예제 3: 좌표 (x, y, z) 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 요구사항: 크기 3 고정, 빈번한 생성
// 권장: std::array
struct Position {
    std::array<float, 3> coords;  // x, y, z
    
    float& x() { return coords[0]; }
    float& y() { return coords[1]; }
    float& z() { return coords[2]; }
};

실무 사례

사례 1: 게임 엔진 - 파티클 시스템

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <array>
#include <iostream>
#include <vector>
struct Particle {
    std::array<float, 3> position;  // x, y, z (고정 크기)
    std::array<float, 3> velocity;
    float lifetime;
};
class ParticleSystem {
private:
    std::vector<Particle> particles_;  // 동적 크기
    
public:
    void emit(const Particle& particle) {
        particles_.push_back(particle);
    }
    
    void update(float deltaTime) {
        for (auto& particle : particles_) {
            particle.position[0] += particle.velocity[0] * deltaTime;
            particle.position[1] += particle.velocity[1] * deltaTime;
            particle.position[2] += particle.velocity[2] * deltaTime;
            particle.lifetime -= deltaTime;
        }
        
        // 수명이 다한 파티클 제거
        particles_.erase(
            std::remove_if(particles_.begin(), particles_.end(),
                [](const Particle& p) { return p.lifetime <= 0; }),
            particles_.end()
        );
    }
    
    size_t count() const {
        return particles_.size();
    }
};
int main() {
    ParticleSystem system;
    
    Particle p = {{0, 0, 0}, {1, 1, 0}, 5.0f};
    system.emit(p);
    
    system.update(0.016f);  // 60 FPS
    
    std::cout << "파티클 수: " << system.count() << std::endl;
    
    return 0;
}

사례 2: 이미지 처리 - 픽셀 버퍼

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <array>
#include <iostream>
#include <vector>
struct Pixel {
    std::array<uint8_t, 3> rgb;  // R, G, B (고정 크기)
};
class Image {
private:
    int width_;
    int height_;
    std::vector<Pixel> pixels_;  // 동적 크기
    
public:
    Image(int width, int height) : width_(width), height_(height) {
        pixels_.resize(width * height, {{{0, 0, 0}}});
    }
    
    Pixel& at(int x, int y) {
        return pixels_[y * width_ + x];
    }
    
    void fill(const Pixel& color) {
        for (auto& pixel : pixels_) {
            pixel = color;
        }
    }
};
int main() {
    Image img(800, 600);
    
    img.at(100, 100) = {{{255, 0, 0}}};  // 빨간색
    
    img.fill({{{255, 255, 255}}});  // 흰색으로 채우기
    
    return 0;
}

사례 3: 네트워크 - 패킷 버퍼

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <array>
#include <iostream>
#include <vector>
constexpr size_t MAX_PACKET_SIZE = 1024;
struct Packet {
    std::array<char, MAX_PACKET_SIZE> data;  // 고정 크기
    size_t length;
};
class PacketQueue {
private:
    std::vector<Packet> queue_;  // 동적 크기
    
public:
    void enqueue(const Packet& packet) {
        queue_.push_back(packet);
    }
    
    Packet dequeue() {
        Packet packet = queue_.front();
        queue_.erase(queue_.begin());
        return packet;
    }
    
    bool empty() const {
        return queue_.empty();
    }
};
int main() {
    PacketQueue queue;
    
    Packet packet;
    packet.length = 5;
    std::copy_n("Hello", 5, packet.data.begin());
    
    queue.enqueue(packet);
    
    if (!queue.empty()) {
        Packet received = queue.dequeue();
        std::cout << "수신: " << std::string(received.data.begin(), received.data.begin() + received.length) << std::endl;
    }
    
    return 0;
}

사례 4: 행렬 연산

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <array>
#include <iostream>
#include <vector>
// 고정 크기 행렬 (3x3)
using Matrix3x3 = std::array<std::array<float, 3>, 3>;
Matrix3x3 multiply(const Matrix3x3& a, const Matrix3x3& b) {
    Matrix3x3 result = {{{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}};
    
    for (int i = 0; i < 3; ++i) {
        for (int j = 0; j < 3; ++j) {
            for (int k = 0; k < 3; ++k) {
                result[i][j] += a[i][k] * b[k][j];
            }
        }
    }
    
    return result;
}
// 동적 크기 행렬
class Matrix {
private:
    int rows_;
    int cols_;
    std::vector<float> data_;
    
public:
    Matrix(int rows, int cols) : rows_(rows), cols_(cols) {
        data_.resize(rows * cols, 0.0f);
    }
    
    float& at(int row, int col) {
        return data_[row * cols_ + col];
    }
};
int main() {
    // 고정 크기 행렬
    Matrix3x3 m1 = {{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}};
    Matrix3x3 m2 = {{{2, 0, 0}, {0, 2, 0}, {0, 0, 2}}};
    Matrix3x3 m3 = multiply(m1, m2);
    
    // 동적 크기 행렬
    Matrix m4(10, 10);
    m4.at(5, 5) = 42.0f;
    
    return 0;
}

트러블슈팅

문제 1: 스택 오버플로우

증상: 크래시 아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 큰 배열을 스택에 할당
int main() {
    int arr[10000000];  // 40MB (스택 오버플로우)
    return 0;
}
// ✅ vector로 힙에 할당
int main() {
    std::vector<int> vec(10000000);  // 힙 할당
    return 0;
}

문제 2: 범위 오류

증상: 미정의 동작 아래 코드는 cpp를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ C 배열: 범위 체크 없음
int arr[5] = {1, 2, 3, 4, 5};
int x = arr[10];  // ❌ 미정의 동작
// ✅ vector: at()으로 범위 체크
std::vector<int> vec = {1, 2, 3, 4, 5};
try {
    int x = vec.at(10);
} catch (const std::out_of_range& e) {
    std::cerr << "범위 오류" << std::endl;
}

문제 3: 함수 전달 시 크기 손실

증상: 크기 정보 손실 다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ C 배열: 크기 정보 손실
void foo(int arr[]) {  // int* 로 decay
    // sizeof(arr)는 8 (포인터 크기)
}
int arr[5] = {1, 2, 3, 4, 5};
foo(arr);
// ✅ std::array: 크기 정보 유지
void foo(const std::array<int, 5>& arr) {
    std::cout << arr.size() << std::endl;  // 5
}
std::array<int, 5> arr2 = {1, 2, 3, 4, 5};
foo(arr2);
// ✅ std::vector: 크기 정보 유지
void foo(const std::vector<int>& vec) {
    std::cout << vec.size() << std::endl;  // 5
}
std::vector<int> vec = {1, 2, 3, 4, 5};
foo(vec);

문제 4: vector 복사 비용

증상: 성능 저하 다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ vector 값 전달 (복사)
void process(std::vector<int> vec) {  // 복사 발생
    // ...
}
std::vector<int> vec(1000000);
process(vec);  // 100만 개 복사
// ✅ const 참조 전달
void process(const std::vector<int>& vec) {  // 복사 없음
    // ...
}
std::vector<int> vec2(1000000);
process(vec2);  // 복사 없음

마무리

배열과 vector의 선택크기 고정 여부안전성 요구사항에 달려 있습니다.

핵심 요약

  1. 3가지 배열 타입
    • C 배열: 스택, 고정 크기, 안전성 낮음
    • std::array: 스택, 고정 크기, 안전
    • std::vector: 힙, 동적 크기, 안전
  2. 선택 기준
    • 기본: vector (안전, 동적 크기)
    • 크기 고정 + 작음: std::array
    • C API 연동: C 배열 (불가피)
  3. 성능
    • 접근 속도: 동일 (최적화 빌드)
    • 생성/소멸: std::array >>> vector
    • 안전성: vector ≈ std::array >>> C 배열
  4. 주의사항
    • 큰 배열은 스택 오버플로우 주의
    • C 배열은 범위 체크 없음
    • vector는 reserve로 재할당 방지

선택 가이드

상황권장이유
기본 선택vector안전, 동적 크기
크기 고정 + 작음std::array스택 할당
빈번한 생성/소멸std::array힙 할당 비용 없음
큰 배열vector스택 오버플로우 방지
크기 변경 필요vector동적 크기
C API 연동C 배열호환성

코드 예제 치트시트

아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// C 배열
int arr1[5] = {1, 2, 3, 4, 5};
// std::array
std::array<int, 5> arr2 = {1, 2, 3, 4, 5};
arr2.at(0);  // 범위 체크
// std::vector
std::vector<int> vec = {1, 2, 3, 4, 5};
vec.push_back(6);  // 동적 크기
vec.at(0);  // 범위 체크
// 함수 전달
void foo(const std::vector<int>& vec);  // 참조

다음 단계

참고 자료

  • “Effective STL” - Scott Meyers
  • “C++ Primer” - Stanley Lippman
  • cppreference: https://en.cppreference.com/w/cpp/container 한 줄 정리: 대부분의 경우 vector를 사용하고, 크기가 고정되고 작으면 std::array를 고려하며, C 배열은 피한다.
... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3