소개
C 인터뷰 질문 및 답변에 대한 포괄적인 가이드에 오신 것을 환영합니다! 첫 C 프로그래밍 직무를 준비하는 신입 졸업생이든, 기술을 연마하려는 숙련된 개발자이든, 또는 강력한 질문 세트를 찾는 면접관이든, 이 문서는 여러분의 귀중한 자료가 될 것입니다. 기본 구문 및 메모리 관리부터 동시성, 임베디드 시스템, 빌드 도구 체인과 같은 고급 개념에 이르기까지 광범위한 주제를 다룹니다. C 에 대한 이해를 심화하고 앞으로 닥칠 기술적 도전을 자신 있게 해결할 준비를 하십시오.

C Fundamentals and Syntax
What is the difference between int a; and int *a; in C?
Answer:
int a; declares an integer variable a. int *a; declares a pointer variable a that can store the memory address of an integer. The asterisk signifies that a is a pointer.
Explain the purpose of the main() function in a C program.
Answer:
The main() function is the entry point of every C program. Execution begins from this function. It typically returns an integer value (0 for success, non-zero for error) to the operating system.
What are the basic data types available in C?
Answer:
The basic data types in C include int (integer), char (character), float (single-precision floating-point), and double (double-precision floating-point). These can be modified with short, long, signed, and unsigned.
Differentiate between const int *p; and int *const p;.
Answer:
const int *p; declares a pointer p to a constant integer; the value pointed to cannot be changed, but p itself can point to a different location. int *const p; declares a constant pointer p to an integer; p cannot be reassigned to point to a different location, but the value it points to can be modified.
What is the role of the preprocessor in C?
Answer:
The C preprocessor is the first phase of compilation. It handles directives like #include (for including header files), #define (for macro definitions), and conditional compilation (#ifdef, #ifndef). It modifies the source code before actual compilation.
Explain the difference between ++i and i++.
Answer:
++i is the pre-increment operator, which increments the value of i first and then uses the new value in the expression. i++ is the post-increment operator, which uses the current value of i in the expression first and then increments i.
What is a header file in C and why are they used?
Answer:
A header file (.h extension) contains function declarations, macro definitions, and type definitions. They are used to declare interfaces to functions and variables that are defined in other source files, promoting modularity and reusability by allowing multiple source files to share common declarations.
How do you declare and initialize an array in C?
Answer:
An array is declared by specifying its type, name, and size, e.g., int arr[5];. It can be initialized during declaration: int arr[5] = {1, 2, 3, 4, 5}; or int arr[] = {1, 2, 3}; where the size is inferred.
What is the purpose of the sizeof operator?
Answer:
The sizeof operator returns the size, in bytes, of a variable or a data type. It is a compile-time operator and is useful for memory allocation, array indexing, and understanding data structure sizes.
Briefly explain type casting in C.
Answer:
Type casting is the explicit conversion of a variable from one data type to another. It is performed by placing the target type in parentheses before the variable or expression, e.g., (float)myInt. It can be used for arithmetic operations or function arguments.
포인터, 메모리 관리 및 데이터 구조
NULL과 void*의 차이점은 무엇인가요?
답변:
NULL은 정수 상수 표현식으로 정의된 매크로이며, 종종 유효하지 않거나 초기화되지 않은 포인터를 나타내는 데 사용됩니다. void*는 모든 데이터 유형을 가리킬 수 있는 일반 포인터 유형이지만, 유형 캐스팅 없이는 직접 역참조할 수 없습니다. NULL은 널 포인터 값을 나타내고, void*는 알 수 없는 유형의 포인터를 나타냅니다.
댕글링 포인터 (dangling pointer) 란 무엇이며 어떻게 피할 수 있나요?
답변:
댕글링 포인터는 해제되었거나 비워진 메모리 위치를 가리킵니다. 메모리가 프로그램의 다른 부분에서 나중에 사용될 경우 정의되지 않은 동작을 유발할 수 있습니다. 메모리를 해제한 직후 해당 메모리를 가리키는 포인터를 NULL로 설정하고 메모리가 여러 번 해제되지 않도록 함으로써 피할 수 있습니다.
malloc()과 calloc()의 차이점을 설명해주세요.
답변:
malloc()은 지정된 크기의 메모리 블록을 할당하고 블록 시작 부분에 대한 포인터를 반환합니다. 할당된 메모리에는 쓰레기 값이 포함됩니다. calloc()은 요소 배열에 대한 메모리 블록을 할당하고 모든 바이트를 0 으로 초기화한 다음 할당된 메모리에 대한 포인터를 반환합니다. calloc()은 또한 두 개의 인수를 받습니다: 요소 수와 각 요소의 크기.
realloc()은 언제 사용하나요?
답변:
realloc()은 이미 할당된 메모리 블록의 크기를 변경하는 데 사용됩니다. 블록을 확장하거나 축소할 수 있습니다. 원래 블록을 제자리에서 크기 조정할 수 없는 경우 realloc()은 새 블록을 할당하고 이전 블록의 내용을 새 블록으로 복사한 다음 이전 블록을 해제합니다. 동적 배열이나 크기를 늘리거나 줄여야 하는 버퍼에 유용합니다.
메모리 누수 (memory leak) 개념을 설명해주세요.
답변:
메모리 누수는 프로그램이 동적으로 메모리를 할당했지만 더 이상 필요하지 않을 때 해제하지 못하는 경우 발생합니다. 이는 사용 가능한 메모리를 점진적으로 감소시켜 프로그램이나 시스템이 느려지거나 충돌할 수 있습니다. 일반적인 원인으로는 free() 호출을 잊거나 할당된 메모리에 대한 포인터를 잃는 것이 있습니다.
이중 포인터 (포인터의 포인터) 란 무엇이며 언제 유용한가요?
답변:
이중 포인터는 다른 포인터의 주소를 저장하는 포인터입니다. 두 개의 별표 (예: int **ptr;) 를 사용하여 선언됩니다. 함수에 인수로 전달된 포인터의 값을 수정해야 할 때 유용합니다. 예를 들어 함수 내에서 메모리를 할당하고 매개변수를 통해 해당 주소를 반환하거나 포인터 배열을 다룰 때 유용합니다.
C 에서 간단한 단일 연결 리스트 (singly linked list) 를 어떻게 구현하나요?
답변:
단일 연결 리스트는 데이터와 다음 노드에 대한 포인터를 포함하는 노드에 대한 struct를 사용하여 구현됩니다. 리스트 자체는 헤드 노드에 대한 포인터로 관리됩니다. 삽입은 새 노드를 연결하기 위해 포인터를 업데이트하는 것을 포함하고, 삭제는 제거할 노드를 찾고 이전 노드의 포인터를 업데이트하여 건너뛰는 것을 포함합니다. 순회는 헤드에서 시작하여 NULL 포인터가 나타날 때까지 반복하여 수행됩니다.
포인터와 함께 const를 사용하는 목적은 무엇인가요?
답변:
포인터와 함께 const를 사용하면 두 가지를 지정할 수 있습니다: 상수 값에 대한 포인터 (const int *p) 또는 값에 대한 상수 포인터 (int *const p). 상수 값에 대한 포인터는 포인터를 통해 가리키는 데이터를 변경할 수 없지만 포인터 자체는 다른 위치를 가리키도록 재할당할 수 있음을 의미합니다. 상수 포인터는 포인터 자체를 다른 위치를 가리키도록 재할당할 수 없지만, 가리키는 데이터는 수정할 수 있음을 의미합니다 (데이터도 const인 경우는 제외).
스택 (stack) 과 힙 (heap) 메모리 할당의 차이점을 설명해주세요.
답변:
스택 메모리는 지역 변수 및 함수 호출에 사용되며 컴파일러에 의해 자동으로 관리됩니다 (LIFO). 할당/해제가 빠르지만 크기가 제한적이고 범위가 함수로 제한됩니다. 힙 메모리는 동적 메모리 할당 (malloc, calloc, realloc) 에 사용되며 프로그래머가 수동으로 관리합니다. 크기와 수명에 더 많은 유연성을 제공하지만, 올바르게 관리하지 않으면 느리고 메모리 누수가 발생하기 쉽습니다.
포인터 산술 연산 (pointer arithmetic) 을 예시와 함께 설명해주세요.
답변:
포인터 산술 연산은 포인터에 산술 연산을 수행하는 것을 포함합니다. 정수가 포인터에 더해지거나 빼지면 포인터의 값은 해당 데이터 유형의 크기에 해당 정수를 곱한 만큼 증가하거나 감소합니다. 예를 들어, int *p;이고 p가 주소 1000을 가리킨다면, p + 1은 1004를 가리킵니다 ( sizeof(int)가 4 바이트라고 가정).
C 에서 배열과 포인터의 차이점은 무엇인가요?
답변:
배열은 연속된 메모리 위치에 저장된 동일한 데이터 유형의 요소 모음이며, 컴파일 시 크기가 고정됩니다 (정적 배열의 경우). 배열 이름은 종종 표현식에서 첫 번째 요소에 대한 포인터로 축소됩니다. 포인터는 메모리 주소를 저장하는 변수입니다. 배열은 포인터 산술 연산을 사용하여 액세스할 수 있지만, 포인터는 동적 메모리 할당 및 메모리 주소 조작에 더 많은 유연성을 제공합니다.
고급 C 개념 및 시스템 프로그래밍
malloc과 calloc의 차이점을 설명해주세요.
답변:
malloc은 지정된 크기의 메모리 블록을 할당하고 첫 번째 바이트에 대한 void 포인터를 반환합니다. 할당된 메모리는 초기화되지 않습니다 (쓰레기 값이 포함됨). calloc은 요소 배열에 대한 메모리 블록을 할당하고 모든 바이트를 0 으로 초기화한 다음 할당된 메모리에 대한 void 포인터를 반환합니다.
C 에서 void 포인터란 무엇인가요? 언제 유용한가요?
답변:
void 포인터는 연관된 데이터 유형이 없는 포인터입니다. 모든 데이터 유형을 가리킬 수 있으며 다른 모든 데이터 포인터 유형으로 유형 캐스팅될 수 있습니다. 메모리 관리 함수 (malloc, free) 또는 다른 데이터 유형에서 작동하는 함수를 작성할 때와 같이 일반 프로그래밍에 유용합니다.
'엔디안성 (endianness)' 개념과 시스템 프로그래밍에서의 중요성을 설명해주세요.
답변:
엔디안성은 메모리에 저장되는 다중 바이트 데이터 (정수 등) 의 바이트 순서를 나타냅니다. 빅 엔디안은 가장 중요한 바이트를 먼저 저장하고, 리틀 엔디안은 가장 중요하지 않은 바이트를 먼저 저장합니다. 다른 시스템 간에 데이터를 올바르게 해석하려면 네트워크 통신 및 파일 I/O 에 중요합니다.
'세그멘테이션 오류 (segmentation fault)'란 무엇이며 어떻게 예방할 수 있나요?
답변:
세그멘테이션 오류는 프로그램이 액세스 권한이 없는 메모리 위치에 액세스하려고 시도하거나 허용되지 않은 방식으로 메모리에 액세스하려고 할 때 (예: 읽기 전용 메모리에 쓰기) 발생합니다. 신중한 포인터 처리, 널 포인터 확인, 범위를 벗어난 배열 액세스 방지 및 올바른 메모리 할당/해제를 통해 예방할 수 있습니다.
C 에서 volatile 키워드의 목적은 무엇인가요?
답변:
volatile 키워드는 변수의 값이 프로그램의 제어 외부 (예: 하드웨어, 다른 스레드) 에 의해 변경될 수 있음을 컴파일러에 알립니다. 이렇게 하면 컴파일러가 해당 변수에 대한 메모리 액세스를 최적화하여 제거하는 것을 방지하고 프로그램이 항상 메모리에서 최신 값을 읽도록 보장합니다.
정적 라이브러리 (static libraries) 와 동적 라이브러리 (dynamic libraries) 란 무엇인가요? 장단점은 무엇인가요?
답변:
정적 라이브러리는 컴파일 시 연결되어 라이브러리 코드를 실행 파일에 직접 포함하므로 실행 파일이 자체 포함되지만 더 커집니다. 동적 라이브러리는 런타임에 연결되어 실행 파일 크기를 줄이고 여러 프로그램이 라이브러리 사본 하나를 공유할 수 있도록 하지만 런타임에 라이브러리가 존재해야 합니다.
C 에서 시스템 호출 (system calls) 의 오류를 어떻게 처리하나요?
답변:
시스템 호출은 일반적으로 실패 시 -1 을 반환하고 특정 오류를 나타내기 위해 전역 errno 변수를 설정합니다. 반환 값을 확인한 다음 perror() 또는 strerror()를 사용하여 errno에 해당하는 사람이 읽을 수 있는 오류 메시지를 출력할 수 있습니다.
프로세스 (process) 와 스레드 (thread) 의 차이점은 무엇인가요?
답변:
프로세스는 자체 메모리 공간, 리소스 및 컨텍스트를 가진 독립적인 실행 환경입니다. 스레드는 프로세스 내의 경량 실행 단위로, 해당 프로세스의 다른 스레드와 동일한 메모리 공간 및 리소스를 공유합니다. 프로세스는 격리를 제공하고, 스레드는 단일 프로세스 내에서 동시성을 제공합니다.
함수의 '재진입성 (reentrancy)' 개념을 설명해주세요.
답변:
재진입 함수는 데이터 손상이나 예기치 않은 동작을 유발하지 않고 여러 스레드 또는 프로세스에서 안전하게 동시 호출될 수 있는 함수입니다. 일반적으로 함수는 잠금으로 보호되지 않은 전역 변수, 정적 변수 또는 기타 공유 리소스를 사용하지 않으며 자체 코드를 수정하지 않습니다.
mmap() 시스템 호출의 목적은 무엇인가요?
답변:
mmap()은 파일이나 장치를 메모리에 매핑합니다. 이를 통해 프로그램은 파일을 자체 주소 공간의 일부인 것처럼 취급할 수 있으며, 파일 I/O 에 대한 직접 메모리 액세스를 가능하게 하여 대용량 파일이나 임의 액세스 패턴의 경우 기존 read()/write() 호출보다 더 효율적일 수 있습니다. 공유 메모리에도 사용됩니다.
시나리오 기반 문제 해결
연결 리스트 (linked list) 가 주어졌을 때, 사이클 (cycle) 이 포함되어 있는지 어떻게 감지할 수 있나요?
답변:
Floyd 의 사이클 감지 알고리즘 (토끼와 거북이) 을 사용합니다. 한 번에 한 칸씩 이동하는 포인터 (느린 포인터) 와 한 번에 두 칸씩 이동하는 포인터 (빠른 포인터) 를 사용합니다. 두 포인터가 만나면 사이클이 존재하는 것입니다. 빠른 포인터가 NULL 에 도달하면 사이클이 없는 것입니다.
C 에서 union을 사용하는 시나리오를 설명해주세요. 장점과 단점은 무엇인가요?
답변:
union은 동일한 메모리 위치에 다른 데이터 유형을 다른 시간에 저장해야 할 때 메모리를 절약하는 데 유용합니다. 예를 들어, 일반적인 '값'에 대해 int 또는 float를 저장하는 경우입니다. 장점은 메모리 효율성이고, 단점은 특정 시점에 하나의 멤버만 값을 가질 수 있으며 잘못된 멤버에 액세스하면 정의되지 않은 동작이 발생한다는 것입니다.
C 에서 동적 배열 (Java 의 ArrayList와 같은) 을 구현해야 합니다. 메모리 관리를 고려하여 어떻게 접근하시겠습니까?
답변:
고정 크기 배열로 시작합니다. 배열이 꽉 차면 더 큰 새 배열 (예: 크기의 두 배) 을 할당하고, 이전 배열의 모든 요소를 새 배열로 복사한 다음, 이전 배열을 해제합니다. 메모리 관리를 위해 malloc, realloc, free를 사용합니다. 현재 크기와 용량을 추적합니다.
함수가 문자열에 대한 포인터를 받습니다. 함수가 원본 문자열을 수정하지 않도록 어떻게 보장할 수 있으며, 이것이 왜 중요한가요?
답변:
매개변수를 const char *str로 선언합니다. 이렇게 하면 포인터가 상수 문자에 대한 포인터가 되어 가리키는 문자열 데이터를 수정할 수 없습니다. 이는 데이터 무결성, 의도하지 않은 부작용 방지 및 호출자에게 함수의 의도를 명확하게 전달하는 데 중요합니다.
프로그램을 작성 중인데 작은 메모리 블록을 자주 할당하고 해제합니다. 어떤 잠재적인 문제가 발생할 수 있으며, 어떻게 완화할 수 있나요?
답변:
잦은 malloc/free는 메모리 단편화 (fragmentation) 를 유발하여 사용 가능한 연속 메모리를 줄이고 성능을 저하시킬 수 있습니다. 또한 메모리 누수 또는 이중 해제 (double-free) 의 위험을 증가시킬 수 있습니다. 완화 전략에는 사용자 정의 메모리 풀/할당자, 객체 풀링 또는 적절한 경우 realloc을 사용하여 시스템 할당자에 대한 호출을 최소화하는 것이 포함됩니다.
임시 변수를 사용하지 않고 두 개의 정수를 교환하는 방법은 무엇인가요?
답변:
비트 XOR 사용: a = a ^ b; b = a ^ b; a = a ^ b;. 또는 산술 연산 사용: a = a + b; b = a - b; a = a - b;. XOR 방법은 큰 숫자에 대한 잠재적인 오버플로 문제를 피하므로 일반적으로 더 안전합니다.
큰 파일이 있고 특정 문자의 발생 횟수를 세어야 합니다. C 에서 이를 효율적으로 수행하는 방법은 무엇인가요?
답변:
파일을 이진 모드 ('rb') 로 엽니다. fread를 사용하여 파일을 청크 (예: 4KB 또는 8KB) 로 버퍼에 읽어들입니다. 버퍼를 반복하여 문자를 세고, feof에 도달할 때까지 반복합니다. 이는 문자를 하나씩 읽는 것에 비해 디스크 I/O 작업을 최소화합니다.
C 에서 '댕글링 포인터 (dangling pointer)'와 '메모리 누수 (memory leak)' 개념을 설명하고 이를 피하는 방법을 설명해주세요.
답변:
댕글링 포인터는 해제된 메모리를 가리키며, 역참조 시 정의되지 않은 동작을 유발합니다. 메모리 누수는 동적으로 할당된 메모리가 더 이상 도달할 수 없지만 해제되지 않아 리소스 고갈을 유발하는 경우입니다. free 후 포인터를 NULL로 설정하여 댕글링 포인터를 피하십시오. 더 이상 필요하지 않을 때마다 해당 malloc에 대한 free가 있는지 확인하여 메모리 누수를 피하십시오.
C 에서 간단한 스택 (stack) 데이터 구조를 구현해야 합니다. 핵심 연산과 기본 스토리지를 어떻게 관리할지 설명해주세요.
답변:
스택은 push(맨 위에 요소 추가) 및 pop(맨 위에서 요소 제거) 을 지원합니다. 배열 또는 연결 리스트를 사용하여 구현할 수 있습니다. 배열의 경우 top 인덱스를 유지하고, 연결 리스트의 경우 push는 헤드에 추가하고 pop은 헤드에서 제거합니다. 배열 기반 스택의 오버플로를 처리하려면 동적 크기 조정 (동적 배열과 유사) 이 필요합니다.
다른 함수에 함수를 인수로 전달해야 하는 시나리오를 고려해보세요. C 에서는 어떻게 달성되나요?
답변:
함수 포인터를 사용하여 달성됩니다. 특정 반환 유형 및 매개변수 목록을 가진 함수를 가리키는 포인터 변수를 선언합니다. 예를 들어, int (*compare_func)(const void *, const void *)는 두 개의 const void *를 받고 int를 반환하는 함수에 대한 포인터를 선언합니다. 이는 qsort와 같은 정렬 알고리즘에서 일반적으로 사용됩니다.
C 프로그램을 디버깅 중이며 버퍼 오버플로우가 의심됩니다. 이를 식별하기 위해 어떤 도구나 기술을 사용하시겠습니까?
답변:
GDB 와 같은 디버거를 사용하여 중단점을 설정하고 배열 경계 주변의 메모리 내용을 검사합니다. Valgrind 와 같은 메모리 오류 감지 도구는 버퍼 오버플로, 초기화되지 않은 메모리 읽기 및 메모리 누수를 자동으로 감지하는 데 매우 유용합니다. 정적 분석 도구는 컴파일 중에 잠재적인 취약점을 식별할 수도 있습니다.
디버깅 및 문제 해결
C 프로그래밍에서 흔히 발생하는 오류 유형은 무엇인가요?
답변:
흔한 오류로는 구문 오류 (컴파일러 오류), 런타임 오류 (예: 세그멘테이션 오류, 메모리 누수), 논리 오류 (프로그램이 예상대로 작동하지 않지만 충돌하지는 않음) 가 있습니다. 오류 메시지 또는 프로그램 동작을 이해하는 것이 유형을 식별하는 열쇠입니다.
일반적으로 C 프로그램을 어떻게 디버깅하나요?
답변:
디버깅은 종종 디버거 (GDB 등) 사용, print 문 (printf 디버깅) 추가, 함수 반환 코드 확인, 문제 코드 섹션을 체계적으로 격리하는 것을 포함합니다. 버그를 일관되게 재현하는 것이 첫 번째 단계입니다.
GDB 와 같은 디버거의 목적을 설명해주세요. 사용할 수 있는 몇 가지 기본 명령어는 무엇인가요?
답변:
GDB(GNU Debugger) 를 사용하면 프로그램을 단계별로 실행하고, 변수를 검사하고, 중단점을 설정하고, 호출 스택을 검사할 수 있습니다. 기본 명령어에는 break (b), run (r), next (n), step (s), print (p), continue (c) 가 있습니다.
세그멘테이션 오류 (segmentation fault) 란 무엇이며, 일반적으로 어떻게 문제를 해결하나요?
답변:
세그멘테이션 오류는 프로그램이 액세스 권한이 없는 메모리 위치에 액세스하려고 할 때 발생하며, 종종 널 포인터 역참조, 배열 요소 범위를 벗어난 액세스 또는 해제된 메모리 사용으로 인해 발생합니다. 문제 해결에는 디버거 또는 메모리 분석 도구를 사용하여 포인터 유효성, 배열 경계 및 메모리 할당/해제를 확인하는 것이 포함됩니다.
C 에서 메모리 누수를 감지하고 방지하는 방법은 무엇인가요?
답변:
메모리 누수는 동적으로 할당된 메모리가 해제되지 않아 점진적인 메모리 소비로 이어질 때 발생합니다. Valgrind 와 같은 도구는 감지에 필수적입니다. 방지하려면 각 malloc에 해당 free가 있는지 확인하고, 특히 복잡한 데이터 구조에서 포인터를 신중하게 관리해야 합니다.
'버스 오류 (bus error)'와 '세그멘테이션 오류'의 차이점은 무엇인가요?
답변:
둘 다 메모리 액세스 문제를 나타내는 신호입니다. 세그멘테이션 오류는 일반적으로 프로세스의 할당된 가상 주소 공간 외부의 메모리에 액세스하는 것을 의미합니다. 버스 오류는 일반적으로 잘못 정렬된 메모리 액세스 또는 존재하지 않는 물리적 주소와 같은 하드웨어 관련 메모리 액세스 문제를 나타냅니다.
'printf 디버깅'을 설명해주세요. 언제 유용하며, 한계점은 무엇인가요?
답변:
Printf 디버깅은 코드에 printf() 문을 삽입하여 변수 값, 실행 흐름, 함수 진입/종료 지점을 표시하는 것을 포함합니다. 빠른 확인 및 간단한 논리 이해에 유용합니다. 한계점으로는 재컴파일이 필요하고, 출력이 지저분해지며, 복잡한 상태 또는 타이밍에 민감한 문제에는 어려움이 있다는 점이 있습니다.
시스템 호출 또는 라이브러리 함수의 반환 오류를 C 에서 어떻게 처리하나요?
답변:
시스템 호출 및 많은 라이브러리 함수는 오류 시 특정 값 (예: 실패 시 -1) 을 반환하고 전역 errno 변수를 설정합니다. 이러한 반환 값을 확인하고 errno와 함께 perror() 또는 strerror()를 사용하여 사람이 읽을 수 있는 오류 메시지를 얻는 것이 중요하며, 이를 통해 적절한 오류 처리가 가능합니다.
'코어 덤프 (core dump)'란 무엇이며 디버깅에 어떻게 도움이 되나요?
답변:
코어 덤프는 충돌 시 실행 중인 프로세스의 메모리 이미지를 포함하는 파일입니다. GDB 와 같은 디버거를 사용하여 프로그램을 다시 실행하지 않고도 충돌 시점의 프로그램 상태 (변수, 호출 스택) 를 검사할 수 있는 사후 디버깅을 가능하게 합니다.
프로그램이 간헐적으로 충돌하지만 일관되지는 않습니다. 이러한 간헐적인 문제를 어떻게 디버깅하시겠습니까?
답변:
간헐적인 문제는 종종 경쟁 조건 (race conditions), 초기화되지 않은 변수 또는 힙 손상 (heap corruption) 을 나타냅니다. 충돌을 유발하는 조건을 좁히고, 메모리 오류 감지 도구 (Valgrind) 를 사용하고, 실패 시점을 정확히 파악하기 위해 광범위한 로깅 또는 어설션 (assertion) 을 추가하는 것을 시도할 것입니다.
C 모범 사례 및 성능 최적화
const를 사용하여 C 코드의 안전성과 잠재적인 성능을 어떻게 향상시킬 수 있나요?
답변:
const는 변수의 값이 초기화 후 변경될 수 없도록 보장하여 우발적인 수정을 방지함으로써 코드 안전성을 향상시킵니다. 포인터의 경우, const는 포인터 자체 또는 포인터가 가리키는 데이터에 적용될 수 있습니다. 컴파일러는 const 정보를 사용하여 데이터를 읽기 전용 메모리에 배치하는 등의 최적화를 수행할 수 있습니다.
malloc과 calloc의 차이점을 설명하고, 언제 하나를 다른 것보다 선호할 수 있나요?
답변:
malloc(size)는 초기화되지 않은 size 바이트의 메모리를 할당합니다. calloc(num, size)는 num * size 바이트를 할당하고 모든 비트를 0 으로 초기화합니다. 0 으로 초기화된 메모리 (예: 모든 값이 0 으로 시작해야 하는 배열 또는 구조체) 가 필요한 경우 calloc을 선호하고, 그렇지 않으면 malloc이 초기화 오버헤드를 피하므로 약간 더 효율적입니다.
C 에서 register 키워드의 목적은 무엇이며, 성능 최적화에 여전히 관련이 있나요?
답변:
register 키워드는 컴파일러에게 변수가 더 빠른 액세스를 위해 CPU 레지스터에 저장되어야 함을 제안합니다. 그러나 최신 컴파일러는 매우 정교하며 종종 프로그래머보다 더 나은 레지스터 할당 결정을 내립니다. 사용은 대체로 폐기되었으며 성능을 거의 향상시키지 못하고 때로는 오히려 저해하기도 합니다.
'캐시 지역성 (cache locality)' 개념과 C 성능 최적화에서의 중요성을 설명해주세요.
답변:
캐시 지역성은 캐시 히트율을 최대화하기 위해 데이터 액세스 패턴을 구성하는 것을 의미합니다. 공간 지역성 (spatial locality) 은 메모리에서 가까운 데이터 요소에 액세스하는 것 (예: 배열 순회) 을 의미합니다. 시간 지역성 (temporal locality) 은 최근에 액세스한 데이터를 재사용하는 것을 의미합니다. 좋은 캐시 지역성은 메모리 액세스 시간을 크게 줄여 전반적인 프로그램 성능을 향상시킵니다.
inline 함수는 언제 사용해야 하며, 잠재적인 장점과 단점은 무엇인가요?
답변:
inline은 컴파일러에게 함수 호출 지점에서 함수 본문을 직접 대체하여 함수 호출 오버헤드를 줄이도록 제안합니다. 장점은 작고 자주 호출되는 함수에 대한 잠재적인 속도 향상입니다. 단점은 과도하게 인라인될 경우 코드 크기가 증가 (코드 비대화) 할 수 있으며, 이는 컴파일러에 대한 힌트일 뿐 명령은 아니라는 것입니다.
비트 연산은 C 에서 성능 최적화를 위해 어떻게 사용될 수 있나요?
답변:
비트 연산 (AND, OR, XOR, 시프트) 은 비트를 직접 조작하기 때문에 특정 작업에서 산술 연산보다 종종 빠릅니다. 예로는 플래그 확인/설정, 2 의 거듭제곱으로 곱셈/나눗셈 (시프트 사용), 효율적인 메모리 패킹 등이 있습니다. 이는 저수준 프로그래밍 및 임베디드 시스템에서 중요합니다.
C 에서 메모리 관리와 관련된 일반적인 함정은 무엇이며, 어떻게 피할 수 있나요?
답변:
일반적인 함정으로는 메모리 누수 (할당된 메모리를 free하는 것을 잊음), 메모리 이중 해제, 해제된 메모리 사용 (댕글링 포인터) 이 있습니다. 이는 항상 malloc과 free를 쌍으로 사용하고, 해제 후 포인터를 NULL로 설정하고, 메모리 소유권 및 수명을 신중하게 추적함으로써 피할 수 있습니다.
C 성능 최적화 맥락에서 '프로파일링 (profiling)' 개념을 설명해주세요.
답변:
프로파일링은 프로그램 실행을 측정하고 분석하여 성능 병목 현상을 식별하는 프로세스입니다. gprof 또는 Valgrind 의 Callgrind 와 같은 도구는 어떤 함수가 가장 많은 CPU 시간 또는 메모리를 소비하는지 보여줄 수 있습니다. 이 데이터는 최적화 노력을 안내하여 가장 큰 영향을 미치는 영역에 집중하도록 합니다.
일반적으로 큰 구조체를 함수에 값으로 전달하는 것보다 포인터로 전달하는 것이 더 나은 이유는 무엇인가요?
답변:
큰 구조체를 값으로 전달하는 것은 전체 구조체를 스택에 복사하는 것을 포함하며, 이는 계산 비용이 많이 들고 상당한 스택 공간을 소비할 수 있습니다. 포인터로 전달하는 것은 구조체의 주소만 복사하므로, 특히 큰 데이터 유형의 경우 훨씬 빠르고 메모리 효율적입니다.
C 개발에서 컴파일러 최적화 플래그 (예: -O2, -O3) 의 중요성은 무엇인가요?
답변:
컴파일러 최적화 플래그는 컴파일러에게 성능 (속도) 을 개선하거나 크기를 줄이기 위해 코드에 다양한 변환을 적용하도록 지시합니다. -O2 및 -O3는 점점 더 공격적인 최적화를 활성화합니다. 유익하지만, 더 높은 수준은 때때로 컴파일 시간을 늘리거나 코드 크기를 늘리거나 디버깅을 더 어렵게 만들 수 있습니다.
C 에서의 동시성 및 멀티스레딩
동시성 (concurrency) 과 병렬성 (parallelism) 의 차이점은 무엇인가요?
답변:
동시성은 여러 작업을 한 번에 처리하는 것에 관한 것으로, 종종 단일 코어에서 실행을 번갈아 가며 수행합니다. 병렬성은 여러 작업을 한 번에 수행하는 것에 관한 것으로, 일반적으로 여러 코어 또는 프로세서에서 작업을 동시에 실행합니다.
POSIX 스레드 (pthreads) 를 사용하여 C 에서 새 스레드를 어떻게 생성하나요?
답변:
pthread_create() 함수를 사용합니다. 이 함수는 스레드 ID, 속성, 시작 루틴 (스레드가 실행할 함수), 시작 루틴에 전달할 인자에 대한 인수를 받습니다. 예를 들어: pthread_create(&tid, NULL, my_thread_func, NULL);
pthread_join()의 목적을 설명해주세요.
답변:
pthread_join()은 특정 스레드가 종료될 때까지 기다리는 데 사용됩니다. 호출하는 스레드는 대상 스레드가 실행을 마칠 때까지 차단됩니다. 또한 종료된 스레드의 반환 값을 검색할 수도 있습니다.
뮤텍스 (mutex) 란 무엇이며, 멀티스레딩 프로그래밍에서 왜 사용되나요?
답변:
뮤텍스 (상호 배제, mutual exclusion) 는 여러 스레드에서 공유 리소스에 동시에 액세스하는 것을 방지하기 위해 사용되는 동기화 기본 요소입니다. 특정 시점에 오직 하나의 스레드만 잠금을 획득하고 임계 영역 (critical section) 에 액세스할 수 있도록 보장하여 경쟁 조건 (race condition) 을 방지합니다.
경쟁 조건 (race condition) 을 설명하고 간단한 예를 들어주세요.
답변:
경쟁 조건은 여러 스레드가 공유 데이터에 동시에 액세스하고 수정할 때 발생하며, 최종 결과는 실행 순서의 비결정성에 따라 달라집니다. 예를 들어, 보호 없이 공유 카운터를 증가시키는 두 스레드는 잘못된 최종 값으로 이어질 수 있습니다.
교착 상태 (deadlock) 란 무엇이며, 어떻게 방지할 수 있나요?
답변:
교착 상태는 두 개 이상의 스레드가 서로 리소스를 해제하기를 기다리며 무기한 차단되는 상황입니다. 일관된 잠금 순서를 보장하거나, 잠금 획득에 타임아웃을 사용하거나, 교착 상태 감지 알고리즘을 사용하여 방지할 수 있습니다.
'임계 영역 (critical section)' 개념을 설명해주세요.
답변:
임계 영역은 공유 리소스 (전역 변수, 파일 또는 하드웨어 등) 에 액세스하는 코드 세그먼트입니다. 데이터 손상 및 경쟁 조건을 방지하기 위해 한 번에 하나의 스레드만 실행하도록 보호해야 합니다.
조건 변수 (condition variable) 란 무엇이며 언제 사용하나요?
답변:
조건 변수는 스레드가 특정 조건이 참이 될 때까지 기다릴 수 있도록 하는 동기화 기본 요소입니다. 항상 뮤텍스와 함께 사용됩니다. 일반적인 사용 사례는 프로듀서 - 컨슈머 (producer-consumer) 문제로, 스레드가 데이터가 사용 가능해지거나 버퍼 공간이 생길 때까지 기다립니다.
pthread_mutex_lock()과 pthread_mutex_trylock()의 차이점은 무엇인가요?
답변:
pthread_mutex_lock()은 차단 호출입니다. 뮤텍스가 이미 잠겨 있으면 호출하는 스레드는 잠금을 획득할 수 있을 때까지 차단됩니다. pthread_mutex_trylock()은 차단되지 않습니다. 잠금을 획득하려고 시도하고 즉시 반환하며 대기하지 않고 성공 또는 실패를 나타냅니다.
C 에서 스레드별 데이터 (thread-specific data) 를 어떻게 처리하나요?
답변:
스레드별 데이터 (TSD) 를 사용하면 변수가 전역으로 선언되어 있더라도 각 스레드가 자체 변수 인스턴스를 가질 수 있습니다. pthreads 에서는 pthread_key_create()를 사용하여 키를 생성하고, pthread_setspecific()을 사용하여 해당 키에 대한 데이터를 설정하고, pthread_getspecific()을 사용하여 검색합니다.
세마포어 (semaphore) 란 무엇이며 뮤텍스와 어떻게 다른가요?
답변:
세마포어는 여러 프로세스 또는 스레드에서 공통 리소스에 대한 액세스를 제어하는 신호 메커니즘입니다. 신호에 사용되는 정수 변수입니다. 일반적으로 이진 (잠김/잠금 해제) 이고 스레드에 의해 소유되는 뮤텍스와 달리, 세마포어는 여러 '허가'를 가질 수 있으며 이를 획득하지 않은 스레드에 의해 신호될 수 있습니다.
임베디드 시스템 및 저수준 프로그래밍
임베디드 시스템에서 휘발성 메모리 (volatile memory) 와 비휘발성 메모리 (non-volatile memory) 의 차이점을 설명해주세요.
답변:
휘발성 메모리 (예: RAM, 캐시) 는 저장된 정보를 유지하기 위해 전원이 필요하며, 전원이 제거되면 데이터가 손실됩니다. 비휘발성 메모리 (예: Flash, EEPROM, ROM) 는 전원이 없어도 데이터를 유지하므로 펌웨어 및 구성 설정을 저장하는 데 적합합니다.
메모리 맵핑된 레지스터 (memory-mapped register) 란 무엇이며, 임베디드 프로그래밍에서 왜 사용되나요?
답변:
메모리 맵핑된 레지스터는 CPU 가 메모리 위치처럼 액세스할 수 있는 하드웨어 레지스터입니다. 이를 통해 CPU 는 특정 메모리 주소에 단순히 읽거나 쓰기만 하면 주변 장치 (예: GPIO, 타이머, UART) 를 제어할 수 있어 하드웨어 상호 작용을 단순화합니다.
임베디드 프로그래밍에서 C 의 volatile 키워드를 언제 사용해야 하나요?
답변:
volatile 키워드는 변수의 값이 프로그램의 정상적인 흐름 외부에서 예상치 못하게 변경될 수 있음을 컴파일러에게 알리는 데 사용됩니다. 이는 메모리 맵핑된 레지스터, ISR 에 의해 수정된 전역 변수 또는 스레드 간에 공유되는 변수에 중요하며, 컴파일러가 해당 변수에 대한 액세스를 최적화하여 제거하는 것을 방지합니다.
인터럽트 서비스 루틴 (ISR) 의 목적과 주요 특징을 설명해주세요.
답변:
ISR 은 하드웨어 또는 소프트웨어 인터럽트에 대한 응답으로 CPU 가 실행하는 특수 함수입니다. ISR 은 짧고 효율적이어야 하며, 중요한 컨텍스트에서 실행되고 정상적인 프로그램 실행을 선점할 수 있으므로 부동 소수점 연산이나 차단 호출과 같은 복잡한 연산을 피해야 합니다.
와치독 타이머 (Watchdog Timer, WDT) 란 무엇이며, 임베디드 시스템에서 왜 중요한가요?
답변:
와치독 타이머는 소프트웨어 실행을 모니터링하는 하드웨어 타이머입니다. 소프트웨어가 미리 정의된 간격 내에 WDT 를 '킥 (kick)' 또는 '피드 (feed)'하지 못하면 WDT 는 시스템 재시작을 트리거합니다. 이는 소프트웨어 오류로 인한 시스템 잠김을 방지하여 신뢰성을 향상시킵니다.
'비트 배깅 (bit banging)' 개념을 설명하고 예를 들어주세요.
답변:
비트 배깅은 전용 하드웨어 주변 장치 없이 소프트웨어가 마이크로컨트롤러의 개별 핀을 직접 제어하여 통신 프로토콜 (예: I2C, SPI) 을 구현하는 기술입니다. 예를 들어, 정밀한 지연으로 GPIO 핀을 높음과 낮음으로 토글하면 구형파 또는 직렬 데이터 스트림을 생성할 수 있습니다.
'베어메탈 (bare-metal)' 임베디드 시스템과 RTOS 를 실행하는 시스템의 차이점은 무엇인가요?
답변:
베어메탈 시스템은 운영 체제 없이 하드웨어에서 직접 실행되므로 개발자에게 완전한 제어권을 제공하지만 작업 및 리소스의 수동 관리가 필요합니다. RTOS(실시간 운영 체제) 는 작업 스케줄링, 프로세스 간 통신 및 리소스 관리와 같은 서비스를 제공하여 적시 응답을 보장하면서 복잡한 멀티태스킹 애플리케이션을 단순화합니다.
임베디드 시스템에서 오류 또는 예상치 못한 상태를 일반적으로 어떻게 처리하나요?
답변:
임베디드 시스템의 오류 처리는 종종 여러 기법의 조합을 포함합니다. 소프트웨어 멈춤을 위한 와치독 타이머 사용, 강력한 오류 코드/플래그 구현, 중요 이벤트 로깅, 방어적 프로그래밍 (예: 입력 유효성 검사, 범위 검사) 사용 등이 있습니다. 복구 불가능한 오류의 경우 시스템 재시작이 일반적인 대체 방법입니다.
엔디언 (endianness) 이란 무엇이며, 임베디드 프로그래밍에서 왜 관련이 있나요?
답변:
엔디언은 다중 바이트 데이터 (정수 등) 가 메모리에 저장되는 바이트 순서를 나타냅니다. 빅 엔디언은 가장 중요한 바이트를 먼저 저장하고, 리틀 엔디언은 가장 중요하지 않은 바이트를 먼저 저장합니다. 서로 다른 엔디언을 가진 시스템 간에 통신하거나 외부 소스 (예: 네트워크 프로토콜, 파일 형식) 에서 데이터를 구문 분석할 때 중요합니다.
임베디드 개발에서 링커 스크립트 (linker script) 의 역할을 설명해주세요.
답변:
링커 스크립트는 컴파일된 코드의 다양한 섹션 (.text, .data, .bss 등) 을 대상 임베디드 장치의 특정 메모리 영역 (예: Flash, RAM) 에 매핑하는 방법을 링커에게 알려주는 구성 파일입니다. 메모리 레이아웃, 진입점 및 심볼 배치를 정의하며, 이는 제약된 하드웨어에서 올바르게 실행되는 데 중요합니다.
C 에서의 객체 지향 프로그래밍 개념
C 에서 '캡슐화 (encapsulation)'를 어떻게 달성할 수 있나요?
답변:
C 에서의 캡슐화는 구조체 (struct) 를 사용하여 데이터와 함수 포인터를 번들로 묶어 달성합니다. 정보 은닉은 구조체 멤버를 private 으로 선언하고 (관례적으로 밑줄로 접두사 붙임), 불투명 포인터 (opaque pointer) 를 통해 종종 데이터와 상호 작용하기 위한 public 함수 (API) 를 제공함으로써 이루어집니다.
C 에서 '추상화 (abstraction)'가 어떻게 구현되는지 설명해주세요.
답변:
C 에서의 추상화는 헤더 파일을 사용하여 모듈 또는 '객체'에 대한 명확한 인터페이스 (API) 를 정의함으로써 구현됩니다. 사용자는 내부 구현 세부 정보나 알고리즘을 알 필요 없이 이러한 public 함수와만 상호 작용합니다. 불투명 포인터는 내부 구조를 숨기는 데 자주 사용됩니다.
C 에서 '상속 (inheritance)'이 직접 지원되나요? 지원되지 않는다면 어떻게 시뮬레이션할 수 있나요?
답변:
아니요, C 는 상속을 직접 지원하지 않습니다. '파생 클래스 (derived class)' 구조체의 첫 번째 멤버로 '기본 클래스 (base class)' 구조체를 포함시킴으로써 시뮬레이션할 수 있습니다. 이를 통해 파생 클래스 포인터를 기본 클래스 포인터로 캐스팅할 수 있으며, 기본 구조체의 함수 포인터를 통해 다형성 (polymorphism) 을 가능하게 합니다.
C 에서 '다형성 (polymorphism)'은 어떻게 시뮬레이션되나요?
답변:
C 에서의 다형성은 구조체 내의 함수 포인터를 사용하여 시뮬레이션되며, 종종 '가상 테이블 (virtual tables)' 또는 '디스패치 테이블 (dispatch tables)'이라고 불립니다. 함수에 대한 다른 구현은 '객체' 유형에 따라 동일한 함수 포인터에 할당될 수 있으며, 이를 통해 공통 인터페이스가 타입별 동작을 호출할 수 있습니다.
'불투명 포인터 (opaque pointer)'란 무엇이며, C 에서 OOP 에 왜 유용한가요?
답변:
불투명 포인터는 불완전한 타입에 대한 포인터로, 일반적으로 헤더 파일에 선언됩니다 (예: typedef struct MyObject MyObject;). 이는 사용자에게 객체의 내부 구조에 직접 액세스하는 것을 방지하고, public API 함수를 통해서만 상호 작용을 허용함으로써 캡슐화와 추상화를 강제합니다.
C 의 맥락에서 '생성자 (constructor)'와 '소멸자 (destructor)' 개념을 설명해주세요.
답변:
C 에서 '생성자'는 객체에 대한 메모리를 할당하고 멤버를 초기화하는 함수로, 종종 새로 생성된 인스턴스에 대한 포인터를 반환합니다. '소멸자'는 객체와 관련된 메모리를 해제하고 리소스를 정리하는 함수로, 메모리 누수를 방지합니다.
C '객체'에 대한 '메서드 (method)'를 어떻게 구현할 수 있나요?
답변:
C '객체'에 대한 '메서드'는 일반적으로 객체 구조체에 대한 포인터를 첫 번째 인수로 받는 일반 C 함수로 구현됩니다. 예를 들어, void object_doSomething(MyObject* obj, int value);와 같습니다. 이러한 함수는 전달된 특정 인스턴스에 대해 작동합니다.
C 구조체에 'private' 및 'public' 멤버를 가질 수 있나요? 이 관례는 어떻게 강제되나요?
답변:
C 구조체에는 내장된 private 또는 public 키워드가 없습니다. 이러한 개념은 관례와 규율을 통해 강제됩니다. 'Public' 멤버는 API 함수를 통해 노출되며, 'private' 멤버 (종종 밑줄로 접두사 붙임) 는 내부 사용만을 위한 것이며 외부 코드에서 직접 액세스되지 않습니다.
C 에서 OOP 와 유사한 접근 방식을 사용하는 것의 장점은 무엇인가요?
답변:
C 에서 OOP 와 유사한 접근 방식을 사용하면 코드 구성, 모듈성 및 유지 관리성이 향상됩니다. 데이터 은닉을 촉진하고, 구성 요소 간의 결합도를 줄이며, 특히 대규모 임베디드 시스템 또는 라이브러리 개발에서 더 유연하고 확장 가능한 설계를 가능하게 합니다.
C++ 와 같은 언어를 사용하는 대신 C 에서 OOP 를 시뮬레이션하기로 선택하는 경우는 언제인가요?
답변:
엄격한 메모리 제약이 있는 환경에서 작업하거나, C++ 런타임 오버헤드가 허용되지 않거나, 기존 C 코드베이스와 인터페이스해야 할 때 C 에서 OOP 를 시뮬레이션하기로 선택할 수 있습니다. 또한 임베디드 시스템, 커널 개발 또는 최소한의 풋프린트가 중요한 경우에도 일반적입니다.
빌드 시스템 및 툴체인 지식
Make 또는 CMake 와 같은 빌드 시스템의 주요 목적은 무엇인가요?
답변:
빌드 시스템은 컴파일 프로세스를 자동화하고 소스 파일 간의 종속성을 관리하며, 변경이 발생했을 때 필요한 구성 요소만 다시 컴파일되도록 보장합니다. 이를 통해 다양한 플랫폼 및 구성에서 빌드 프로세스를 간소화합니다.
'make'와 'cmake'의 차이점을 설명해주세요.
답변:
Make 는 Makefile 의 지침을 실행하는 빌드 자동화 도구입니다. CMake 는 상위 수준의 구성 스크립트에서 네이티브 빌드 시스템 파일 (Makefile 또는 Visual Studio 프로젝트 등) 을 생성하는 메타 빌드 시스템으로, 플랫폼 독립성을 제공합니다.
'Makefile'이란 무엇이며, 필수 구성 요소는 무엇인가요?
답변:
Makefile 은 'make' 유틸리티가 빌드 프로세스를 자동화하는 데 사용하는 스크립트입니다. 필수 구성 요소는 '타겟 (target)'(빌드할 대상), '전제 조건 (prerequisites)'(타겟을 빌드하는 데 필요한 파일), 그리고 '레시피 (recipes)'(실행할 명령) 입니다.
C 프로그램의 일반적인 컴파일 단계를 설명해주세요.
답변:
일반적인 단계는 다음과 같습니다: 전처리 (매크로 확장, 헤더 포함), 컴파일 (C 코드를 어셈블리로 변환), 어셈블리 (어셈블리를 오브젝트 코드로 변환), 그리고 링킹 (오브젝트 파일 및 라이브러리를 실행 파일로 결합) 입니다.
링커 (linker) 의 역할은 무엇이며, 정적 링킹 (static linking) 과 동적 링킹 (dynamic linking) 의 차이점은 무엇인가요?
답변:
링커는 오브젝트 파일과 라이브러리를 결합하여 실행 가능한 프로그램을 만듭니다. 정적 링킹은 라이브러리 코드를 실행 파일에 직접 포함시키는 반면, 동적 링킹은 런타임에 라이브러리 종속성을 해결하여 더 작은 실행 파일과 공유 라이브러리 사용을 가능하게 합니다.
정적 링킹 대신 동적 링킹을 선택하는 경우와 그 반대의 경우는 언제인가요?
답변:
대상 시스템에 특정 라이브러리 버전이 존재하지 않아도 되는 독립적인 실행 파일의 경우 정적 링킹을 선택합니다. 디스크 공간을 절약하고, 애플리케이션을 다시 컴파일하지 않고 라이브러리를 업데이트할 수 있으며, 동일한 라이브러리를 사용하는 프로세스 간에 메모리를 공유하려면 동적 링킹을 선택합니다.
'공유 라이브러리'(Windows 에서는 '동적 링크 라이브러리') 란 무엇이며, 왜 사용되나요?
답변:
공유 라이브러리는 런타임에 메모리에 로드되어 여러 프로그램에서 사용할 수 있는 사전 컴파일된 코드 모음입니다. 디스크 공간을 절약하고, 메모리 사용량을 줄이며, 애플리케이션을 다시 컴파일하지 않고도 더 쉬운 업데이트 및 버그 수정을 가능하게 합니다.
포함 가드 (include guards) 는 헤더 파일의 중복 포함을 어떻게 방지하나요?
답변:
포함 가드는 전처리기 지시문 (#ifndef, #define, #endif) 을 사용하여 고유한 매크로가 이미 정의되었는지 확인합니다. 정의되었다면 헤더 파일의 내용이 건너뛰어져 재정의 오류 및 순환 종속성을 방지합니다.
크로스 컴파일 (cross-compilation) 이란 무엇이며, 왜 필요한가요?
답변:
크로스 컴파일은 한 아키텍처 (호스트) 에서 코드를 컴파일하여 다른 아키텍처 (대상) 에서 실행되도록 하는 것입니다. 대상 시스템이 리소스가 제한적이거나 (예: 임베디드 시스템) 적절한 컴파일러가 없는 경우 필요합니다.
오픈 소스 프로젝트에서 자주 발견되는 'configure' 스크립트의 목적을 설명해주세요.
답변:
'configure' 스크립트는 시스템 환경 (예: 컴파일러, 라이브러리, 헤더) 을 검사하고 적절한 Makefile 또는 빌드 스크립트를 생성합니다. 로컬 구성을 조정하여 다양한 시스템에서 소프트웨어가 올바르게 빌드되도록 보장합니다.
요약
C 면접 질문을 마스터하는 것은 언어의 기본 및 고급 개념에 대한 탄탄한 이해를 증명하는 것입니다. 이러한 질문에 답하기 위한 준비는 기술적 기술을 연마할 뿐만 아니라 복잡한 아이디어를 명확하고 간결하게 설명하는 데 자신감을 심어줍니다. 이 문서는 포괄적인 개요를 제공하여 자신감을 가지고 면접에 접근할 수 있도록 하는 것을 목표로 했습니다.
C 또는 다른 프로그래밍 언어를 배우는 여정은 지속적이라는 것을 기억하십시오. 성공적인 면접 후에도 계속 탐색하고, 구축하고, 기술을 다듬으십시오. 새로운 도전을 받아들이고, 프로젝트에 기여하고, 호기심을 유지하십시오. 지속적인 학습에 대한 헌신은 역동적이고 진화하는 기술 환경에서 가장 큰 자산이 될 것입니다.



