🌱 Embedded C - User-Defined Data Type - Kiểu dữ liệu Struct

🌱 Embedded C - Embedded C - User-Defined Data Type - Kiểu dữ liệu Struct

    Ở bài viết về Embedded C Data Type, chúng ta đã nhắc đến User-Defined Data Type (Kiểu dữ liệu người dùng tự định nghĩa). C hỗ trợ 3 kiểu User-Defined Data Type: Struct, Union và Enum.

    Bài viết này sẽ đi chi tiết vào Struct, kiểu dữ liệu được sử dụng thường xuyên và rất quan trọng trong lập trình Embedded C.


Mục Lục


Tại sao cần có Struct?

    Trước đó chúng ta đã bàn về Array, với một ví dụ: "Khi cần lưu trữ nhiệt độ, ta tạo một biến float temp;. Nhưng khi cần lưu trữ vào quản lý nhiều dữ liệu hơn, chẳng hạn lưu trữ lịch sử nhiệt độ 100 lần gần nhất, việc tạo ra 100 biến sẽ thực sự khó khăn trong việc quản lý (float temp1, float temp2, ...; 😰), nên chúng ta có thể tạo Array ↪ float temp[100];"

    Ở một trường hợp khác, nếu ta cần lưu trữ các thông tin khác kiểu dữ liệu, ví dụ thời gian, độ ẩm, ... thì việc tạo nhiều mảng cũng sẽ gây ra khó quản lý.

    Ví dụ: int time[100]; float temp[100]; int humidity[100]; ➜ khá ổn nếu chỉ lưu một node cảm biến, nếu có nhiều node hơn, thì lại phải tạo nhiều mảng như vậy với cái tên time1
😰

    Chính vì vậy, C hỗ trợ một kiểu dữ liệu có thể lưu trữ các thành phần với data type khác nhau, còn lưu gì thì để người dùng tự định nghĩa vì mỗi bài toán sẽ lưu thông tin khác nhau, đó chính là Struct.

Definition: In C programming, a struct (or structure) is a collection of variables (can be of different types) under a single name.

Tạo một Struct

    Trước khi sử dụng, dev cần tạo một struct (Data Type) sử dụng struct keyword.

struct structureName {
  dataType member1;
  dataType member2;
  ...
};

    Các thành phần bên trong struct (item / element) có thể là một biến, một mảng, một struct, ... (hoặc bất cứ kiểu dữ liệu nào bạn đã biết).

    Một vài ví dụ về struct:

struct Sensor {
  int time;
  float temp;
  int humidity;
};

struct Person {
  char name[50];    // array in struct
  int citNo;
  float salary;
};

struct infor {
  struct Person per;    // nested struct
  int hasgtag;
};

    Các struct trên là chúng ta đã tạo ra các kiểu dữ liệu (User-defined Data Type), chứ chưa phải các biến, tức là bản thân chúng không chiếm bộ nhớ. Dưới đây là ví dụ về việc tạo các biến với kiểu dữ liệu struct đã tạo:

struct Sensor Data1;    // Un-Initialized Variable
struct Sensor Data2 = { 1, 30.5, 40 };    // Initialized Variable
struct Sensor Data3 = { .time = 1, .temp = 30.5, .humidity = 40 };    // Initialized Variable
struct Sensor Data4 = { .time = 1, .temp = 30.5 };    // .humidity = 0 default

typedef keyword

    Dễ thấy với cách khai báo trên, từ "struct" sẽ thường khiến việc viết code trở nên dài dòng, có thể sử dụng từ khóa typedef để rút gọn tên.

    Ví dụ:

typedef struct {
  int time;
  float temp;
  int humidity;
} Sensor_t;    // Create Data Type

// Create Variable
Sensor_t Data = { .time = 1, .temp = 30.5, .humidity = 40 };

    Các Data Type được tạo ra nên thêm đuôi '_t' hoặc 'Type' để đánh dấu đây là một kiểu dữ liệu (học tập cách làm của thư viện stdint.h).

Sử dụng một Struct

    Giống như một biến thông thường, một biến tạo ra theo kiểu struct cũng có những đặc điểm tương tự:

  • Identifier, Chiếm bộ nhớ, có địa chỉ
  • Có thể tạo mảng các struct
  • Có thể truyền vào hàm và trả về hàm

    Một số điểm khác biệt:

struct chứa nhiều element nên cần truy xuất từng phần tử

    Để truy cập đến phần tử bên trong struct, sử dụng toán tử '.' (Đối với struct pointer có thể sử dụng toán tử sẽ được nhắc đến ở phần sau).

  1. #include <stdio.h>
  2. #include <string.h>
  3. typedef struct {
  4. int time;
  5. float temp;
  6. int humidity;
  7. } Sensor_t; // Create Data Type
  8. int main() {
  9. // Create Variable
  10. Sensor_t Data;
  11. // assign values struct element
  12. Data.time = 1;
  13. Data.temp = 30.5;
  14. Data.humidity = 40;
  15. // print struct variables
  16. printf("Time: %d\n", Data.time);
  17. printf("Temperature: %f\n", Data.temp);
  18. printf("Huminity : %d", Data.humidity);
  19. return 0;
  20. }

Kích thước của struct

    Theo logic thông thường thì kích thước struct có lẽ bằng tổng kích thước các phần tử bên trong nó. Tuy nhiên thực tế thì kết quả lại khác! Hãy thử đoạn code sau:

  1. #include <stdio.h>
  2. #include <stdint.h>
  3. typedef struct {
  4. uint8_t A;
  5. uint32_t B;
  6. } TestType;
  7. int main() {
  8. printf("Size of TestType: %d", sizeof(TestType)); // 8
  9. return 0;
  10. }

    Dễ thấy kết quả là 8 thay vì 5. Điều này là kết quả của khái niệm Alignment - Padding Struct sẽ được đề cập ở dưới.

Truyền struct vào hàm

    Với khái niệm Alignment kể trên, kích thước struct luôn ≥ tổng kích thước các phần tử trong nó, vậy khi truyền struct vào hàm hoặc hàm trả về struct, việc stacking khi function call sẽ chiếm một số lượng bộ nhớ khá lớn.

    ↪ Chính vì vậy, việc truyền struct lớn vào hàm thường được sử dụng dưới dạng Pass by Reference thay vì Pass by Value để giảm thiểu tiêu tốn bộ nhớ stack.

Struct Alignment Padding

    Phần này khá nhiều nội dung nên mình đã tổng hợp trong bài viết này!

Struct & Alignment Video Demo

>>>>>> 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.

Đăng nhận xét

Mới hơn Cũ hơn
//