[2026] C++ 기술 면접 질문 30선 | 포인터와 참조의 차이는? 실전 답변 정리

[2026] C++ 기술 면접 질문 30선 | 포인터와 참조의 차이는? 실전 답변 정리

이 글의 핵심

C++ 기술 면접에서는 포인터·RAII·가상 함수·STL·동시성 등 개념을 구두로 설명할 수 있어야 합니다. 이 글에서는 자주 나오는 질문 30가지와 모범 답변 흐름을 주제별로 묶어 면접 준비에 활용할 수 있게 했습니다.

들어가며: C++ 면접에서 자주 나오는 질문들

C++ 기술 면접은 코딩 테스트와 달리, 개념 이해도실무 경험을 평가합니다. “포인터와 참조의 차이는?”, “가상 함수는 어떻게 동작하나요?”, “멀티스레딩에서 race condition(경쟁 조건—여러 스레드가 같은 자원에 접근할 때 결과가 실행 순서에 따라 달라지는 상황)을 어떻게 막나요?” 같은 질문이 나옵니다. 이 글은 실제 면접에서 자주 나오는 30개 질문모범 답변을 정리합니다. 이 글에서 다루는 것:

  • 포인터와 메모리 관리 (10문)
  • 객체지향·가상 함수 (7문)
  • STL과 템플릿 (6문)
  • 멀티스레딩과 동시성 (5문)
  • 모던 C++ (C++11 이후) (2문)


목차

  1. 포인터와 메모리 관리 (10문)
  2. 객체지향·가상 함수 (7문)
  3. STL과 템플릿 (6문)
  4. 멀티스레딩과 동시성 (5문)
  5. 모던 C++ (C++11 이후) (2문)

1. 포인터와 메모리 관리 (10문)

Q1. 포인터와 참조의 차이는?

답변:

특징포인터참조
재할당가능 (ptr = &other;)불가능 (초기화 후 고정)
nullptr가능불가능 (반드시 유효한 객체)
문법*ptr, ptr->일반 변수처럼 사용
크기포인터 자체 크기 (8바이트, 64비트)별칭이므로 추가 메모리 없음
예시:
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 복사해 붙여넣은 뒤: g++ -std=c++17 -o ptr_ref ptr_ref.cpp && ./ptr_ref
#include <iostream>
int main() {
    int x = 10;
    int* ptr = &x;
    ptr = nullptr;  // ✅ 가능
    int& ref = x;
    std::cout << ref << "\n";  // 10
    return 0;
}

실행 결과: 10 이 한 줄 출력됩니다. 언제 쓰나요?

  • 포인터: 동적 할당, 옵셔널 값 (nullptr 가능), 배열
  • 참조: 함수 인자 (복사 방지), 반환값 (lvalue 반환) 실무 가이드라인:
  • 함수 인자로 객체를 받을 때: const 참조 사용 (복사 비용 절약)
  • 함수가 객체를 수정해야 할 때: 비const 참조 사용
  • 옵셔널 값 (없을 수도 있음): 포인터 또는 std::optional 사용
  • 배열·자료구조: 포인터 사용 예시: 아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ✅ 좋은 코드
void print(const std::string& str) {  // 복사 안 함, 수정 안 함
    std::cout << str << '\n';
}
void modify(std::string& str) {  // 복사 안 함, 수정 가능
    str += " modified";
}
std::string* findUser(int id) {  // 없을 수도 있음
    if (id == 0) return nullptr;
    return new std::string("User");
}

Q2. 스택과 힙의 차이는?

답변:

특징스택 (Stack)힙 (Heap)
할당 방법자동 (지역 변수)수동 (new, malloc)
해제 방법자동 (스코프 벗어나면)수동 (delete, free)
속도빠름 (포인터 이동만)느림 (메모리 관리 오버헤드)
크기제한적 (보통 8MB)큼 (시스템 메모리)
생명주기함수 종료 시 해제명시적 해제 전까지 유지
예시:
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
void func() {
    int a = 10;           // 스택
    int* p = new int(20); // 힙
    delete p;             // 명시적 해제 필요
}  // a는 자동 해제

