오늘 모의 면접을 봤는데 개인적으로 많이 아쉬웠습니다 그래서 복기합니다
질문 1. 포인터와 레퍼런스의 차이점을 설명해주세요.
내 대답:
포인터와 레퍼런스의 차이는 주로 메모리 접근 방식과 유연성에 있습니다.
- 포인터는 어떤 객체의 메모리 주소를 저장해서, 나중에 해당 주소를 통해 값을 바꾸거나 확인할 수 있습니다. nullptr이 될 수도 있어서 객체 존재 여부를 체크해야 할 때 유용합니다.
- 레퍼런스는 어떤 객체의 ‘별명’처럼 동작합니다. 한 번 연결되면 다른 객체로 바꿀 수 없고, 항상 유효해야 하므로 존재가 확실한 경우에 사용합니다.
꼬리 질문 1.
레퍼런스를 함수 인자로 사용할 경우, 값을 복사하지 않고 직접 참조만 하기 때문에 복사 비용이 절감됩니다. 이때 발생할 수 있는 문제와 그 해결 방법은 무엇인가요?
❌ 내 대답:
한 객체가 다른 객체의 멤버를 참조하면 객체의 압축성(? → 일관성 or 캡슐화)을 해칠 수 있어서 문제가 됩니다.
✅ 기대 대답:
레퍼런스를 함수 인자로 사용할 경우 원본 데이터를 직접 수정할 수 있기 때문에, 의도치 않은 수정이 발생할 수 있습니다.
이를 방지하려면 const 키워드를 사용해서 읽기 전용 참조로 만들면 됩니다.
예: void Func(const MyStruct& data);
질문 2. Stack과 Heap의 메모리 차이가 무엇인가요?
내 대답:
Stack
- 컴파일 타임에 크기와 생명 주기가 정해진 데이터 저장 영역
- 지역 변수나 일시적인 계산용 변수 저장 (예: float DeltaTime)
- 메모리 자동 할당/해제, 접근 속도 빠름, 오버헤드 적음
- 공간 제한, 동적 할당에는 부적합
- Unreal에서는 Tick 함수 내부의 임시 변수 등에 사용됨
Heap
- 런타임에 크기나 생명 주기가 유동적인 객체 저장
- new, delete 등을 통해 수동으로 메모리 관리
- 유연하지만, 메모리 누수나 해제 타이밍 주의 필요
- Unreal에서는 대부분의 게임 오브젝트(예: AActor, UObject)가 Heap에 생성됨
- SpawnActor, NewObject 등을 사용하며, GC(가비지 컬렉션)으로 관리
꼬리 질문 1:
float DeltaTime의 경우 호출될때 어디에 저장되고 언제 해제 되는지?
⚠️ 내 대답: Stack에 저장되고 함수가 종료되면 해제됩니다.
✅ 기대 대답:
float DeltaTime은 Tick(float DeltaTime) 함수의 매개변수로 전달되는 값입니다.
이 값은 함수 호출 시 생성되는 Stack 프레임 내에 저장됩니다.
Stack 프레임이란?
함수가 호출될 때 생성되는 임시 메모리 블록으로,
매개변수, 지역 변수, 리턴 주소 등의 정보를 저장합니다.
함수가 종료되면 해당 Stack 프레임은 사라지면서,
그 안의 변수들도 함께 자동으로 해제됩니다
따라서 DeltaTime은 매 프레임마다 Tick() 함수가 호출될 때
Stack 프레임에 저장되고, 함수가 끝나면 프레임과 함께 해제됩니다.
꼬리 질문 2:
delete는 누가해주나요?
내 대답: ... (머뭇)... 제가.. 해줍니다?
반응: 네ㅋㅋㅋ 개발자가 해줍니다.
꼬리 질문 3:
Heap과 관련하여 메모리 관리를 해주는 번거로움이 있다고 하셨는데, 스마트 포인터가 있습니다.
그 종류와 차이점에 대해 말씀해주세요.
⚠️ 내 대답:
스마트 포인터의 종류는 unique_ptr, shared_ptr, weak_ptr이 있다.
unique_ptr은 하나만 참조 가능, shared_ptr은 여러 참조 가능.
weak_ptr은 참조 카운트를 증가시키지 않아 순환 참조를 피할 수 있다.
➡️ 핵심 개념은 잘 언급했으나, 각 포인터의 사용 시점, 해제 타이밍, 구체적 차이에 대한 설명이 부족했음.
✅ 기대 대답:
종류 |
소유권 |
참조 카운트 |
해제 시점 |
용도 |
unique_ptr |
단독 소유 |
X |
포인터가 범위를 벗어나면 즉시 해제 |
리소스를 하나만 소유할 때 |
shared_ptr |
공동 소유 |
O |
참조 카운트가 0이 되면 해제 |
여러 개가 소유할 수 있을 때 |
weak_ptr |
소유 X |
O (shared_ptr의 참조를 약하게 유지) |
없음 (shared_ptr이 해제되면 무효화됨) |
순환 참조 방지, shared_ptr 감시 용도 |
꼬리 질문 4:
스마트 포인터를 이용하는 이유는 무엇인가요?
⚠️ 내 대답: 안전 코딩? 메모리 누수 방지?
반응: 하나만 말해주세요
✅ 내 대답: 메무리 누수 방지요..
반응: 네 맞습니다^^
질문 3. 해시 테이블이란 무엇이며, 충돌은 어떻게 해결할 수 있나요?
내 대답:
해시 테이블: 데이터를 "키(Key)와 값(Value)" 쌍으로 저장하는 자료구조
- 빠르게 데이터를 저장하고 검색할 수 있다는 장점.
- Collision이 없다는 가정하, 배열과 다르게 선형 검색 X 시간복잡도: O(1)
- 해시 테이블의 작동 원리:
- 구조: 데이터 저장 자체는 배열에
- Hash Function:특정 키를 받아 배열의 인덱스(해시값)를 반환
- Key 입력 -> Hash Function -> 인덱스 반환 -> 배열 접근
Collision: 해시 Function은 여러 개의 키를 하나의 인덱스로 보낼 수 있기 때문에, 두 개 이상의 키가 같은 인덱스를 가지는 현상
해결 방법:
1. 체이닝(Chaining): 충돌이 발생한 인덱스에 리스트를 두고 값을 계속 추가하는 방식
2. 오픈 어드레싱(Open Addressing): 충돌이 발생하면 빈 다른 인덱스를 찾아서 저장
반응: "인덱스"보다 "해시값"이라는 표현을 쓰는 것이 더 적절함
꼬리 질문 1:
체이닝(Chaning) 방법을 사용하는 이점이 무엇인가요?
❌ 내 대답:
- 충돌이 발생한 인덱스에 계속해서 값을 추가할 수 있다?
✅ 기대 대답:
- 체이닝은 각 해시값에 연결 리스트(혹은 다른 구조, 예: 트리)를 두어 충돌된 데이터를 저장함
- 장점: 해시 테이블의 크기보다 더 많은 데이터를 저장할 수 있으며, 삽입이 간단함
- 연결 리스트의 이점과 연관: 연결 리스트 구조를 쓰면 동적으로 메모리를 사용할 수 있어 충돌이 여러 번 발생해도 유연하게 처리 가능함
- 리사이징 필요성이 덜함
꼬리 질문 2:
좋은 해시 함수란 무엇을 의미할까요?
⚠️ 내 대답:
- Collision이 덜 일어나는 Function?
✅ 기대 대답:
- 좋은 해시 함수란 해시값이 균등하게 분포되도록 만드는 함수
- 충돌을 최소화
- 계산이 너무 느리지 않도록 효율적이어야 함
- 예시로는 DJB2, MurmurHash 등이 있음