C/C++

Structure in C

Xin chào, trong bài viết này chúng ta sẽ tìm hiểu về kiểu dữ liệu tự tạo struct của ngôn ngữ C.


WHAT IS STRUCTURE?

Trong ngôn ngữ C, structure là một kiểu dữ liệu do người dùng tạo ra để chứa nhiều loại dữ liệu khác nhau (thay vì chỉ một loại dữ liệu như array). Structure cũng như các kiểu dữ liệu khác, đều có thể được đưa vào mảng hoặc truyền vào function.

Structure được ứng dụng để đóng gói các dữ liệu riêng lẻ có cùng quan hệ vào một thực thể duy nhất. Ví dụ dễ gặp nhất là khi cần ghi lại thông tin cá nhân của người dùng, trích xuất các trường dữ liệu từ hệ thống, hoặc khi cần lập trình MCU để điều khiển các thiết bị ngoại vi.


CREATE A NEW STRUCTURE

Để tạo một structure mới chúng ta sử dụng keywork struct  typedef (tùy chọn).

Example 1: Tạo một structure mới (không sử dụng typedef)

struct ID {
    char *name;
    char *nationality;
    char age;
};

Với cách này, mỗi khi cần khai báo một biến có kiểu dữ liệu structure ID, chúng ta cần phải viết:

struct ID variableName;

Example 2: Tạo một structure mới (có sử dụng typedef)

typedef struct {
    char *name;
    char *nationality;
    char age;
} ID;

Bằng cách sử dụng typedef, kiểu dữ liệu structure vừa được tạo sẽ có tên là ID. Để khai báo một biến có kiểu dữ liệu này, chúng ta chỉ cần viết:

ID variableName;

Vì vậy, để đơn giản, thuận tiện và dễ hiểu hơn, chúng ta nên sử dụng typedef khi tạo một structure mới.
Chú ý: Khi tạo một structure mới, chúng ta chỉ khai báo các trường dữ liệu có trong nó chứ không khởi tạo các giá trị, vì lúc này nó chưa hề được cấp phát bộ nhớ (bộ nhớ chỉ được cấp phát khi có một biến thuộc kiểu dữ liệu này được tạo).


DECLARE AND DEFINE STRUCTURE VARIABLES

Sau khi đã tạo được một structure mới, chúng ta có thể khai báo và định nghĩa các biến có kiểu dữ liệu mới này như sau:

Example 3: Khai báo biến structure

#include "stdio.h"

typedef struct {
    char *name;
    char *nationality;
    char age;
} ID;

int main() {
    ID person_1 = {"Messi", "Argentinian", 30};
    ID person_2 = {.nationality = "Portuguese", .age = 32, .name = "Ronaldo"};
    ID person_3;
    person_3.age = 26;
    person_3.name = "Neymar";
    person_3.nationality = "Brazilian";

    printf("Name: %s\nAge: %d\nNationality: %s\n", person_1.name, person_1.age, person_1.nationality);
    return 0;
}

Giải thích:
– Ở dòng 10: khai báo và khởi tạo các thành phần của biến person_1 bằng cách đưa tất cả các trường dữ liệu vào ngoặc nhọn theo thứ tự đúng với đã khai báo trong struct ID;
– Ở dòng 11: khai báo và khởi tạo các thành phần của biến person_2 theo thứ tự tùy ý bằng cách đưa tất cả dữ liệu vào ngoặc nhọn kèm them tên trường dữ liệu đã khai báo trong struct ID;
– Dòng 12-15: khai báo và khởi tạo riêng từng thành phần của biến person_3;
– Dòng 15: in ra các trường dữ liệu của biến person_1;

Output:
Name: Messi
Age: 30
Nationality: Argentinian


STRUCTURE VARIABLE AS FUNCTION ARGUMENT

Tương tự với các kiểu dữ liệu khác, structure có thể được truyền vào function ở dạng đối số bằng bản sao hoặc pointer của nó.

Example 4: Truyền bản sao của structure vào function

#include "stdio.h"

typedef struct {
    char *name;
    char *nationality;
    char age;
} ID;

ID modifyByCopy(ID);

int main( ) {
    ID person_1 = {"Messi", "Argentinian", 30};
    ID person_2 = {.nationality = "Portuguese", .age = 32, .name = "Ronaldo"};
    ID person_3;
    person_3.age = 26;
    person_3.name = "Neymar";
    person_3.nationality = "Brazilian";

    printf("modifyByCopy - Current infomation:\n-name: %s\n-age: %d\n-nationality: %s\n", person_3.name, person_3.age, person_3.nationality);
    person_3 = modifyByCopy(person_3);
    printf("modifyByCopy - After modifying:\n-name: %s\n-age: %d\n-nationality: %s\n", person_3.name, person_3.age, person_3.nationality);

    return 0;
}

ID modifyByCopy(ID id) {
    id.name = "Cong Phuong";
    id.age = 23;
    id.nationality = "Vietnamese";

    return id;
}