스택이 빠른 이유:
스택 할당은 스택 포인터(SP)를 이동하는 것만으로 끝납니다. CPU 명령어 12개면 됩니다. 반면 힙 할당은 메모리 관리자(Allocator)가 “어디에 빈 공간이 있나?” 찾아야 하므로, 수십수백 개의 명령어가 필요합니다. 언제 힙을 써야 하나요?:

  • 크기가 큰 데이터 (스택 크기 제한 8MB)
  • 생명주기가 함수를 넘어서는 데이터 (함수 밖에서도 써야 함)
  • 크기를 런타임에 결정 (예: 사용자 입력에 따라 배열 크기 결정)

Q3. 메모리 누수(Memory Leak)란? 어떻게 방지하나요?

답변: 메모리 누수: new로 할당한 메모리를 delete하지 않아서, 프로그램이 종료될 때까지 메모리가 해제되지 않는 현상입니다. 예시: 다음은 간단한 cpp 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

void leak() {
    int* p = new int(42);
    // delete p; 를 안 함 ❌
}  // p는 사라지지만, 힙 메모리는 남음

방지법: 1. 스마트 포인터 사용 (권장): 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 실행 예제
void noLeak() {
    std::unique_ptr<int> p = std::make_unique<int>(42);
    // 자동 해제 ✅
}

2. RAII (Resource Acquisition Is Initialization): 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

class FileHandle {
    FILE* file;
public:
    FileHandle(const char* path) : file(fopen(path, "r")) {}
    ~FileHandle() { if (file) fclose(file); }  // 소멸자에서 해제
};

3. Valgrind로 탐지 (Linux):

valgrind --leak-check=full ./myapp

Q4. 얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy)의 차이는?

답변: 얕은 복사: 포인터 주소만 복사 → 두 객체가 같은 메모리를 가리킴
깊은 복사: 포인터가 가리키는 데이터까지 복사 → 독립적인 메모리 예시: 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class MyClass {
    int* data;
public:
    MyClass(int val) : data(new int(val)) {}
    
    // ❌ 기본 복사 생성자 (얕은 복사)
    // MyClass(const MyClass& other) : data(other.data) {}
    
    // ✅ 깊은 복사
    MyClass(const MyClass& other) : data(new int(*other.data)) {}
    
    ~MyClass() { delete data; }
};

문제 (얕은 복사):

MyClass a(10);
MyClass b = a;  // 얕은 복사 → a.data와 b.data가 같은 주소
// 소멸 시 double free ❌

해결: 복사 생성자·복사 대입 연산자를 명시적으로 정의하거나, std::unique_ptr 사용 (복사 불가, 이동만 가능).

Q5. 스마트 포인터 종류와 차이는?

답변:

종류소유권복사사용 예
unique_ptr단독 소유불가 (이동만)단일 소유자, RAII
shared_ptr공유 소유 (참조 카운트)가능여러 객체가 공유
weak_ptr소유 안 함 (관찰만)-순환 참조 방지
예시:
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
std::unique_ptr<int> p1 = std::make_unique<int>(42);
// std::unique_ptr<int> p2 = p1;  // ❌ 복사 불가
std::unique_ptr<int> p2 = std::move(p1);  // ✅ 이동 (p1은 nullptr)
std::shared_ptr<int> s1 = std::make_shared<int>(42);
std::shared_ptr<int> s2 = s1;  // ✅ 복사 (참조 카운트 2)

Q6. 댕글링 포인터(Dangling Pointer)란?

답변: 이미 해제된 메모리를 가리키는 포인터입니다. 예시:

int* ptr = new int(42);
delete ptr;
*ptr = 10;  // ❌ 댕글링 포인터 사용 (Undefined Behavior)

