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

Top 20 câu hỏi phỏng vấn C/C++ cho Embedded Engineer

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:

  1. Static local variable: Biến local nhưng giữ giá trị giữa các lần gọi hàm
  2. Static global variable/function: Giới hạn scope trong file hiện tại (internal linkage)
  3. 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:

Featuremalloccalloc
Parametersmalloc(size)calloc(count, size)
InitializationKhông khởi tạo (garbage values)Khởi tạo = 0
SpeedNhanh hơnChậm hơn (do zero-init)
Use caseKhi 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));   // Đã = 0

6. 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() sau malloc/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:

FeatureStructUnion
MemoryTổng size của tất cả membersSize của member lớn nhất
AccessTất cả members cùng lúcChỉ 1 member tại 1 thời điểm
Use caseNhóm data liên quanTiế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

7 min read
InterviewC/C++EmbeddedProgrammingJob Interview

Bài viết liên quan