C 언어 주니어 기술면접
새 면접Q. C 언어에서 malloc()과 free()의 역할을 설명하고, 메모리 누수(memory leak)가 발생하는 상황을 예를 들어 설명해주세요.
동적 메모리 할당의 생명주기와 프로그래머의 책임에 대해 생각해보세요.
malloc()은 힙 영역에 동적으로 메모리를 할당하는 함수로, 요청한 바이트 크기만큼의 메모리 주소를 반환합니다. free()는 malloc()으로 할당받은 메모리를 해제하여 시스템에 반환하는 함수입니다. C 언어는 가비지 컬렉션이 없기 때문에 프로그래머가 직접 메모리를 관리해야 합니다. 메모리 누수는 malloc()으로 할당한 메모리를 free()로 해제하지 않고 프로그램이 종료되거나 해당 포인터를 잃어버릴 때 발생합니다. 예를 들어 반복문 안에서 계속 malloc()을 호출하면서 free()를 하지 않으면 메모리가 계속 쌓여 시스템 자원이 고갈될 수 있습니다.
- • malloc()은 힙 메모리 동적 할당
- • free()는 할당된 메모리 해제
- • 메모리 누수는 할당 후 미해제 시 발생
서버 프로그램에서 클라이언트 요청마다 메모리를 할당하고 해제하지 않으면 장시간 운영 시 메모리 부족으로 서버가 다운될 수 있습니다.
calloc()과 malloc()의 차이점은 무엇이며, 언제 calloc()을 사용하는 것이 더 적절한가요?
Q. 포인터와 배열의 관계를 설명하고, 포인터 연산에서 증가 연산자(++)를 사용할 때 실제로 메모리 주소가 어떻게 변하는지 설명해주세요.
배열 이름이 포인터처럼 동작하는 이유와 포인터의 자료형이 중요한 이유를 생각해보세요.
C 언어에서 배열 이름은 배열의 첫 번째 요소를 가리키는 포인터 상수로 취급됩니다. 따라서 arr[i]는 *(arr + i)와 동일한 표현입니다. 포인터 연산에서 증가 연산자를 사용하면 포인터가 가리키는 자료형의 크기만큼 주소가 증가합니다. 예를 들어 int 포인터(4바이트)를 증가시키면 주소값이 4 증가하고, double 포인터(8바이트)를 증가시키면 8 증가합니다. 이는 포인터가 자료형 정보를 가지고 있기 때문에 가능하며, 배열을 순회할 때 효율적으로 다음 요소에 접근할 수 있게 해줍니다. 이러한 특성 덕분에 포인터를 이용한 배열 순회가 인덱스를 사용하는 것만큼 직관적이면서도 효율적입니다.
- • 배열 이름은 첫 번째 요소를 가리키는 포인터 상수
- • 포인터 증가는 자료형 크기만큼 주소 증가
- • 포인터 연산으로 배열 순회 가능
문자열 배열을 처리하거나 2차원 배열을 동적으로 할당할 때 포인터 연산을 통해 메모리를 효율적으로 관리할 수 있습니다.
이중 포인터(double pointer)는 어떤 상황에서 사용되며, 실무에서 어떤 예시가 있을까요?
Q. 구조체의 메모리 패딩(padding)과 정렬(alignment)이 무엇인지 설명하고, 왜 구조체의 실제 크기가 멤버 변수들의 크기 합보다 클 수 있는지 설명해주세요.
CPU가 메모리에 접근하는 효율성과 구조체 멤버의 배치 순서를 고려해보세요.
메모리 정렬은 CPU가 메모리에 효율적으로 접근하기 위해 특정 자료형을 특정 주소 경계에 배치하는 것을 의미합니다. 대부분의 시스템에서 4바이트 변수는 4의 배수 주소에, 8바이트 변수는 8의 배수 주소에 배치됩니다. 메모리 패딩은 이러한 정렬 규칙을 만족시키기 위해 구조체 멤버 사이나 끝에 빈 공간을 추가하는 것입니다. 예를 들어 char(1바이트)와 int(4바이트)를 순서대로 가진 구조체는 char 뒤에 3바이트의 패딩이 추가되어 총 8바이트가 됩니다. 이는 성능 최적화를 위한 것으로, 정렬되지 않은 메모리 접근은 추가적인 CPU 사이클이 필요하거나 일부 시스템에서는 오류를 발생시킬 수 있습니다. 구조체 멤버를 크기 순으로 정렬하면 패딩을 최소화할 수 있습니다.
- • 메모리 정렬은 CPU 접근 효율성을 위한 것
- • 패딩은 정렬 규칙을 만족시키기 위한 빈 공간
- • 구조체 크기는 멤버 배치 순서에 영향받음
임베디드 시스템에서 메모리가 제한적일 때 구조체 멤버 순서를 최적화하여 메모리 사용량을 줄이는 것이 중요합니다.
packed 구조체 속성을 사용하면 패딩을 제거할 수 있는데, 이것의 장단점은 무엇인가요?
Q. C 언어의 전처리기 지시문인 #define과 const의 차이점을 설명하고, 각각을 언제 사용하는 것이 적절한지 설명해주세요.
컴파일 과정에서 처리되는 시점과 타입 안정성에 대해 생각해보세요.
#define은 전처리 단계에서 단순 텍스트 치환을 수행하는 매크로이며, 타입 검사가 없고 디버깅이 어렵습니다. 반면 const는 컴파일 시점에 처리되는 상수 변수로 타입 정보를 가지며 디버거에서 확인할 수 있습니다. #define은 조건부 컴파일, 함수형 매크로, 헤더 가드 등에 사용되며, const는 타입 안정성이 필요한 상수 선언에 적합합니다. const는 메모리 공간을 차지하지만 포인터로 참조할 수 있고, #define은 메모리를 차지하지 않지만 코드 크기를 증가시킬 수 있습니다. 현대적인 C 프로그래밍에서는 일반 상수에는 const를 사용하고, 조건부 컴파일이나 특수한 매크로가 필요할 때만 #define을 사용하는 것이 권장됩니다.
- • #define은 전처리 단계 텍스트 치환
- • const는 타입 안정성을 가진 상수 변수
- • 용도에 따라 적절히 선택 필요
멀티 플랫폼 프로젝트에서 #define을 이용한 조건부 컴파일로 운영체제별로 다른 코드를 선택적으로 컴파일할 수 있습니다.
함수형 매크로와 인라인 함수의 차이점과 각각의 장단점은 무엇인가요?
Q. C 표준 라이브러리의 문자열 처리 함수인 strcpy()와 strncpy()의 차이점을 설명하고, 버퍼 오버플로우를 방지하기 위해 어떤 함수를 사용해야 하는지 설명해주세요.
복사할 문자열의 길이와 목적지 버퍼의 크기 관계를 고려해보세요.
strcpy()는 소스 문자열을 null 문자를 만날 때까지 목적지 버퍼에 복사하는 함수로, 목적지 버퍼의 크기를 검사하지 않아 버퍼 오버플로우 위험이 있습니다. strncpy()는 최대 복사할 바이트 수를 지정할 수 있어 상대적으로 안전하지만, 소스가 지정된 크기보다 길 경우 null 문자가 자동으로 추가되지 않는다는 함정이 있습니다. 버퍼 오버플로우를 방지하기 위해서는 strncpy()를 사용하되 반드시 마지막 바이트를 null 문자로 명시적으로 설정해야 합니다. 더 안전한 대안으로는 C11 표준의 strcpy_s()나 BSD 계열의 strlcpy()를 사용할 수 있으며, 이들은 자동으로 null 종료를 보장합니다. 실무에서는 복사 전 버퍼 크기를 항상 검증하고, 가능하면 안전한 함수를 사용하는 것이 중요합니다.
- • strcpy()는 버퍼 크기 검사 없음
- • strncpy()는 크기 제한하지만 null 종료 보장 안함
- • 안전한 대안 함수 사용 권장
웹 서버나 네트워크 프로그램에서 사용자 입력을 처리할 때 안전하지 않은 문자열 함수 사용은 심각한 보안 취약점으로 이어질 수 있습니다.
sprintf()와 snprintf()의 차이점과 포맷 문자열 취약점에 대해 설명해주세요.
아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!