방지법:

  • deleteptr = nullptr; 설정
  • 스마트 포인터 사용 (자동 해제)

Q7. new와 malloc의 차이는?

답변:

특징newmalloc
언어C++C
타입 안전O (타입 체크)X (void* 반환)
생성자 호출OX
실패 시예외 (std::bad_alloc)nullptr 반환
해제deletefree
예시:
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// new
MyClass* obj = new MyClass(10);  // 생성자 호출 ✅
delete obj;
// malloc
MyClass* obj2 = (MyClass*)malloc(sizeof(MyClass));  // 생성자 호출 X ❌
free(obj2);

권장: C++에서는 new/delete 또는 스마트 포인터 사용.

Q8. RAII란?

답변: RAII (Resource Acquisition Is Initialization): 리소스(메모리, 파일, 락 등)를 객체의 생명주기에 묶는 기법입니다. 생성자에서 획득, 소멸자에서 해제하므로, 예외가 발생해도 자동으로 정리됩니다. 예시: 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class FileLock {
    std::mutex& mtx;
public:
    FileLock(std::mutex& m) : mtx(m) { mtx.lock(); }
    ~FileLock() { mtx.unlock(); }
};
void process() {
    std::mutex mtx;
    FileLock lock(mtx);  // 생성 시 lock
    // 예외가 나도 소멸자에서 unlock ✅
}

STL 예시: std::unique_ptr, std::lock_guard, std::ifstream (파일 자동 닫기)

Q9. 가상 소멸자는 왜 필요한가요?

답변: 다형성을 쓸 때, 베이스 클래스 포인터로 파생 클래스 객체를 delete하면, 베이스 클래스 소멸자만 호출됩니다. 파생 클래스의 리소스가 해제되지 않아 메모리 누수가 발생합니다. 문제 코드: 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Base {
public:
    ~Base() { std::cout << "~Base\n"; }  // ❌ 가상 아님
};
class Derived : public Base {
    int* data;
public:
    Derived() : data(new int[100]) {}
    ~Derived() { delete[] data; std::cout << "~Derived\n"; }
};
int main() {
    Base* ptr = new Derived();
    delete ptr;  // ❌ ~Base만 호출, ~Derived는 호출 안 됨 → 메모리 누수
    return 0;
}

해결: 다음은 간단한 cpp 코드 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

class Base {
public:
    virtual ~Base() { std::cout << "~Base\n"; }  // ✅ 가상 소멸자
};

이제 delete ptr;~Derived → ~Base 순서로 호출됩니다.

Q10. 스택 오버플로우는 언제 발생하나요?

답변: 스택 메모리가 고갈될 때 발생합니다. 주요 원인: 1. 무한 재귀:

void recursive() {
    recursive();  // ❌ 종료 조건 없음
}

2. 큰 배열을 스택에 할당: 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 변수 선언 및 초기화
int main() {
    int arr[10000000];  // ❌ 40MB (스택 기본 크기 8MB 초과)
    return 0;
}

해결:

  • 재귀에 종료 조건 추가
  • 큰 배열은 힙에 할당 (std::vector 또는 new)

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

2. 객체지향·가상 함수 (7문)

Q11. 가상 함수(Virtual Function)는 어떻게 동작하나요?

답변: 가상 함수 테이블(vtable)을 통해 런타임에 호출할 함수를 결정합니다. 동작 원리:

  1. 가상 함수가 있는 클래스는 vtable (함수 포인터 배열)을 가집니다.
  2. 각 객체는 vptr (vtable 포인터)을 멤버로 가집니다.
  3. 가상 함수 호출 시, vptr → vtable → 실제 함수 순서로 간접 호출합니다. 구체적인 메모리 레이아웃: 아래 코드는 code를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 실행 예제
