🌱 Bàn về khái niệm Struct Alignment, Padding trong C

🌱 Bàn về khái niệm Struct Alignment, Padding trong C

    Một khái niệm rất quen thuộc khi học C - Struct đó chính là Alignment, một khái niệm rất quan trọng, bàn về việc sắp xếp của các phần tử của một Struct.

    Bài viết này sẽ bàn về các khái niệm Alignment Padding, để hiểu được bài viết này, bạn cần có kiến thức về kiểu dữ liệu Struct.

    👉 Khái niệm Data Alignment trên Memory

    Tất cả các kiểu dữ liệu trong C đều được yêu cầu Alignment, khái niệm này phụ thuộc vào kiến trúc của vi xử lý. Một vi xử lý sẽ được xử lý độ rộng của một word phụ thuộc vào kích thước của bus dữ liệu. Đối với vi xử lý 32-bits, kích thước một word là 4 bytes (Trong bài viết này sẽ chỉ nói đến vi xử lý 32-bits).

C Struct
C Memory Bank

    Dễ thấy, việc đặt một word = 4 bytes sẽ giúp CPU truy cập đến bộ nhớ nhanh hơn, vì một chu kỳ máy chúng ta sẽ lấy được 4 bytes thay vì 1 byte.

    Chính vì vậy, CPU sẽ lấy dữ liệu trong 4 bytes/1 chu kỳ. Một trường hợp khá phổ biến nếu như data được đặt không đúng theo Alignment như hình bên dưới.

C Struct Alignment Missing

    Giả sử có một số 32 bits, uint32_t Var = 0x12345678 được đặt vào bộ nhớ như trên (Little Endian). Việc access vào bộ nhớ để truy xuất đến biến Var này sẽ cần 2 chu kỳ máy, tức là mất thời gian hơn gấp đôi.

    ⟹ Chính vì vậy, khái niệm Data Alignment sinh ra để giúp việc truy cập vào các biến nhanh chóng hơn.

    Theo đó, các biến có kích thước n bytes sẽ được đặt vào các ô nhớ có địa chỉ chia hết cho n.

  • Các số 4 bytes sẽ được đặt vào BANK 0, 1, 2, 3; 
  • Các số 2 bytes (uint16_t) sẽ được đặt vào BANK 0, 1 hoặc BANK 2, 3;
  • Các số 1 byte có thể được đặt vào bất kỳ BANK nào.
  • Trong khi đó các kiểu dữ liệu 8 bytes như uint64_t hay double sẽ được đặt vào trong 2 Words.

    👉 Struct Padding trong C

     Từ khái niệm Alignment, dễ thấy các phần tử trong Struct sẽ được tự động Align theo cách trên. Khái niệm Struct Padding là việc bổ sung một số bytes bộ nhớ trống trong cấu trúc Alignment.

    ➤  Ví dụ ta có một struct như sau:

typedef struct {
    uint8_t   A;
    uint32_t  B;
} TestType;

      Phần tử A trong Struct trên sẽ nằm ở 1 byte ô nhớ có địa chỉ nhỏ nhất, sau đó, nếu không tuân theo Alignment, thì bộ nhớ của B sẽ được sắp xếp như sau:

C Struct

    ➤ Tuy nhiên, như đã nói ở trên, việc truy xuất phần tử B cần đến 2 chu kỳ máy, gây ra việc tốc độ thực thi chương trình bị chậm. Vì vậy, theo cách Alignment tự nhiên, bộ nhớ sẽ được sắp xếp như sau:

C Struct

    Khái niệm Padding ở đây thực tế chỉ là việc bổ sung một số byte bộ nhớ trống trong struct để căn chỉnh các các phần tử dữ liệu trong bộ nhớ. Padding được thực hiện để giảm thiểu số chu kỳ đọc của CPU khi truy cập đến các phần tử trong struct.

    ❓ Struct sắp xếp như này có rủi ro gì không

    1. Tốn bộ nhớ

    Điều đầu tiên bạn có thể nhìn thấy ngay bằng mắt thường, đó là mặc dù tiết kiệm về mặt thời gian nhưng Padding lại gây tốn thêm chút bộ nhớ, mặc dù không đáng kể nếu sử dụng ít. Nhưng trong một số trường hợp cần dùng các Struct Array thì trường hợp này cũng khá đáng báo động.

    2. Rủi ro khi truyền dữ liệu

    Một số trường hợp khi cần truyền dữ liệu qua các chuẩn truyền thông (chẳng hạn UART), dữ liệu được truyền theo từng Byte, nếu 2 bên truyền nhận không thống nhất về Alignment của Struct thì có thể gây ra sai lệch dữ liệu.

    Ví dụ 2 thiết bị Vi điều khiển (STM32F4 và PC) qua UART, cần truyền một struct data như sau:

typedef struct {
    uint8_t   A;
    uint64_t  B;
} TestType;

  • STM32F4 có Alignment tự nhiên là 4 bytes, sizeof(TestType) = 12 bytes.
  • PC vi xử lý x86 có Alignment tự nhiên là 8 bytes, sizeof(TestType) = 16 bytes.
    Nếu truyền dữ liệu từng byte qua UART từ STM32F4 sang PC hoặc ngược lại thì số lượng byte data sẽ bị lệch. Vì vậy, cần thống về Alignment ở bên truyền và nhận.

    👉 Khái niệm Struct Packing?

    Trong một số trường hợp chúng ta cần phải tránh Alignment một cách tự nhiên và Padding như kể trên. Vì vậy, hầu hết các compiler đều hỗ trợ các tiện ích để tắt phần padding mặc định (xem chi tiết tài liệu của từng compiler).

    Ví dụ trong GCC, chúng ta có thể sử dụng cách sau để tắt phần padding:

#pragma pack(1)    // 1 or 2, 4, 8, ...

    hoặc,

struct name {
    ...
}__attribute__((packed));

    hoặc, khi bạn muốn cấu hình padding cho một số struct nhất định,

#pragma pack(push,2)    // 1 or 2, 4, 8

typedef struct {
    uint8_t   A;
    uint64_t  C;
} TestType;

#pragma pack(pop)

    Tất nhiên là cái pack(x) này chỉ áp dụng cho số mũ của 2, vì làm gì có kiểu dữ liệu 3, 5, 6, 7 byte 😆 Trường hợp này Compiler sẽ báo Warning và tự động để Alignment tự nhiên.

C Struct Invalid Alignment
Invalid Pack Alignment

    Kết luận. Alignment, Padding, Packing là các khái niệm rất quan trọng trong C và Embedded, vì vậy cần nắm thật chắc khái niệm và thường xuyên thực hành để tránh nhầm lần trong quá trình sử dụng.

>>>= Follow ngay =<<<

Để nhận được những bài học miễn phí mới nhất nhé 😊

Chúc các bạn học tập tốt 😊

                 

Nguyễn Văn Nghĩa

Mình là một người thích học hỏi và chia sẻ các kiến thức về Nhúng IOT.

1 Nhận xét

Mới hơn Cũ hơn