[CS 기초]
❓ "32비트와 64비트의 차이에 대해 설명해주세요."
32비트와 64비트는 컴퓨터가 한 번에 처리할 수 있는 데이터 크기와 주소 지정 범위의 차이를 말합니다.
먼저 가장 큰 차이는 메모리 주소 공간입니다. 32비트 시스템은 이론적으로 약 4GB(2³²)의 메모리만 주소화할 수 있는 반면, 64비트는 최대 2의 64승 바이트까지 지원 가능하여 실질적으로는 수십 테라바이트까지 확장할 수 있습니다.
이뿐 아니라 CPU 레지스터의 폭, 데이터 처리량, 병렬 처리 능력에서도 차이가 납니다. 예를 들어, 64비트 CPU는 64비트 정수나 포인터를 한 번에 처리할 수 있어 더 복잡한 연산이나 대규모 데이터 처리에 유리합니다.
실무에서는 대형 프로그램, 특히 메모리 사용이 많은 애플리케이션(예: 머신러닝, 게임, 영상 처리 등)에서 64비트가 필수적입니다. 예전엔 32비트에서 작동하던 드라이버나 소프트웨어 호환성 문제가 있었지만, 요즘은 대부분 64비트로 전환되어 표준이 되었습니다.
❓ "Python에서 Call by Value와 Call by Reference는 어떻게 다르나요?"
Python에서는 엄밀히 말하면 'Call by Value'나 'Call by Reference' 대신 Call by Object Reference 혹은 Call by Assignment라는 표현이 더 정확합니다.
Python에서 함수에 인자를 넘기면, 해당 객체에 대한 참조(reference)가 함수에 전달됩니다. 이때 전달된 객체가 불변 객체(immutable)인지 가변 객체(mutable)인지에 따라 동작이 달라집니다.
- 예를 들어 정수, 문자열, 튜플 같은 불변 객체는 함수 내에서 변경하려 해도 새로운 객체가 생성되므로 원본에는 영향을 주지 않습니다.
- 반면 리스트나 딕셔너리 같은 가변 객체는 함수 내에서 직접 원본 객체의 값을 변경할 수 있습니다.
제가 리스트를 정렬하는 함수에서, 리스트 자체를 함수 내에서 sort()로 정렬하면 원본이 변경되어 의도치 않은 결과가 발생한 경험이 있습니다. 이런 이유로 인자를 복사하거나 copy.deepcopy()를 써서 안전하게 함수 내에서 다루는 습관을 들이고 있습니다.
❓ "실수 표현 방식인 부동소수점과 고정소수점의 차이를 설명해주세요."
실수는 컴퓨터에서 정수보다 표현이 복잡한데, 대표적으로 부동소수점(Floating-point)과 고정소수점(Fixed-point) 방식이 사용됩니다.
고정소수점은 소수점의 위치가 고정되어 있어 연산이 단순하고 빠르지만, 표현할 수 있는 숫자의 범위가 제한적입니다. 임베디드 시스템처럼 하드웨어 리소스가 제한된 환경에서 주로 사용됩니다.
반면 부동소수점은 소수점을 부동적으로 이동시킬 수 있어 더 넓은 범위와 정밀도를 표현할 수 있습니다. IEEE 754 표준에 따라 부호, 지수, 가수로 나뉘며, float(32bit)와 double(64bit) 타입이 이에 해당합니다. 다만, 이진 표현으로 인해 오차가 발생할 수 있으며, 실무에서 금융 계산 등 정밀도가 필요한 곳에서는 Decimal 라이브러리를 사용하기도 합니다.
❓ "C에서 포인터와 배열의 차이를 설명해주세요."
포인터는 메모리 주소를 저장하는 변수이고, 배열은 연속된 메모리 공간입니다. C에서는 배열 이름 자체가 배열의 첫 번째 요소를 가리키는 포인터처럼 동작하기 때문에, 포인터와 배열은 유사하게 보일 수 있습니다.
예를 들어 int a[5];에서 a는 &a[0]과 같은 주소를 의미하며, *(a + i) 방식으로 배열 요소에 접근할 수 있습니다. 하지만 배열은 선언 시 크기가 고정되며, 포인터는 동적 메모리 할당이 가능하다는 점에서 차이가 있습니다.
제가 C로 간단한 버퍼를 구현할 때, 포인터 연산을 잘못 사용해 버퍼 오버런이 발생한 적이 있습니다. 이 경험을 통해 포인터의 위험성과 배열과의 차이를 명확히 이해하게 되었습니다.
❓ "Garbage Collection이 무엇인가요?"
Garbage Collection은 프로그래머가 직접 메모리 해제를 하지 않아도, 더 이상 사용되지 않는 객체를 자동으로 메모리에서 제거하는 기능입니다. Java, Python, C# 같은 언어에서 지원하며, C나 C++은 기본적으로 수동 메모리 관리 언어입니다.
Python에서는 참조 카운팅(reference counting)과 cycle detection을 조합한 가비지 컬렉터를 사용합니다. 참조가 0이 된 객체는 자동으로 해제되며, 순환 참조는 추가적인 알고리즘을 통해 탐지합니다.
Garbage Collection은 프로그래밍을 더 안전하게 만들어주지만, 실행 타이밍이 예측 불가능해 성능 지연이 발생할 수 있습니다. 그래서 실시간 시스템에서는 수동 메모리 관리가 여전히 선호되기도 합니다.
❓ "Stack과 Heap의 차이에 대해 설명해주세요."
네, Stack과 Heap은 프로그램이 메모리를 사용하는 방식에서 중요한 두 영역입니다.
- Stack은 함수 호출 시 생성되는 지역 변수나 매개변수를 저장하는 메모리 공간입니다. LIFO 구조로 작동하기 때문에 함수 호출이 끝나면 그에 따른 메모리도 자동으로 해제됩니다. 메모리 접근 속도가 빠르고 구조가 단순해 성능이 좋지만, 크기가 제한적이고 동적으로 크기를 조절하기 어렵습니다.
- Heap은 동적으로 메모리를 할당할 때 사용되는 공간입니다. 예를 들어 C에서는 malloc, C++에서는 new, Python에서는 객체 생성 시 내부적으로 Heap을 사용합니다. 사용자가 명시적으로 해제하지 않으면 계속 메모리에 남아 있어서 메모리 누수(leak)의 원인이 되기도 합니다. 또한, Heap은 동기화를 필요로 하기 때문에 Stack보다 상대적으로 느릴 수 있습니다.
제가 진행했던 프로젝트 중 TCP 서버-클라이언트 통신 구현에서, 서버가 다수 클라이언트를 관리하기 위해 구조체 포인터를 동적 할당해 연결 리스트 형태로 관리했습니다. 이때 Heap에 메모리를 할당하고, 종료 시 정확히 해제하지 않으면 메모리 누수가 발생하는 것을 경험했고, valgrind를 통해 디버깅했습니다. 이처럼 Stack과 Heap은 단순한 구조 차이뿐만 아니라 실전에서 메모리 안정성과 직결되기 때문에 명확히 이해하고 있어야 한다고 생각합니다.
❓ "JPG, PNG, GIF의 차이를 설명해주세요."
이 세 가지는 모두 이미지 파일 포맷이지만, 압축 방식, 색상 수, 투명도 지원, 애니메이션 지원 여부에서 차이가 있습니다.
- JPG (JPEG): 손실 압축을 사용하여 파일 크기를 줄이는 데 최적화된 포맷입니다. 색상이 풍부한 사진 이미지에 적합하지만, 반복적인 압축과 저장 시 품질 손상이 누적될 수 있습니다.
- PNG: 무손실 압축을 사용하며, **투명도(알파 채널)**를 지원합니다. 품질을 유지해야 하는 UI 요소나 로고에 적합합니다. 대신 파일 크기는 JPG보다 큰 편입니다.
- GIF: 256색만 지원하는 팔레트 기반 포맷으로, 애니메이션이 가능하고 간단한 움직임 표현에 적합합니다. 투명도는 지원하지만 반투명은 불가능합니다.
예를 들어, 웹 프론트엔드 프로젝트를 할 때 로고는 PNG, 배너 사진은 JPG, 로딩 애니메이션은 GIF로 사용했습니다. 이미지 포맷 선택은 퍼포먼스와 품질 모두에 영향을 미칩니다.
❓ "2의 보수와 1의 보수의 차이를 설명해주세요."
2의 보수와 1의 보수는 음수를 표현하기 위한 이진수 방식입니다.
- 1의 보수는 양수의 모든 비트를 반전시키는 방식입니다. 단점은 0을 +0과 -0 두 가지로 표현하게 되어 비효율적입니다.
- 2의 보수는 1의 보수에 +1을 더한 것으로, 현대 컴퓨터 시스템에서 표준으로 사용됩니다. 덧셈과 뺄셈 연산을 동일한 회로로 처리할 수 있어 연산 효율성이 높습니다.
예를 들어 8비트 기준, +5는 00000101, -5는 11111011로 표현됩니다. 실습 중 2의 보수를 직접 연산해보며 이 방식이 얼마나 깔끔하게 음수 연산을 처리하는지 체감한 적이 있습니다.
❓ "CPU와 GPU의 차이를 설명해주세요."
CPU와 GPU는 모두 프로세서이지만, 설계 목적과 구조가 다릅니다.
- CPU (Central Processing Unit)는 소수의 고성능 코어로 복잡한 로직과 순차적 처리에 강합니다. OS 제어, 논리 판단, 프로세스 스케줄링 등 범용적인 작업을 수행합니다.
- GPU (Graphics Processing Unit)는 수백~수천 개의 단순한 코어를 갖추고 대규모 데이터를 병렬로 처리합니다. 주로 그래픽 렌더링, 머신러닝 연산, 벡터 연산 등에 활용됩니다.
실제로 딥러닝 프레임워크에서 학습 속도를 획기적으로 높이기 위해 GPU를 사용하며, CUDA나 OpenCL을 통해 병렬 연산을 최적화할 수 있습니다.
❓ "SSD와 HDD의 차이를 설명해주세요."
HDD는 자기 디스크 회전을 통해 데이터를 읽고 쓰는 반면, SSD는 반도체 플래시 메모리를 사용하여 데이터를 저장합니다.
- HDD는 가격이 저렴하고 대용량 저장에 유리하지만, 기계적 회전 방식으로 인해 속도가 느리고 충격에 취약합니다.
- SSD는 기계적 부품이 없기 때문에 데이터 접근 속도가 매우 빠르고 내구성이 뛰어나며, 부팅 속도와 파일 접근 시간이 대폭 개선됩니다.
제가 개발환경을 HDD에서 SSD로 바꾼 이후, 컴파일 속도와 빌드 시간이 크게 줄어 작업 효율이 향상된 경험이 있습니다.
[OS]
❓ "프로세스와 스레드의 차이에 대해 설명해주세요."
프로세스는 운영체제에서 자원을 할당받는 독립 실행 단위이고, 스레드는 프로세스 내에서 작업을 수행하는 실행 단위입니다. 하나의 프로세스는 여러 개의 스레드를 가질 수 있으며, 이들은 메모리 공간(코드, 데이터, 힙)을 공유합니다.
예를 들어 웹 브라우저는 하나의 프로세스이고, 탭마다 별도의 스레드로 동작하면서 동시에 다양한 작업을 수행합니다.
스레드를 사용하면 자원 공유로 인해 효율적이지만, 반대로 동기화 문제와 레이스 컨디션이 발생하기 쉬워 세마포어나 뮤텍스 같은 동기화 기법이 필요합니다. 제가 구현한 서버 프로젝트에서도 다중 클라이언트를 처리하기 위해 멀티스레딩을 사용했고, 이 과정에서 공유 데이터 접근 시 동기화의 중요성을 체감했습니다.
❓ "Context Switching이 무엇인가요?"
Context Switching은 CPU가 하나의 프로세스 또는 스레드에서 다른 것으로 전환될 때, 현재 작업 상태를 저장하고 새로운 작업 상태를 불러오는 과정을 말합니다. 이 과정에서 레지스터, 프로그램 카운터, 스택 포인터 등의 정보를 저장하고 복원해야 하므로 오버헤드가 발생합니다.
멀티태스킹 운영체제에서는 Context Switching이 필수지만, 너무 잦은 전환은 성능 저하를 초래할 수 있습니다. 따라서 스케줄링 알고리즘이 이를 최소화하면서도 공정하게 자원을 배분하도록 설계되어야 합니다.
❓ "Deadlock이란 무엇이고, 해결 방법은?"
Deadlock은 둘 이상의 프로세스가 서로가 가진 자원을 기다리며 무한정 대기 상태에 빠지는 현상입니다. 이는 상호 배제, 점유 대기, 비선점, 환형 대기의 네 가지 조건이 모두 만족될 때 발생합니다.
해결 방법으로는 자원 낙관적 할당을 통한 Deadlock Prevention, 발생 여부를 실시간으로 감지하는 Detection, 또는 Banker 알고리즘 같은 회피(Avoidance) 기법이 있습니다. 실무에서는 자원 요청 순서를 명확히 정하거나, 일정 시간 내 응답이 없으면 타임아웃으로 강제 종료하는 방식도 활용됩니다.
❓ "CPU 스케줄링 알고리즘에 대해 설명해주세요."
CPU 스케줄링은 다중 프로세스를 효율적으로 실행하기 위해 CPU를 어떤 순서로 할당할지 결정하는 방식입니다. 주요 알고리즘은 다음과 같습니다:
- FCFS (First Come First Serve): 도착 순서대로 실행합니다. 구현은 간단하지만 대기 시간이 길어질 수 있습니다.
- SJF (Shortest Job First): 수행 시간이 짧은 프로세스를 먼저 실행하여 평균 대기 시간을 줄입니다. 단점은 긴 작업이 계속 밀릴 수 있다는 점입니다.
- Round Robin: 각 프로세스에 일정 시간만큼 CPU를 할당하며 순환합니다. 공정성을 보장하지만, 타임 슬라이스 설정이 중요합니다.
- Priority Scheduling: 프로세스에 우선순위를 부여해 높은 우선순위를 먼저 실행합니다. 우선순위가 낮은 프로세스는 굶을 수 있습니다(Starvation).
실제 OS 과제에서 Round Robin 스케줄러를 구현하며 context switching 횟수와 타임 슬라이스 조정이 전체 성능에 미치는 영향을 실감했습니다.
❓ "Semaphore와 Mutex의 차이를 설명해주세요."
Semaphore와 Mutex는 모두 임계 구역에 대한 동기화 수단입니다.
- Mutex (Mutual Exclusion)는 하나의 스레드만 임계 구역에 진입할 수 있도록 제어하는 이진 락입니다. 반드시 락을 획득한 스레드가 해제해야 하며, 오용 시 데드락이 발생할 수 있습니다.
- Semaphore는 카운팅 기능이 있는 락으로, 여러 개의 스레드가 공유 자원을 접근할 수 있도록 제어합니다. 예를 들어 자리가 3개인 리소스 풀에 3개의 스레드만 접근하도록 제한할 수 있습니다.
TCP 서버 프로젝트에서 다중 클라이언트의 동시 접속 처리를 하며 연결 풀을 구현할 때 세마포어로 제한해 과도한 리소스 사용을 방지한 경험이 있습니다.
❓ "Race Condition이란 무엇인가요?"
Race Condition은 여러 스레드가 동시에 공유 자원에 접근하면서, 실행 순서에 따라 결과가 달라지는 상황입니다. 이로 인해 예기치 않은 버그나 데이터 손상이 발생할 수 있습니다.
예를 들어 은행 계좌의 잔액을 입금과 출금하는 두 스레드가 동시에 실행되면, 중간 값이 잘못 계산되어 최종 잔액이 틀어질 수 있습니다.
이를 방지하기 위해 Mutex, Semaphore 같은 동기화 기법을 사용합니다. 실습에서 실제로 캐시 배열을 동시에 쓰는 스레드에서 출력이 꼬이는 문제를 겪고, 뮤텍스를 사용해 해결한 경험이 있습니다.
❓ "System Call이 무엇인가요?"
System Call은 사용자 프로그램이 운영체제의 기능을 요청하는 메커니즘입니다. 사용자 모드에서 직접 하드웨어에 접근할 수 없기 때문에, OS 커널을 통해 작업을 위임합니다.
예를 들어 read(), write(), fork(), exec() 등이 system call입니다. C언어에서 파일을 읽을 때 내부적으로 read 시스템콜이 호출되며, 이는 커널 모드로 전환되어 하드웨어 작업을 수행한 뒤 다시 사용자 모드로 돌아옵니다.
System call은 보안과 안정성을 유지하면서도 사용자 프로그램이 하드웨어를 사용할 수 있도록 하는 중요한 인터페이스입니다.
❓ "가상 메모리란 무엇인가요?"
가상 메모리는 실제 물리 메모리보다 더 큰 주소 공간을 프로세스에 제공하여 효율적인 메모리 관리와 보안성, 프로세스 간 메모리 격리를 가능하게 합니다.
운영체제는 페이지 단위로 가상 메모리를 물리 메모리에 매핑하며, 이를 위해 페이지 테이블과 TLB(Translation Lookaside Buffer)를 사용합니다. 만약 참조한 페이지가 메모리에 없다면, Page Fault가 발생하여 디스크에서 해당 페이지를 불러오게 됩니다.
실제로 페이지 교체 알고리즘(LRU 등)을 학습하며 Page Fault가 얼마나 큰 성능 저하를 유발할 수 있는지 확인했고, 최적화를 위해 메모리 접근 패턴을 고려하는 것이 중요하다는 것을 느꼈습니다.
[malloc lab]
❓ "implicit 방식의 메모리 할당이란 무엇인가요?"
Implicit 방식은 메모리 할당기의 가장 단순한 구현 중 하나로, 명시적인 free list 없이 메모리 블록을 처음부터 끝까지 순차적으로 탐색하면서 적절한 크기의 free 블록을 찾는 방식입니다.
주로 first fit, next fit, best fit 등의 탐색 전략을 사용하며, 각각 탐색 방식에 따라 성능과 외부 단편화에 영향을 줍니다. 블록은 header에 크기 및 할당 상태를 포함하고 있어, 이를 기반으로 연속적으로 블록을 따라가며 조건에 맞는 free 블록을 찾습니다.
제가 malloc lab을 수행하며 처음 구현한 것이 implicit 방식이었습니다. 구조가 단순해 디버깅이 쉬웠지만, 할당이 많아질수록 선형 탐색 시간이 증가해 성능이 떨어졌고, 외부 단편화도 상당하다는 단점을 직접 체감했습니다. 이후 explicit 방식으로 확장하면서 성능 개선을 확인할 수 있었습니다.
❓ "explicit 방식의 메모리 할당이란 무엇인가요?"
Explicit 방식은 명시적인 free list를 유지하면서, 메모리 할당 시 전체 힙을 탐색하지 않고, free list에 있는 블록들만 탐색하여 더 빠르게 메모리를 할당할 수 있는 방식입니다.
free 블록들은 연결 리스트로 구성되며, 보통 prev와 next 포인터를 header나 footer에 포함시켜 관리합니다. 이 방식의 장점은 탐색 범위를 줄일 수 있고, 블록을 빠르게 삽입/삭제할 수 있어 성능이 향상된다는 것입니다.
실제로 malloc lab에서 implicit 방식 이후 explicit으로 구현을 바꾸고 나서 할당 요청이 많을수록 확실히 성능이 개선됨을 확인했습니다. 하지만 포인터 연산을 잘못하면 segmentation fault가 발생할 수 있어 디버깅에 신중을 기해야 했습니다.
❓ "Segregated Free List 방식에 대해 설명해주세요."
Segregated Free List 방식은 블록의 크기 구간별로 free list를 여러 개 관리하는 방식입니다. 예를 들어, 8~15바이트, 16~31바이트, 32~63바이트 같은 식으로 구간을 나누고, 각 구간에 해당하는 free 블록을 별도 리스트에 보관합니다.
이 방식을 사용하면 free list 탐색 시 필요한 크기 범위에만 집중할 수 있어 탐색 시간이 대폭 줄고, 할당/반환 효율이 높아집니다. 다만, free list의 개수가 늘어나 구조가 복잡해지고, 메모리 블록이 잘못 분류되거나 구간 경계에 따라 내부 단편화가 생길 수 있습니다.
제가 seglist를 구현하면서 처음엔 크기별 구간 선택 로직이 헷갈렸지만, 버디 시스템과 비교해가며 정리했고, 실험 결과 일부 workload에서는 성능이 가장 뛰어났습니다.
❓ "Buddy System에 대해 설명해주세요."
Buddy System은 메모리 관리에서 2의 거듭제곱 단위 블록으로 메모리를 관리하며, 빠르게 할당과 병합을 수행할 수 있는 구조입니다. 처음에는 전체 메모리를 하나의 큰 블록으로 시작하고, 요청 크기에 맞게 계속 반으로 나누어(buddy) 블록을 할당합니다.
블록을 해제하면 해당 블록의 buddy 블록과 병합하여 더 큰 블록으로 만들 수 있어 외부 단편화를 줄이고 블록 병합이 빠른 장점이 있습니다. 단점은 2의 제곱 단위에 맞춰 할당하다 보니 내부 단편화가 심해질 수 있다는 점입니다.
제가 커널 수준 메모리 할당 전략을 공부할 때 buddy system을 처음 접했고, 페이지 단위로 관리하는 Linux 커널의 buddy allocator가 매우 효과적인 전략임을 알게 되었습니다. 이후 메모리 관리 성능을 비교하는 시뮬레이터를 만들어 buddy system의 병합 성능을 직접 실험해본 적이 있습니다.
❓ "Internal Fragmentation과 External Fragmentation의 차이는 무엇인가요?"
메모리 단편화는 할당/해제 과정에서 효율적인 공간 사용을 방해하는 현상입니다.
- Internal Fragmentation은 할당된 블록 내부에 사용하지 못하는 낭비 공간이 생기는 경우입니다. 예를 들어, 33바이트를 요청했는데 64바이트 블록이 할당되면, 나머지 31바이트는 사용되지 않지만 할당된 상태입니다. 이는 주로 고정 크기 블록 할당이나 buddy system에서 발생합니다.
- External Fragmentation은 여러 개의 free 블록이 존재하지만, 연속적인 큰 공간이 없어서 할당이 불가능한 상태를 말합니다. 이는 가변 크기 블록을 사용하는 malloc/free와 같은 시스템에서 흔히 발생하며, 메모리 압축(compaction)이 해결책이 될 수 있지만 일반적인 환경에서는 어렵습니다.
malloc lab 실습 중, internal fragmentation은 블록 크기를 적절히 조정해 최소화하고, external fragmentation은 coalescing 구현을 통해 어느 정도 해결했습니다. 이 개념을 통해 효율적인 메모리 할당 전략 설계의 중요성을 느꼈습니다.
[알고리즘 및 자료구조]
❓ "Big-O 표기법에 대해 설명해주세요."
Big-O 표기법은 알고리즘의 성능을 입력 크기(n)에 따라 수학적으로 표현한 것으로, 주로 최악의 시간복잡도를 기준으로 사용됩니다. 시간뿐 아니라 공간복잡도도 O 표기로 표현할 수 있습니다.
예를 들어, 배열 순회는 O(n), 이중 반복문은 O(n²), 이진 탐색은 O(log n), 병합 정렬은 O(n log n) 등으로 표현됩니다. 이를 통해 입력이 커졌을 때 알고리즘이 어떻게 확장되는지 예측할 수 있습니다.
제가 정렬 알고리즘을 비교 실험한 적이 있었는데, 같은 데이터라도 퀵 정렬과 선택 정렬의 수행 시간이 확연히 달라졌고, 이때 Big-O 개념이 실제로 성능에 어떤 영향을 주는지 체감했습니다.
❓ "재귀와 반복의 차이를 설명해주세요."
재귀는 함수가 자기 자신을 호출하는 방식으로, 문제를 작은 부분 문제로 나눠서 푸는 분할 정복 방식에 적합합니다. 반면 반복은 loop 문을 사용하여 조건이 충족될 때까지 코드를 반복 실행합니다.
예를 들어 피보나치 수열을 재귀로 구현하면 코드가 간결하지만, 동일한 계산을 반복해 비효율적입니다. 반복문으로 구현하면 중복 계산이 없으므로 더 효율적입니다. 따라서 시간/공간 효율성을 고려해 상황에 맞게 선택하는 것이 중요합니다.
제가 DFS를 재귀와 스택으로 각각 구현해본 적이 있는데, 재귀는 코드가 깔끔했지만 스택 오버플로우 위험이 있었고, 반복은 더 안전하고 명시적인 방식이었습니다.
❓ "Linked List와 Array의 차이를 설명해주세요."
- **배열(Array)**은 연속된 메모리 공간에 데이터를 저장하고, 인덱스를 통한 빠른 접근(O(1))이 가능하지만, 크기 변경이 어렵고 삽입/삭제는 느립니다(O(n)).
- **연결 리스트(Linked List)**는 포인터로 노드를 연결하므로 메모리 재배치가 유연하고 삽입/삭제가 빠르지만, 인덱스 접근은 O(n)입니다.
예를 들어, 키오스크 관리자 웹 프로젝트에서 서버에 저장된 대기열을 처리할 때는 삽입/삭제가 잦아 링크드 리스트 구조를 활용한 큐를 설계했습니다. 반면, 고정된 크기의 상태 테이블을 조회할 때는 배열을 사용했습니다.
❓ "Stack과 Queue에 대해 설명해주세요."
- Stack은 LIFO(Last In First Out) 구조로, 최근에 넣은 데이터를 먼저 꺼냅니다. 함수 호출, undo 기능, 괄호 검사 등에 활용됩니다.
- Queue는 FIFO(First In First Out) 구조로, 먼저 들어온 데이터가 먼저 나갑니다. 대기열, 작업 스케줄링 등에 사용됩니다.
TCP 서버 프로젝트에서 연결 요청을 처리하기 위한 대기 큐를 설계하면서 큐의 특성을 활용했고, 웹 애플리케이션의 경로 추적에서는 스택을 사용하여 네비게이션 구조를 관리한 경험이 있습니다.
❓ "Deep Copy와 Shallow Copy의 차이를 설명해주세요."
- Shallow Copy는 객체의 참조만 복사하기 때문에, 원본 객체의 내부 변경이 복사본에도 영향을 미칩니다.
- Deep Copy는 참조된 객체까지 모두 재귀적으로 복사하여, 원본과 복사본이 완전히 독립된 구조가 됩니다.
Python에서는 copy.copy()가 shallow copy, copy.deepcopy()가 deep copy입니다.
웹 프론트엔드 프로젝트에서 객체 상태를 저장하고 되돌릴 때 deep copy를 사용하지 않아 버그가 발생했던 경험이 있습니다. 이 경험을 통해 상태 관리에서는 참조 구조에 특히 신경 써야 한다는 점을 배웠습니다.
❓ "정렬 알고리즘(QuickSort, MergeSort, HeapSort)에 대해 설명해주세요."
- QuickSort: 피벗을 중심으로 분할 정복. 평균 O(n log n), 최악 O(n²). 실무에서 가장 많이 쓰이지만 pivot 선택이 중요합니다.
- MergeSort: 항상 O(n log n)의 안정 정렬. 메모리를 추가로 사용하지만, 정렬 결과의 안정성이 중요할 때 적합합니다.
- HeapSort: 힙 자료구조 기반, 항상 O(n log n). 메모리 사용이 적지만, 안정 정렬이 아닙니다.
제가 10만 건 이상의 로그 데이터를 정렬할 때, MergeSort를 선택해 안정성과 일관된 성능을 확보한 경험이 있습니다.
❓ "해시 테이블의 충돌 해결 방식에 대해 설명해주세요."
해시 테이블은 키-값 쌍을 저장하고, 해시 함수를 통해 인덱스를 계산해 데이터를 저장합니다. **충돌(Collision)**은 서로 다른 키가 같은 인덱스를 가질 때 발생하며, 이를 해결하는 방법은 다음과 같습니다:
- Chaining: 같은 인덱스에 연결 리스트나 버킷을 두어 여러 데이터를 저장합니다.
- Open Addressing: 비어 있는 다른 인덱스를 찾아 저장합니다. (Linear, Quadratic probing, Double hashing)
- Rehashing: 테이블이 꽉 차면 더 큰 테이블로 옮기고 해시를 다시 계산합니다.
실제로 Python의 dict도 내부적으로 해시 충돌을 관리하는 로직이 있어, 해시 함수의 품질과 충돌 대응이 성능에 큰 영향을 준다는 것을 체감했습니다.
❓ "DFS와 BFS의 차이를 설명해주세요."
- DFS(Depth First Search)는 깊이 우선 탐색으로, 스택 또는 재귀를 사용합니다. 백트래킹, 미로 탐색, 순열 생성 등 깊은 탐색이 필요한 문제에 적합합니다.
- BFS(Breadth First Search)는 너비 우선 탐색으로 큐를 사용하며, 최단 경로 문제에 자주 사용됩니다.
제가 미로 경로 탐색 문제를 구현할 때, DFS는 경로 탐색에 적합했고, BFS는 최단 경로를 구하는 데 효과적이었습니다. 각 알고리즘의 탐색 순서와 시간 복잡도(O(V+E))를 고려해 선택하는 것이 중요합니다.
❓ "그래프와 트리의 차이를 설명해주세요."
- 그래프(Graph)는 정점과 간선으로 이루어진 자료구조로, 사이클이 허용되고 방향/무방향, 가중치 여부에 따라 다양한 유형이 있습니다.
- 트리(Tree)는 그래프의 특수한 형태로, 사이클이 없고 계층 구조를 가지며, 루트 노드부터 하위 노드로 전파됩니다.
트리 구조는 폴더 시스템, 조직도 등에 활용되고, 그래프는 도로망, 네트워크 토폴로지 등 복잡한 관계 표현에 적합합니다. DFS/BFS, 다익스트라 알고리즘, MST 등에서 모두 그래프를 기반으로 하며, 저는 트리 기반의 디렉토리 시각화 도구를 개발한 경험이 있습니다.
❓ "Dynamic Programming과 Greedy 알고리즘의 차이를 설명해주세요."
- Dynamic Programming (DP)은 문제를 작은 부분 문제로 나누고, 그 결과를 저장해 중복 계산을 줄이는 최적화 기법입니다.
예: 피보나치, 배낭 문제, 최단 경로. - Greedy Algorithm은 매 순간 가장 최선의 선택을 하여 전체 최적 해를 구하는 방식입니다.
예: 최소 신장 트리(Kruskal), 활동 선택 문제.
DP는 더 복잡하지만 항상 최적 해를 보장하는 반면, Greedy는 구현이 간단하고 속도가 빠르지만 항상 최적을 보장하지는 않습니다.
저는 배낭 문제를 DP와 Greedy로 각각 풀며 두 방법의 차이와 성능, 해의 정확도 차이를 직접 비교해본 경험이 있습니다.
❓ "균형 이진 트리인 AVL 트리와 Red-Black 트리의 차이는 무엇인가요?"
AVL 트리와 Red-Black 트리는 둘 다 이진 탐색 트리의 높이를 제한하여 O(log n)의 탐색 성능을 유지하기 위한 구조입니다.
- AVL 트리는 모든 노드의 왼쪽과 오른쪽 서브트리 높이 차이가 1 이하가 되도록 유지합니다. 삽입/삭제 후 필요하면 회전(Rotation)을 수행합니다. 균형 유지가 엄격하여 탐색 성능은 더 우수하지만, 삽입/삭제 비용이 상대적으로 큽니다.
- Red-Black 트리는 각 노드에 색상을 부여하고, 균형 조건을 완화하여 삽입/삭제 시 회전 수를 줄입니다. 이는 Linux 커널의 rbtree, Java의 TreeMap 등에 활용될 만큼 실용성이 높습니다.
제가 직접 AVL과 Red-Black 트리를 각각 구현해보며, 데이터 삽입 패턴에 따라 성능 차이가 뚜렷하다는 것을 확인했습니다. 실무에서는 요구 사항에 따라 선택해야 한다고 생각합니다.
[네트워크]
❓ "BSD 소켓이란 무엇인가요?"
BSD 소켓은 Berkeley Software Distribution UNIX에서 처음 도입된 **네트워크 통신을 위한 프로그래밍 인터페이스(API)**입니다. TCP/IP 기반의 네트워크 프로그래밍에서 소켓을 사용해 서버와 클라이언트 간 통신을 구현할 수 있습니다.
주요 함수로는 socket(), bind(), listen(), accept(), connect(), send(), recv() 등이 있으며, 이들을 통해 TCP 또는 UDP 통신을 설정할 수 있습니다.
제가 C 언어로 TCP 서버를 구현한 경험이 있는데, select()를 활용한 멀티 클라이언트 처리 과정에서 소켓 프로그래밍의 중요성과 동기화 처리의 어려움을 체감했습니다. BSD 소켓은 리눅스, 윈도우, 맥OS 등 다양한 OS에서 기본 통신 수단으로 활용됩니다.
❓ "프록시 서버란 무엇인가요?"
프록시 서버는 클라이언트와 서버 사이에서 요청과 응답을 중계하는 서버입니다. 사용자는 실제 서버가 아닌 프록시 서버를 통해 인터넷에 접근하게 됩니다.
주요 기능으로는 다음과 같습니다:
- 캐싱: 자주 요청되는 데이터를 저장하여 응답 속도 향상
- 보안: 내부 네트워크 보호 및 IP 주소 은닉
- 필터링: 특정 사이트 차단 또는 트래픽 제어
예를 들어, 기업 내부 네트워크에서 외부로 나가는 요청은 프록시 서버를 거쳐야 하며, 이를 통해 웹 필터링과 로깅이 가능합니다. 저는 Python으로 간단한 HTTP 프록시 서버를 구현해본 적이 있으며, 헤더 가공과 캐싱 기능을 실습해보았습니다.
❓ "TCP/IP와 UDP의 차이에 대해 설명해주세요."
TCP와 UDP는 모두 전송 계층의 프로토콜이지만, 신뢰성과 성능 측면에서 큰 차이가 있습니다.
- TCP (Transmission Control Protocol): 연결 기반 프로토콜로, 데이터의 순서 보장, 흐름 제어, 혼잡 제어, 오류 검사 기능을 제공합니다. 예: HTTP, HTTPS, FTP, SMTP
- UDP (User Datagram Protocol): 비연결형 프로토콜로, 속도는 빠르지만 신뢰성이 낮아 데이터 손실이 허용되는 환경에 적합합니다. 예: DNS, VoIP, 실시간 스트리밍
실제로 실시간 게임 서버 구현 시, 클라이언트 이동 정보는 UDP로 빠르게 전달하고, 로그인/결제 같은 중요 정보는 TCP로 처리하는 혼합 구조를 많이 사용합니다.
❓ "HTTP란 무엇인가요? 어떤 방식으로 동작하나요?"
HTTP(HyperText Transfer Protocol)는 웹에서 클라이언트와 서버 간 데이터를 주고받는 응용 계층 프로토콜입니다. 기본적으로 비연결성(stateless)과 요청-응답(request-response) 구조를 가집니다.
클라이언트가 HTTP 요청(GET, POST 등)을 보내면, 서버는 이에 대한 응답(HTML, JSON 등)을 보냅니다. 요청은 보통 헤더와 바디로 구성되며, 응답 역시 상태 코드(예: 200 OK, 404 Not Found)와 함께 전달됩니다.
최근에는 HTTP/2, HTTP/3가 등장하면서 다중 연결, 헤더 압축, 전송 속도 개선 등의 기능이 추가되고 있습니다. 웹 애플리케이션 개발에서 REST API를 설계하며 HTTP 요청 구조를 세밀하게 조정했던 경험이 있습니다.
❓ "file descriptor가 무엇인가요?"
File descriptor는 UNIX 및 Linux 기반 OS에서 열린 파일이나 소켓을 식별하는 정수형 핸들입니다. 각 프로세스는 열린 리소스를 file descriptor 테이블에서 관리하며, 보통 0은 stdin, 1은 stdout, 2는 stderr를 의미합니다.
네트워크 소켓도 file descriptor로 관리되기 때문에, select()나 poll() 같은 다중 입출력 함수에서 이들을 기반으로 이벤트를 감지합니다.
제가 다중 클라이언트를 처리하는 TCP 서버를 구현했을 때, 각 소켓을 file descriptor로 추적하며 select 기반 논블로킹 I/O 처리를 했던 경험이 있습니다.
❓ "DNS가 어떻게 동작하는지 설명해주세요."
DNS(Domain Name System)는 도메인 이름을 실제 IP 주소로 변환하는 시스템입니다. 사용자가 www.example.com을 입력하면, DNS는 이를 IP 주소로 바꿔서 서버에 연결할 수 있도록 합니다.
DNS 조회는 계층적으로 작동합니다:
- 먼저 로컬 캐시 확인
- 없으면 재귀 DNS 서버가 루트 → TLD → 권한 DNS 순으로 질의
- 최종 IP를 받아 클라이언트에 응답
이 구조는 확장성과 분산 처리에 매우 적합하며, 실제로 웹 속도에 큰 영향을 미치기 때문에 CDN이나 DNS 최적화가 중요합니다.
❓ "REST API란 무엇인가요?"
REST는 Representational State Transfer의 약자로, 자원의 표현을 HTTP를 통해 전송하는 아키텍처 스타일입니다. RESTful API는 자원(URI)과 이를 조작하는 HTTP 메서드(GET, POST 등)를 활용합니다.
특징은 다음과 같습니다:
- 무상태성(Stateless): 서버는 클라이언트 상태를 저장하지 않음
- 표현 기반: JSON, XML 등 다양한 형태로 응답
- 계층 구조: 중간 서버(프록시, 로드밸런서 등) 존재 가능
예를 들어 /users/123에 GET 요청은 사용자 정보 조회, PUT 요청은 정보 수정이 됩니다. 저는 Flask 기반 RESTful API를 설계하면서 이 원칙에 맞게 CRUD 인터페이스를 구성했던 경험이 있습니다.
❓ "HTTP 메서드(GET, POST, PUT, DELETE)에 대해 설명해주세요."
HTTP 메서드는 서버의 자원에 대해 어떤 동작을 수행할지 정의하는 명령어입니다. 주요 메서드는 다음과 같습니다:
- GET: 자원 조회 (읽기)
- POST: 자원 생성 (쓰기)
- PUT / PATCH: 자원 전체 수정 / 일부 수정
- DELETE: 자원 삭제
이 외에도 OPTIONS, HEAD 등이 있으며, REST API에서는 이들을 자원 중심으로 매핑해 사용합니다. 저는 API를 설계할 때 GET /users, POST /users, PUT /users/1과 같이 직관적인 URI 설계를 중시하며 개발합니다.
❓ "OSI 7계층을 설명해주세요."
OSI 7계층은 네트워크 통신을 7단계로 나누어 각 계층이 명확한 역할을 담당하도록 설계한 표준 모델입니다:
- Physical (물리 계층): 케이블, 전기 신호 등 하드웨어
- Data Link (데이터 링크 계층): MAC 주소, 프레임 전송 (ex. Ethernet)
- Network: IP 주소 지정, 라우팅 (ex. IP, ICMP)
- Transport: 세그먼트 전송, 오류 제어 (ex. TCP, UDP)
- Session: 연결 설정 및 동기화 (많이 생략됨)
- Presentation: 데이터 형식 변환, 인코딩 (ex. 암호화, 압축)
- Application: 사용자와 가장 가까운 계층 (ex. HTTP, FTP, DNS)
실제로 TCP/IP 4계층 모델이 실무에서 더 자주 쓰이지만, OSI 모델은 네트워크 문제를 계층별로 분석할 때 유용하게 사용됩니다.
❓ "CDN이란 무엇이고, 어떻게 동작하나요?"
CDN(Content Delivery Network)은 지리적으로 분산된 서버를 통해 사용자와 가까운 위치에서 콘텐츠를 제공함으로써 응답 속도와 안정성을 향상시키는 기술입니다.
동작 방식은 다음과 같습니다:
- 사용자가 콘텐츠 요청 시, DNS가 가장 가까운 엣지 서버(Edge Server)로 요청을 유도
- 엣지 서버에 캐시된 콘텐츠가 있으면 즉시 응답
- 없으면 원 서버에서 가져와 저장하고 응답
CDN은 정적 콘텐츠(이미지, JS, CSS)뿐만 아니라 동적 콘텐츠 캐싱, TLS 오프로드, DDoS 방어에도 활용됩니다. 저는 프론트엔드 앱을 AWS CloudFront로 배포하며, 사용자 접속 속도가 크게 개선된 것을 확인했습니다.
[면접 기출 대응]
❓ “본인이 직접 구현한 코드 중 가장 복잡했던 부분은 무엇이고, 어떻게 해결했나요?”
네, 제가 TCP/IP 기반 서버-클라이언트 통신 프로젝트를 진행하면서, 구조체를 통한 데이터 전송에서 가변 크기 메시지 처리와 바이트 정렬 문제가 가장 복잡했던 부분이었습니다.
처음에는 구조체를 그대로 전송했지만, 클라이언트와 서버 간 구조체의 padding 차이와 endian 차이 때문에 데이터가 깨졌습니다. 이 문제를 해결하기 위해, 구조체를 전송용 바이트 스트림으로 변환하고, 수신 측에서 다시 복원하는 방식을 적용했습니다. 또한 htonl(), ntohl() 함수를 통해 네트워크 바이트 오더를 통일했습니다.
이 경험을 통해 네트워크 통신에서의 데이터 일관성 유지가 얼마나 중요한지를 체감했고, 문제 상황을 분석하고 체계적으로 해결하는 능력을 기를 수 있었습니다.
❓ “협업 중 발생한 갈등이나 어려움을 어떻게 해결했나요?”
웹 키오스크 관리자 페이지를 팀 프로젝트로 개발하던 중, 프론트엔드와 백엔드 간의 API 규격 불일치 문제로 기능 연동이 실패하는 상황이 있었습니다. 초기에는 서로의 구현을 바꾸기 어려워 갈등이 있었지만, 저는 먼저 Swagger 문서화를 제안하고, API 명세를 명확히 시각화하여 서로의 입장을 공유했습니다.
결국 백엔드는 JSON 형식을 표준화하고, 프론트엔드는 Axios 인터셉터를 이용해 예외 처리를 보완하는 방향으로 합의하여 문제를 원만히 해결했습니다.
이 경험을 통해 기술적 대화를 기반으로 한 협업과 조율 능력을 배웠고, 어떤 상황에서도 소통이 우선이라는 교훈을 얻었습니다.
❓ “멀티스레딩에서 발생할 수 있는 문제를 설명해주세요.”
멀티스레딩 환경에서는 Race Condition, Deadlock, Thread Starvation 등의 문제가 발생할 수 있습니다.
예를 들어, 여러 스레드가 동시에 공유 자원에 접근하면서 순서에 따라 결과가 달라지는 Race Condition은 대표적인 문제입니다. 이를 해결하기 위해 Mutex나 Semaphore 같은 동기화 도구를 사용하고, 임계 구역(Critical Section)을 최소화하는 방식으로 병렬성을 유지하면서 안정성을 확보해야 합니다.
웹 프록시 서버 구현 프로젝트에서 각 클라이언트와 연결된 여러 스레드들이 캐시 array에 동시에 접근해 deadlock이 걸리는 문제가 있었습니다. 이 때 pthread_rwlock을 통해 동기화를 적용해서, 동시에 여러 스레드들이 캐시를 작성하지 못하게 설정해 문제를 해결했습니다.
❓ “메모리 누수란 무엇이고, 어떻게 디버깅해봤나요?”
메모리 누수는 프로그램이 더 이상 사용하지 않는 메모리를 해제하지 않아, 사용 가능한 메모리가 점점 줄어드는 현상입니다. 이는 프로그램의 안정성과 성능에 큰 영향을 줍니다.
제가 malloc lab을 진행하면서, explicit free list를 잘못 구현해 coalescing(병합) 로직 누락으로 인해 일부 free 블록이 리스트에서 빠져나가지 않고 메모리에 남는 현상이 발생한 적이 있습니다.
이를 valgrind 도구로 분석한 결과, 누수가 발생한 블록의 주소와 함수 호출 스택을 추적할 수 있었고, 병합 시 포인터가 잘못 설정된 문제를 확인해 수정했습니다.
❓ “알고리즘 문제 해결 시 어떤 전략을 사용하시나요?”
저는 문제를 풀기 전, 먼저 문제 유형을 분류하고 그에 적합한 전략을 선택합니다.
- 중복된 계산이 예상되면 DP(Memoization)
- 전체 탐색이 필요한 문제는 BFS/DFS 또는 백트래킹
- 최적 선택이 중요하면 Greedy
예를 들어, 'N개의 물건을 가방에 최대한 담는 문제'에서는 처음엔 Greedy로 접근했지만, 최적해가 보장되지 않아 DP 방식으로 전환했습니다. dp[i][w] = max(dp[i-1][w], dp[i-1][w-weight]+value) 방식으로 구현했고, 이 경험을 통해 최적화 문제에 대한 직관과 수학적 사고를 훈련할 수 있었습니다.
'Krafton Jungle > 2. Keywords' 카테고리의 다른 글
| [WEEK11] USERPROG 관련 키워드 정리 (0) | 2025.05.27 |
|---|---|
| [WEEK08] 네트워크 계층 (0) | 2025.05.01 |
| [WEEK07] DMA와 이더넷 (0) | 2025.04.29 |
| [WEEK07] 가비지 컬렉션과 메모리 관련 오류들 (0) | 2025.04.28 |
| [WEEK07] System Call (0) | 2025.04.28 |