Base 객체:
[vptr: 8바이트][멤버 변수들...]
Base의 vtable:
[0] → Base::show 주소
[1] → Base::other 주소
Derived의 vtable:
[0] → Derived::show 주소  (오버라이드)
[1] → Base::other 주소     (오버라이드 안 함)

호출 과정:

Base* ptr = new Derived();
ptr->show();
  1. ptr->show() 호출
  2. ptrvptr을 읽음 → Derived의 vtable 주소
  3. vtable의 0번 슬롯 읽음 → Derived::show 주소
  4. 해당 주소로 점프 예시: 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Base {
public:
    virtual void show() { std::cout << "Base\n"; }
};
class Derived : public Base {
public:
    void show() override { std::cout << "Derived\n"; }
};
int main() {
    Base* ptr = new Derived();
    ptr->show();  // "Derived" 출력 (런타임에 결정)
    delete ptr;
    return 0;
}

오버헤드: 간접 호출 1회 (보통 무시할 수준).

Q12. 순수 가상 함수(Pure Virtual Function)란?

답변: 구현이 없는 가상 함수로, = 0으로 선언합니다. 순수 가상 함수가 있는 클래스는 추상 클래스(Abstract Class)가 되어, 인스턴스화할 수 없습니다. 예시: 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Shape {
public:
    virtual double area() const = 0;  // 순수 가상 함수
    virtual ~Shape() = default;
};
class Circle : public Shape {
    double radius;
public:
    Circle(double r) : radius(r) {}
    double area() const override { return 3.14 * radius * radius; }
};
int main() {
    // Shape s;  // ❌ 컴파일 에러 (추상 클래스)
    Circle c(5.0);
    std::cout << c.area() << '\n';  // ✅
    return 0;
}

용도: 인터페이스 정의 (Java의 interface와 유사).

Q13. override 키워드는 왜 쓰나요?

답변: override는 C++11에서 추가된 키워드로, 실수로 오버라이드를 안 하는 것을 방지합니다. 문제 코드: 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

class Base {
public:
    virtual void show() const {}
};
class Derived : public Base {
public:
    void show() {}  // ❌ const가 없어서 오버라이드 안 됨 (새로운 함수)
};

해결: 다음은 간단한 cpp 코드 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

class Derived : public Base {
public:
    void show() override {}  // ❌ 컴파일 에러: const가 없어서 오버라이드 실패
};

컴파일러가 오버라이드가 안 되면 에러를 내므로, 실수를 미리 잡을 수 있습니다.

Q14. 다중 상속의 문제점은?

답변: 다이아몬드 문제(Diamond Problem): 두 부모 클래스가 같은 조상을 상속하면, 조상의 멤버가 중복됩니다. 예시: 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class A { public: int value; };
class B : public A {};
class C : public A {};
class D : public B, public C {};  // A가 두 번 상속됨
int main() {
    D d;
    // d.value = 10;  // ❌ 모호함: B::value인가 C::value인가?
    d.B::value = 10;  // ✅ 명시적 지정
    return 0;
}

해결: 가상 상속(Virtual Inheritance): 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};  // A는 한 번만 상속됨
int main() {
    D d;
    d.value = 10;  // ✅ 모호하지 않음
    return 0;
}

실무: 다중 상속은 복잡하므로, 인터페이스(순수 가상 함수만 있는 클래스)를 다중 상속하는 것이 일반적입니다.

Q15. static 멤버 변수는 언제 쓰나요?

답변: 모든 객체가 공유하는 변수입니다. 클래스당 하나만 존재합니다. 예시: 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Counter {
    static int count;  // 선언
public:
    Counter() { count++; }
    static int getCount() { return count; }
};
int Counter::count = 0;  // ✅ 정의 (클래스 외부)
int main() {
    Counter c1, c2, c3;
    std::cout << Counter::getCount() << '\n';  // 3
    return 0;
}

용도: 싱글톤, 객체 개수 추적, 공유 설정값.

Q16. const 멤버 함수는 무엇인가요?