Giải thích:
– Vì chỉ có bản sao của biến person_3 được truyền vào function modifyByCopy() nên sau khi chỉnh sửa nội dung các trường dữ liệu, chúng ta cần gán biến person_3 với kết quả trả về từ function modifyByCopy() để nhận được kết quả mong muốn.

Output:
modifyByCopy – Current infomation:
-name: Neymar
-age: 26
-nationality: Brazilian
modifyByCopy – After modifying:
-name: Cong Phuong
-age: 23
-nationality: Vietnamese

Chú ý: đối với một structure lớn, tốt hơn cả là sử dụng pointer thay vì copy toàn bộ structure vào function.

Example 5: Truyền pointer của structure vào function

#include "stdio.h"

typedef struct {
    char *name;
    char *nationality;
    char age;
} ID;

void modifyByPointer(ID*);

int main( ) {
    ID person_1 = {"Messi", "Argentinian", 30};
    ID person_2 = {.nationality = "Portuguese", .age = 32, .name = "Ronaldo"};
    ID person_3;
    person_3.age = 26;
    person_3.name = "Neymar";
    person_3.nationality = "Brazilian";

    printf("modifyByPointer - Current infomation:\n-name: %s\n-age: %d\n-nationality: %s\n", person_2.name, person_2.age, person_2.nationality);
    modifyByPointer(&person_2);
    printf("modifyByPointer - After modifying:\n-name: %s\n-age: %d\n-nationality: %s\n", person_2.name, person_2.age, person_2.nationality);

    return 0;
}

void modifyByPointer(ID *p) {
    p->name = "Cong Phuong";
    p->age = 23;
    p->nationality = "Vietnamese";
}

Giải thích:
– khi sử dụng pointer đến structure, chúng ta sẽ truy cập đến các trường dữ liệu của nó bằng toán tử -> thay vì toán tử . như trước đây.

Output:
modifyByCopy – Current infomation:
-name: Ronaldo
-age: 32
-nationality: Portuguese
modifyByCopy – After modifying:
-name: Cong Phuong
-age: 23
-nationality: Vietnamese


PADDING AND DATA PACKING IN STRUCTURE

Để dễ dàng trong xử lý, compiler sẽ nhóm các trường dữ liệu của structure (tương tự như các kiểu dữ liệu khác ) thành các block 4 bytes (đối với 32-bit processor). Như vậy, giá trị kiểu int sẽ chiếm trọn một block 4, giá trị kiểu double sẽ chiếm 2 block.

32-bit processor có thể đọc 32 bits dữ liệu (4 bytes) trong một chu kỳ. Do đó, để đọc các giá trị char, short, int từ bộ nhớ nó chỉ mất 1 chu kỳ, riêng đối với giá trị kiểu double sẽ cần 2 chu kỳ.

Compiler sẽ quy định alignment requirement cho từng structure dựa trên kiểu dữ liệu lớn nhất có trong structure đó, char = 1, short = 2, int = 4, double = 8 và pointer = 4.

Trở lại ví dụ của chúng ta:

Example 6:

#include "stdio.h"

typedef struct {
    char *name;
    char *nationality;
    char age;
} ID;

typedef struct {
    char *name;
    char *nationality;
    char age;
} __attribute__((__packed__)) newID;

int main() {
    printf("Size of structure ID: %d\n, sizeof(ID));
    printf("Size of structure newID: %d\n, sizeof(newID));
    return 0;
}

Output:
Size of structure ID: 12
Size of structure newID: 9

Giải thích:
– nhìn vào các trường dữ liệu của structure ID chúng ta có thể tính được kích thước của struct ID là: 4+4+1=9 bytes. Tuy nhiên, do kiểu char* có kích thước 4 bytes nên compiler đã nhóm các trường dữ liệu của struct ID vào 3 blocks, tương đương 12 bytes như kết quả chúng ta nhận được;
– Ở dòng 13, để ngăn compiler tự động nhóm các trường dữ liệu vào các block, chúng ta sử dụng thuộc tính __attribute__((__packed__)) khi tạo structure mới. Lúc này structure newID sẽ có kích thước đúng với tổng kích thước các trường dữ liệu của nó.

Thông thường chúng ta sẽ không cần thay đổi thuộc tính này của structure. Tuy nhiên, đôi khi việc thay đổi thuộc tính padding của structure là bắt buộc, ví dụ như khi đọc dữ liệu từ các header của các định dạng file như BMP và JPEG.


SUMMARY

Như vậy chúng ta đã tìm hiểu khá đầy đủ về kiểu dữ liệu structure trong ngôn ngữ C, bao gồm cách tạo structure, khai báo, khởi tạo, pointer đến structure và structure padding. Cảm ơn các bạn đã theo dõi bài viết.

Thân ái và quyết thắng.

Reference:
[1] The C programming language. Chapter 6 – Structures.
[2] Tutorialspoint – Structure in C.
[3] Geeksforgeeks – Structure in C.
[4] Structure Member Alignment, Padding and Data Packing.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s