Top 20 câu hỏi phỏng vấn C/C++ cho Embedded Engineer
Tổng hợp các câu hỏi phỏng vấn phổ biến về C/C++ dành cho vị trí Embedded Software Engineer với đáp án chi tiết
Giới thiệu
Phỏng vấn cho vị trí Embedded Software Engineer thường tập trung vào kiến thức sâu về C/C++, đặc biệt là các khía cạnh liên quan đến hardware và resource-constrained systems. Bài viết này tổng hợp 20 câu hỏi phổ biến nhất kèm theo đáp án chi tiết.
Phần 1: Kiến thức cơ bản về C
1. Sự khác biệt giữa const int *, int * const, và const int * const?
Đáp án:
const int *ptr1; // Pointer to constant integer (không thay đổi giá trị được trỏ tới)
int * const ptr2; // Constant pointer to integer (không thay đổi địa chỉ được trỏ tới)
const int * const ptr3; // Constant pointer to constant integer (cả hai đều không đổi)Ví dụ:
int value1 = 10;
int value2 = 20;
const int *ptr1 = &value1;
// *ptr1 = 15; // ❌ Lỗi: không thể thay đổi giá trị
ptr1 = &value2; // ✅ OK: có thể thay đổi địa chỉ
int * const ptr2 = &value1;
*ptr2 = 15; // ✅ OK: có thể thay đổi giá trị
// ptr2 = &value2; // ❌ Lỗi: không thể thay đổi địa chỉ2. static keyword có những ý nghĩa gì trong C?
Đáp án:
static có 3 ý nghĩa tùy theo context:
- Static local variable: Biến local nhưng giữ giá trị giữa các lần gọi hàm
- Static global variable/function: Giới hạn scope trong file hiện tại (internal linkage)
- Static member (C++): Thuộc class, không phải instance
// 1. Static local variable
void counter() {
static int count = 0; // Chỉ khởi tạo 1 lần
count++;
printf("Count: %d\n", count);
}
// 2. Static global (file scope)
static int internal_var = 10; // Chỉ visible trong file này
static void internal_func() { // Chỉ gọi được trong file này
// ...
}3. Volatile keyword là gì và khi nào cần dùng?
Đáp án:
volatile báo cho compiler biết biến có thể thay đổi bất ngờ (bởi hardware, interrupt, thread khác), không được optimize.
Khi nào dùng:
- Hardware registers
- Variables shared với ISR (Interrupt Service Routine)
- Multi-threaded applications
- Memory-mapped I/O
// Hardware register
volatile uint32_t *GPIO_ODR = (uint32_t *)0x40020014;
// Shared với interrupt
volatile uint8_t flag = 0;
void interrupt_handler(void) {
flag = 1; // Set từ ISR
}
void main_loop(void) {
while (flag == 0) { // Compiler không optimize vì volatile
// Wait...
}
}4. Memory layout của một C program như thế nào?
Đáp án:
┌─────────────────┐ High Address
│ Stack │ ← Local variables, function parameters
├─────────────────┤ ↓ Grows down
│ ↓ │
│ │
│ ↑ │
├─────────────────┤ ↑ Grows up
│ Heap │ ← Dynamic memory (malloc)
├─────────────────┤
│ BSS Segment │ ← Uninitialized global/static variables
├─────────────────┤
│ Data Segment │ ← Initialized global/static variables
├─────────────────┤
│ Text Segment │ ← Code (instructions)
└─────────────────┘ Low Address
Ví dụ:
int global_init = 10; // Data segment
int global_uninit; // BSS segment
static int static_var; // BSS segment
const int readonly = 5; // Text segment (hoặc rodata)
void function() {
int local; // Stack
static int s_local; // Data/BSS segment
int *ptr = malloc(4); // Heap
}Phần 2: Pointer và Memory Management
5. Sự khác biệt giữa malloc và calloc?
Đáp án:
| Feature | malloc | calloc |
|---|---|---|
| Parameters | malloc(size) | calloc(count, size) |
| Initialization | Không khởi tạo (garbage values) | Khởi tạo = 0 |
| Speed | Nhanh hơn | Chậm hơn (do zero-init) |
| Use case | Khi sẽ ghi đè toàn bộ | Khi cần data = 0 |
// malloc
int *arr1 = malloc(10 * sizeof(int)); // Giá trị random
if (arr1) {
memset(arr1, 0, 10 * sizeof(int)); // Phải clear thủ công
}
// calloc
int *arr2 = calloc(10, sizeof(int)); // Đã = 06. Memory leak là gì? Cách phát hiện và tránh?
Đáp án:
Memory leak: Bộ nhớ được cấp phát nhưng không được giải phóng, dẫn đến hết memory.
Cách tránh:
- Luôn
free()saumalloc/calloc - Set pointer = NULL sau khi free
- Dùng RAII pattern (C++)
- Smart pointers (C++)
Ví dụ memory leak:
// ❌ Memory leak
void bad_function() {
char *buffer = malloc(1024);
// ... xử lý ...
// Quên free(buffer);
return; // buffer bị mất, không free được nữa
}
// ✅ Đúng cách
void good_function() {
char *buffer = malloc(1024);
if (!buffer) return;
// ... xử lý ...
free(buffer);
buffer = NULL;
}Tools phát hiện:
- Valgrind (Linux)
- AddressSanitizer (GCC/Clang)
- Static analyzers (Cppcheck, PVS-Studio)
Phần 3: Bit Manipulation
7. Làm sao để set/clear/toggle/check một bit?
Đáp án:
#define BIT(n) (1U << (n))
// Set bit
value |= BIT(3); // Set bit 3
// Clear bit
value &= ~BIT(3); // Clear bit 3
// Toggle bit
value ^= BIT(3); // Toggle bit 3
// Check bit
if (value & BIT(3)) { // Check bit 3 = 1
// Bit is set
}Ví dụ thực tế với GPIO:
// STM32 GPIO example
#define GPIO_PIN_5 (1U << 5)
// Set pin 5 HIGH
GPIOA->ODR |= GPIO_PIN_5;
// Set pin 5 LOW
GPIOA->ODR &= ~GPIO_PIN_5;
// Toggle pin 5
GPIOA->ODR ^= GPIO_PIN_5;
// Check pin 5 state
if (GPIOA->IDR & GPIO_PIN_5) {
// Pin is HIGH
}8. Làm sao để kiểm tra một số là lũy thừa của 2?
Đáp án:
bool is_power_of_2(unsigned int n) {
return (n != 0) && ((n & (n - 1)) == 0);
}Giải thích:
- Số lũy thừa 2 có duy nhất 1 bit = 1
- VD: 8 =
1000, 8-1 = 7 =0111 - 8 & 7 =
1000&0111=0000
Ví dụ:
is_power_of_2(8) // true: 1000 & 0111 = 0000
is_power_of_2(16) // true: 10000 & 01111 = 00000
is_power_of_2(10) // false: 1010 & 1001 = 1000 (≠ 0)Phần 4: Struct và Union
9. Sự khác biệt giữa struct và union?
Đáp án:
| Feature | Struct | Union |
|---|---|---|
| Memory | Tổng size của tất cả members | Size của member lớn nhất |
| Access | Tất cả members cùng lúc | Chỉ 1 member tại 1 thời điểm |
| Use case | Nhóm data liên quan | Tiết kiệm memory, type punning |
struct MyStruct {
int a; // 4 bytes
char b; // 1 byte
float c; // 4 bytes
}; // Total: 12 bytes (với padding)
union MyUnion {
int a; // 4 bytes
char b; // 1 byte
float c; // 4 bytes
}; // Total: 4 bytes (largest member)Use case thực tế:
// Union để truy cập từng byte của uint32_t
union U32Bytes {
uint32_t word;
uint8_t bytes[4];
};
union U32Bytes data;
data.word = 0x12345678;
printf("Byte 0: 0x%02X\n", data.bytes[0]); // 0x78 (little-endian)Phần 5: Embedded Specific
10. Watchdog timer là gì và tại sao quan trọng?
Đáp án:
Watchdog Timer (WDT): Hardware timer reset MCU nếu không được "feed" định kỳ.
Tại sao quan trọng:
- Phát hiện software hang/crash
- Tự động recovery
- Critical trong embedded systems (không thể restart thủ công)
Ví dụ STM32:
// Khởi tạo watchdog (4 giây timeout)
void watchdog_init(void) {
IWDG->KR = 0x5555; // Enable register access
IWDG->PR = 6; // Prescaler
IWDG->RLR = 4095; // Reload value
IWDG->KR = 0xCCCC; // Start watchdog
}
// Main loop
void main(void) {
watchdog_init();
while(1) {
// Xử lý task...
IWDG->KR = 0xAAAA; // Refresh watchdog (feed the dog)
delay_ms(1000);
}
}Kết luận
Các câu hỏi trên là nền tảng quan trọng cho phỏng vấn Embedded Engineer. Để chuẩn bị tốt:
- Hiểu sâu, không chỉ nhớ: Hiểu tại sao, khi nào dùng
- Practice coding: Viết code thực tế, không chỉ đọc
- Làm projects: Portfolio về embedded thực tế
- Đọc datasheets: Quen với hardware documentation
- Debug skills: Biết dùng debugger, logic analyzer
Chúc bạn phỏng vấn thành công! 🚀
Tài liệu tham khảo
Bài viết liên quan
Câu hỏi phỏng vấn Linux và Operating System cơ bản đến nâng cao
Chuẩn bị cho phỏng vấn vị trí Linux Developer/DevOps/System Engineer với các câu hỏi từ cơ bản đến nâng cao về Linux và Operating System.
Giới thiệu về Vi điều khiển STM32
STM32 là dòng vi điều khiển 32-bit dựa trên kiến trúc ARM Cortex-M, được sử dụng rộng rãi trong các ứng dụng nhúng từ IoT đến điều khiển công nghiệp.
Git là gì? Các lệnh thường dùng khi đi làm, Github & Gitlab, Source Control trong VSCode, Git Graph, Git Blame, Sourcetree
Tìm hiểu Git từ cơ bản đến nâng cao: các lệnh Git thiết yếu khi đi làm, làm việc với Github/Gitlab, và sử dụng các công cụ Git GUI như VSCode Source Control, Git Graph, Git Blame và Sourcetree.