답변: 객체의 상태를 변경하지 않는 함수입니다. const 객체에서도 호출할 수 있습니다. 예시: 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Point {
    int x, y;
public:
    Point(int x, int y) : x(x), y(y) {}
    
    int getX() const { return x; }  // ✅ const 멤버 함수
    void setX(int val) { x = val; }  // 비const
};
int main() {
    const Point p(10, 20);
    std::cout << p.getX() << '\n';  // ✅ const 함수 호출 가능
    // p.setX(30);  // ❌ 컴파일 에러: const 객체에서 비const 함수 호출 불가
    return 0;
}

Q17. friend 키워드는 언제 쓰나요?

답변: 외부 함수·클래스가 private 멤버에 접근할 수 있게 합니다. 예시: 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Box {
    int width;
    friend void printWidth(const Box& b);  // friend 선언
public:
    Box(int w) : width(w) {}
};
void printWidth(const Box& b) {
    std::cout << b.width << '\n';  // ✅ private 접근 가능
}

용도: 연산자 오버로딩 (operator<<), 테스트 코드. 주의: 캡슐화를 깨므로 남용하지 말 것.

3. STL과 템플릿 (6문)

Q18. vector와 list의 차이는?

답변:

특징vectorlist
구조동적 배열이중 연결 리스트
임의 접근O(1)O(N)
삽입/삭제 (중간)O(N)O(1) (이터레이터 있으면)
메모리연속비연속 (노드별 할당)
캐시 효율높음낮음
언제 쓰나요?
  • vector: 대부분의 경우 (임의 접근, 순회 빠름)
  • list: 중간 삽입·삭제가 매우 빈번한 경우

Q19. map과 unordered_map의 차이는?

답변:

특징mapunordered_map
구조레드-블랙 트리해시 테이블
정렬O (키 순서)X
접근 시간O(log N)평균 O(1), 최악 O(N)
메모리적음많음 (해시 테이블)
언제 쓰나요?
  • map: 키 순서가 필요한 경우
  • unordered_map: 빠른 접근이 필요한 경우 (코딩 테스트 대부분)

Q20. 템플릿 특수화(Template Specialization)란?

답변: 특정 타입에 대해 다른 구현을 제공하는 기법입니다. 예시: 다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

template <typename T>
T add(T a, T b) {
    return a + b;
}
// ✅ std::string에 대한 특수화
template <>
std::string add<std::string>(std::string a, std::string b) {
    return a + " " + b;  // 공백 추가
}
int main() {
    std::cout << add(3, 5) << '\n';           // 8
    std::cout << add<std::string>("Hello", "World") << '\n';  // "Hello World"
    return 0;
}

Q21. iterator가 무효화되는 경우는?

답변: vector:

  • push_back, insert, erase재할당이 일어나면 모든 이터레이터 무효화.
  • erase 호출 시 삭제된 위치 이후 이터레이터 무효화. 예시: 아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
std::vector<int> v = {1, 2, 3, 4, 5};
for (auto it = v.begin(); it != v.end(); ++it) {
    if (*it == 3) {
        v.erase(it);  // ❌ it 무효화 → 이후 ++it는 Undefined Behavior
    }
}

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

for (auto it = v.begin(); it != v.end(); ) {
    if (*it == 3) {
        it = v.erase(it);  // ✅ erase는 다음 이터레이터 반환
    } else {
        ++it;
    }
}

Q22. emplace_back과 push_back의 차이는?

답변: push_back: 객체를 생성한 뒤 복사/이동
emplace_back: 컨테이너 내부에서 직접 생성 (in-place construction) 예시:

std::vector<std::pair<int, std::string>> v;
v.push_back({1, "apple"});           // 임시 객체 생성 → 이동
v.emplace_back(2, "banana");         // ✅ 직접 생성 (더 효율적)

효과: 복사·이동 생성자 호출 횟수 감소 → 성능 향상 (특히 무거운 객체).

Q23. 템플릿 메타프로그래밍이란?

답변: 컴파일 타임에 계산을 수행하는 기법입니다. 런타임 오버헤드가 없습니다. 예시 (컴파일 타임 팩토리얼): 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

template <int N>
struct Factorial {
    static const int value = N * Factorial<N-1>::value;
};
template <>
struct Factorial<0> {
    static const int value = 1;
};
int main() {
    std::cout << Factorial<5>::value << '\n';  // 120 (컴파일 타임에 계산)
    return 0;
}

C++11 이후: constexpr로 더 간단하게: 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

constexpr int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}
int main() {
    constexpr int result = factorial(5);  // 컴파일 타임
    return 0;
}

4. 멀티스레딩과 동시성

일상 비유로 이해하기: 동시성은 주방에서 여러 요리를 동시에 하는 것과 비슷합니다. 한 명의 요리사(싱글 스레드)가 국을 끓이다가 불을 줄이고, 그 사이에 야채를 썰고, 다시 국을 확인하는 식이죠. 반면 병렬성은 요리사 여러 명(멀티 스레드)이 각자 다른 요리를 동시에 만드는 겁니다. (5문)

Q24. Race Condition이란? 어떻게 방지하나요?

답변: 여러 스레드가 동시에 같은 메모리를 읽고 쓸 때, 실행 순서에 따라 결과가 달라지는 현상입니다. 왜 문제인가요?
counter++원자적 연산이 아닙니다. 실제로는 세 단계로 나뉩니다:

  1. 메모리에서 counter 값 읽기 (예: 100)
  2. 1 증가 (101)
  3. 메모리에 쓰기 (101) 두 스레드가 동시에 실행하면:
  • 스레드 1: 100 읽음 → 101 계산 → (아직 안 씀)
  • 스레드 2: 100 읽음 → 101 계산 → 101 씀
  • 스레드 1: 101 씀
  • 결과: 101 (200이어야 하는데!) 문제 코드: 다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
int counter = 0;
void increment() {
    for (int i = 0; i < 100000; ++i) {
        counter++;  // ❌ Race condition
    }
}
int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    t1.join(); t2.join();
    std::cout << counter << '\n';  // 200000이 아닐 수 있음
    return 0;
}

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

std::mutex mtx;
int counter = 0;
void increment() {
    for (int i = 0; i < 100000; ++i) {
        std::lock_guard<std::mutex> lock(mtx);  // ✅ 락 획득
        counter++;
    }  // 자동 unlock
}

Q25. Deadlock은 언제 발생하나요?

답변: 두 개 이상의 스레드가 서로의 락을 기다리며 무한 대기하는 상황입니다. 예시: 아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

std::mutex mtx1, mtx2;
void thread1() {
    std::lock_guard<std::mutex> lock1(mtx1);
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
    std::lock_guard<std::mutex> lock2(mtx2);  // ❌ thread2가 mtx2를 잡고 있음
}
void thread2() {
    std::lock_guard<std::mutex> lock2(mtx2);
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
    std::lock_guard<std::mutex> lock1(mtx1);  // ❌ thread1이 mtx1을 잡고 있음
}

해결: 락 순서 통일 또는 std::lock 사용: 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

void thread1() {
    std::lock(mtx1, mtx2);  // ✅ 두 락을 동시에 획득 (데드락 방지)
    std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
    std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
}

Q26. atomic이란?

답변: 원자적 연산(Atomic Operation)을 제공하는 타입입니다. 락 없이 스레드 안전한 읽기·쓰기가 가능합니다. 예시: 다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

std::atomic<int> counter(0);
void increment() {
    for (int i = 0; i < 100000; ++i) {
        counter++;  // ✅ 원자적 증가 (락 불필요)
    }
}
int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    t1.join(); t2.join();
    std::cout << counter << '\n';  // 항상 200000
    return 0;
}

주의: 복잡한 연산(여러 변수 동시 수정)은 뮤텍스를 써야 합니다.

Q27. condition_variable은 언제 쓰나요?

답변: 특정 조건이 만족될 때까지 스레드를 대기시키는 동기화 도구입니다. 생산자-소비자 패턴에서 자주 씁니다. 예시: 다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

std::mutex mtx;
std::condition_variable cv;
std::queue<int> q;
void producer() {
    for (int i = 0; i < 10; ++i) {
        {
            std::lock_guard<std::mutex> lock(mtx);
            q.push(i);
        }
        cv.notify_one();  // 소비자에게 알림
    }
}
void consumer() {
    for (int i = 0; i < 10; ++i) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, []{ return !q.empty(); });  // 큐가 비면 대기
        int val = q.front();
        q.pop();
        std::cout << val << '\n';
    }
}

Q28. thread_local이란?

답변: 각 스레드마다 독립적인 복사본을 가지는 변수입니다. 예시: 다음은 cpp를 활용한 상세한 구현 코드입니다. 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

thread_local int counter = 0;
void increment() {
    for (int i = 0; i < 100; ++i) {
        counter++;  // 각 스레드가 독립적인 counter를 가짐
    }
    std::cout << std::this_thread::get_id() << ": " << counter << '\n';
}
int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    t1.join(); t2.join();
    // 각 스레드가 100 출력 (공유 안 됨)
    return 0;
}

5. 모던 C++ (C++11 이후) (2문)

Q29. 이동 시맨틱(Move Semantics)이란?

답변: 복사 대신 리소스를 이동해서 성능을 높이는 기법입니다. rvalue 참조(&&)를 사용합니다. 왜 필요한가요?
큰 객체(예: std::vector<int>(1000000))를 복사하면 메모리 할당 + 데이터 복사로 느립니다. 하지만 임시 객체(rvalue)는 곧 사라질 것이므로, 복사할 필요 없이 소유권만 이전하면 됩니다. 비유:

  • 복사: 친구 집에 가서 책을 복사기로 복사해 옴 (느림)
  • 이동: 친구가 “이 책 너 가져”라고 소유권을 넘김 (빠름) rvalue vs lvalue:
  • lvalue: 이름이 있는 변수 (예: x, arr[0])
  • rvalue: 임시 값 (예: 42, x + y, std::move(x)) 예시: 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class MyString {
    char* data;
public:
    MyString(const char* str) {
        data = new char[strlen(str) + 1];
        strcpy(data, str);
    }
    
    // 복사 생성자
    MyString(const MyString& other) {
        data = new char[strlen(other.data) + 1];
        strcpy(data, other.data);  // 깊은 복사
    }
    
    // ✅ 이동 생성자
    MyString(MyString&& other) noexcept : data(other.data) {
        other.data = nullptr;  // 소유권 이전
    }
    
    ~MyString() { delete[] data; }
};

효과: std::vector<MyString>push_back 시, 복사 대신 이동 → 성능 향상.

Q30. Lambda 표현식의 캡처 방식은?

답변: [캡처]로 외부 변수를 람다 내부에서 사용할 수 있습니다.

캡처의미
[]아무것도 캡처 안 함
[x]x를 값으로 캡처 (복사)
[&x]x를 참조로 캡처
[=]모든 외부 변수를 값으로 캡처
[&]모든 외부 변수를 참조로 캡처
[this]클래스 멤버 접근
예시:
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
int x = 10, y = 20;
auto lambda1 = [x]() { return x + 1; };  // x 복사
auto lambda2 = [&x]() { x++; };          // x 참조 (수정 가능)
auto lambda3 = [=]() { return x + y; };  // 모두 복사
auto lambda4 = [&]() { x++; y++; };      // 모두 참조

면접 답변 팁

1. 구조화된 답변

나쁜 답변:

“포인터는…주소를 가리키고…참조는…별칭이고…” 좋은 답변: “포인터와 참조의 주요 차이는 세 가지입니다. 첫째, 포인터는 재할당이 가능하지만 참조는 초기화 후 고정됩니다. 둘째, 포인터는 nullptr이 가능하지만 참조는 반드시 유효한 객체를 가리켜야 합니다. 셋째, 문법적으로 포인터는 *->를 쓰고, 참조는 일반 변수처럼 씁니다.”

2. 예시 코드 제시

개념 설명 후 짧은 코드 예시를 보여 주면 이해도가 높아 보입니다.

3. 실무 경험 연결

“실무에서는 메모리 누수를 방지하기 위해 unique_ptr을 기본으로 쓰고, 공유가 필요한 경우만 shared_ptr을 씁니다.”

4. 모르면 솔직히

“정확히는 모르겠지만, 제 생각에는…맞나요?” 추측이라도 사고 과정을 보여 주는 것이 좋습니다. 한 줄 요약: 포인터·메모리·가상 함수·STL·멀티스레딩 등 30문으로 C++ 기술 면접 핵심을 정리해 두었습니다. 다음으로 신입 개발자 면접 준비코딩테스트 준비를 읽어보면 좋습니다.


자주 묻는 질문 (FAQ)

Q. 면접에서 코드를 완벽하게 외워야 하나요?

A: 아닙니다. 개념과 원리를 이해하고, 대략적인 코드 구조를 설명할 수 있으면 됩니다. 면접관이 “정확한 문법은 나중에 찾아보면 되니까, 의사 코드(pseudocode)로 설명해 보세요”라고 할 수도 있습니다.

Q. 신입인데 멀티스레딩 질문이 나오면?

A: 기본 개념(race condition, mutex)만 알아도 됩니다. “실무 경험은 없지만, 학교 프로젝트에서 thread와 mutex를 써 봤습니다”라고 솔직히 말하세요. 신입에게 고급 동시성 패턴을 기대하지 않습니다.

Q. 모던 C++ (C++11 이후)를 모르면 불리한가요?

A: 요즘은 거의 필수입니다. 최소한 auto, 람다, 스마트 포인터, 이동 시맨틱은 알아야 합니다. 면접관이 “C++11 이후 변화를 아나요?”라고 물으면, 위 4가지만 언급해도 충분합니다.

Q. “이 코드의 문제점은?”이라는 질문이 나오면?

A:

  1. 메모리 누수 확인 (new 있는데 delete 없음)
  2. 예외 안전성 (예외 발생 시 리소스 해제되는지)
  3. 효율성 (불필요한 복사, O(N²) 알고리즘)
  4. 엣지 케이스 (빈 입력, nullptr, 오버플로우)

관련 글


C++ 기술 면접개념 암기보다 이해와 적용을 중시합니다. 위 30개 질문은 실제 면접에서 90% 이상 커버되는 핵심 주제입니다. 각 질문에 대해 짧은 코드 예시와 함께 설명할 수 있도록 준비하고, 실무에서 어떻게 쓰는지 한 문장씩 덧붙이면 좋은 인상을 줄 수 있습니다. 모르는 질문이 나와도 당황하지 말고, 아는 범위 내에서 논리적으로 추론하는 모습을 보여 주세요. 검색 시 참고 키워드: C++ 기술 면접, 포인터 참조 차이, 가상 함수 vtable, 스마트 포인터, race condition, 이동 시맨틱

아키텍처 다이어그램

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

graph TD
    A[시작] --> B{조건 확인}
    B -->|예| C[처리 1]
    B -->|아니오| D[처리 2]
    C --> E[완료]
    D --> E

설명: 위 다이어그램은 전체 흐름을 보여줍니다.

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

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

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

C++, 기술면접, 면접질문, 포인터, 메모리관리, STL, 멀티스레딩, 가상함수 등으로 검색하시면 이 글이 도움이 됩니다.

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3