Bài 17: Cấp phát động bộ nhớ với malloc, calloc và realloc trong C

cấp phát động bộ nhớ với calloc, malloc và realloc

Trong bài hôm nay chúng ta sẽ học cấp phát động bộ nhớ với malloc, calloc và realloc, Đây là các hàm giúp các bạn cấp phát và giải phóng bộ nhớ khi làm việc với con trỏ trong ngôn ngữ C.

Bài 17 trong Serie Học lập trình C từ A tới Z

Cấp phát bộ nhớ là gì? Tại sao cần cấp phát động bộ nhớ

Chúng ta đã biết, mỗi khi tạo ra một biến nào đó, trình biên dịch sẽ đưa ra 1 địa chỉ để lưu giữ biến đó. Khi chúng ta sử dụng biến có thể truy cập bằng tên biến hoặc con trỏ.
Việc cấp phát như vậy gọi là cấp phát tĩnh. 

Khi cấp phát tĩnh, ô nhớ đó sẽ tồn tại từ khi chương trình hoạt động tới khi chương trình kết thucs

Giả sử bạn phải khai báo 1 mảng mà chưa rõ phải sử dụng kích thước là bao nhiêu. Vậy thì nếu cấp phát tĩnh bộ nhớ cho mảng đó sẽ xảy ra 2 vấn đề:

  • Thiếu kích thước dẫn tới lưu thiếu dữ liệu
  • Thừa kích thước dẫn tới lãng phí bộ nhớ

Vậy nên chúng ta phải sử dụng cấp phát động trong trường hợp này.

Cấp phát động bộ nhớ chính là việc cấp phát/giải phóng, thay đổi kích thước bộ nhớ một cách linh hoạt. Giúp chúng ta điều khiển được việc sử dụng bộ nhớ của chương trình.
Thế nhưng cấp phát động không tốt (không giải phóng ô nhớ) sẽ khiến chương trình gặp lỗi tràn bộ nhớ Memory leak, vậy nên hãy cẩn thận khi sử dụng nhé.

Cấp phát động và cấp phát tĩnh

Vậy cấp phát tĩnh và cấp phát động giống và khác nhau như thế nào

Cấp phát bộ nhớ tĩnh Cấp phát bộ nhớ động
Bộ nhớ được cấp phát trước khi chạy chương trình (trong quá trình biên dịch) Bộ nhớ được cấp phát trong quá trình chạy chương trình.
Không thể cấp phát hay phân bổ lại bộ nhớ trong khi chạy chương trình Cho phép quản lý, phân bổ hay giải phóng bộ nhớ trong khi chạy chương trình
Vùng nhớ được cấp phát và tồn tại cho đến khi kết thúc chương trình Chỉ cấp phát vùng nhớ khi cần sử dụng tới
Chương trình chạy nhanh hơn so với cấp phát động Chương trình chạy chậm hơn so với cấp phát tĩnh
Tốn nhiều không gian bộ nhớ hơn Tiết kiệm được không gian bộ nhớ sử dụng

Để cấp phát vùng nhớ động cho biến con trỏ trong ngôn ngữ C, bạn có thể sử dụng hàm malloc() hoặc hàm calloc(). Sử dụng hàm free() để giải phóng bộ nhớ đã cấp phát khi không cần sử dụng, sử dụng realloc() để thay đổi (phân bổ lại) kích thước bộ nhớ đã cấp phát trong khi chạy chương trình.

cap phat dong

Sử dụng hàm free

Việc cấp phát bộ nhớ động trong C dù sử dụng malloc() hay calloc() thì chúng cũng đều không thể tự giải phóng bộ nhớ. Bạn cần sử dụng hàm free() để giải phóng vùng nhớ.

Cú pháp:

free(ptr);//ptr là con trỏ

Lệnh này sẽ giải phóng vùng nhớ mà con trỏ ptr đã được cấp phát. Giải phóng ở đây có nghĩa là trả lại vùng nhớ đó cho hệ điều hành và hệ điều hành có thể sử dụng vùng nhớ đó vào việc khác nếu cần.

Sử dụng hàm malloc

Từ malloc là đại diện cho cụm từ memory allocation (dịch: cấp phát bộ nhớ). Khi khai báo kiểu malloc, các ô nhớ sẽ được giữ nguyên bộ nhớ ban đầu (draf data hay dữ liệu rác).

Hàm malloc được định nghĩa như sau

void* ICACHE_RAM_ATTR malloc(size_t size)

Kiểu trả về là con trỏ void (không có giá trị), tham số truyền vào là  size tính bằng byte

VD:

int *pt = malloc(10* sizeof(int));

Cấp phát con trỏ kiểu int, với kích thước là 10*4 = 40 byte. Vì 1 int có kích thước 4 byte.

Vậy thì khai báo như vậy con trỏ pt sẽ được cấp cho 40byte tương ứng với 10 phần tử trong mảng.

#include <stdio.h>
int main()
{
   int *pt = malloc(10* sizeof(int));
   pt[0] = 1;   
   pt[1] = 2; 
   pt[2] = 3; 
   pt[3] = 4; 
   pt[4] = 5; 
   pt[5] = 6; 
   pt[6] = 7; 
   pt[7] = 8; 
   pt[8] = 9; 
   pt[9] = 10;
   free(pt);
}

Vì hàm malloc trả về dạng void, vậy nên chúng ta nên ép kiểu cho nó trở về đúng kiểu con trỏ mà chúng ta sử dụng, như sau:

Cú pháp:

ptr = (castType*) malloc(size);

VD:

int *ptr;

ptr = (int*) malloc(100 * sizeof(int));
VD:
#include <stdio.h>
#include <stdlib.h> // Thư viện này để cấp phát bộ nhớ động

int main()
{
    int n, i, *ptr, sum = 0;
    printf("Nhap so luong phan tu: ");
    scanf("%d", &n);
    ptr = (int *)malloc(n * sizeof(int));
 
    // Nếu không thể cấp phát, 
    // hàm malloc sẽ trả về con trỏ NULL
    if (ptr == NULL)
    {
        printf("Co loi! khong the cap phat bo nho.");
        exit(0);
    }
    for (i = 0; i < n; ++i)
    {
        printf("Nhap gia tri %d: ", i+1); 
        scanf("%d", ptr + i);
        printf("\n"); 
        sum += *(ptr + i);
    }
    printf("Tong = %d", sum);
 
    // Giải phóng vùng nhớ cho con trỏ
    free(ptr);
    return 0;
}

Kết quả

Nhap so luong phan tu: 4
Nhap gia tri 1: 3

Nhap gia tri 2: 1

Nhap gia tri 3: 2

Nhap gia tri 4: 3

Tong = 9

 

Sử dụng hàm calloc

Hàm calloc() thực hiện cấp phát bộ nhớ và khởi tạo tất cả các ô nhớ có giá trị bằng 0. Vì thế nên hàm calloc sẽ cần thời gian thực thi lâu hợn malloc()

Việc cấp phát và sử dụng cũng khá giống malloc() nhưng thay vì kích cỡ cố định là byte, calloc sẽ cho chúng ta thêm 1 tham số truyền vào là size của 1 dữ liệu.

Hàm calloc được định nghĩa như sau:

void* ICACHE_RAM_ATTR calloc(size_t count, size_t size)

Giá trị trả về là con trỏ void, tham số truyền vào là số lượng phần tử và kích thước của phần tử.

Cú pháp:

ptr = (castType*)calloc(num, size);

Với num là số lượng phần tử, size là kích thước mỗi phần tử.

VD:

char *pt = calloc(100, sizeof(char));

hoặc

int *ptr;

ptr = (int*) calloc(100, sizeof(int));

VD: Cấp phát bộ nhớ và ghi dữ liệu vào mảng con trỏ

#include <stdio.h>

int main()
{
   int *pt = calloc(10, sizeof(int));
   pt[0] = 1;   
   pt[1] = 2; 
   pt[2] = 3; 
   pt[3] = 4; 
   pt[4] = 5; 
   pt[5] = 6; 
   pt[6] = 7; 
   pt[7] = 8; 
   pt[8] = 9; 
   pt[9] = 10;
   free(pt);
}

Cấp phát bộ nhớ có 10 phần tử kiểu int, sau đó ghi vào theo kiểu truy cập phần tử mảng.

VD:

#include <stdio.h>
#include <stdlib.h> // Thư viện này để cấp phát bộ nhớ động

int main()
{
    int n, i, *ptr, sum = 0;
    printf("Nhap so luong phan tu: ");
    scanf("%d", &n);
    ptr = (int *)calloc(n, sizeof(int));
 
    // Nếu không thể cấp phát, 
    // hàm malloc sẽ trả về con trỏ NULL
    if (ptr == NULL)
    {
        printf("Co loi! khong the cap phat bo nho.");
        exit(0);
    }
    for (i = 0; i < n; ++i)
    {
        printf("Nhap gia tri %d: ", i+1); 
        scanf("%d", ptr + i);
        printf("\n"); 
        sum += *(ptr + i);
    }
    printf("Tong = %d", sum);
 
    // Giải phóng vùng nhớ cho con trỏ
    free(ptr);
    return 0;
}

Tương tự là nhập số phần tử và tính tổng các phần tử mà thôi

Sử dụng hàm realloc

Hàm realloc được sử dụng khi bạn đã cấp phát động bộ nhớ nhưng thấy thiếu, cần cấp phát thêm bộ nhớ để sử dụng.

Cú pháp:

void* ICACHE_RAM_ATTR realloc(void* ptr, size_t size)

Với ptr là con trỏ đã được cấp phát động, size là kích cỡ thêm vào tính theo byte.

Khai báo:

ptr = realloc(ptr, n);

VD: Nhập thêm số lượng phẩn tử vào tổng đã có

#include <stdio.h>
#include <stdlib.h> // Thư viện này để cấp phát bộ nhớ động

int main()
{
    int n, i, *ptr, sum = 0;
    printf("Nhap so luong phan tu: ");
    scanf("%d", &n);
    ptr = (int *)calloc(n, sizeof(int));
 
    // Nếu không thể cấp phát, 
    // hàm malloc sẽ trả về con trỏ NULL
    if (ptr == NULL)
    {
        printf("Co loi! khong the cap phat bo nho.");
        exit(0);
    }
    for (i = 0; i < n; ++i)
    {
        printf("Nhap gia tri %d: ", i+1); 
        scanf("%d", ptr + i);
        printf("\n"); 
        sum += *(ptr + i);
    }
    printf("Tong = %d \n", sum);

    printf("Them phan tu: ");
    int m;
    scanf("%d", &m);
    ptr = (int*)realloc(ptr, m* sizeof(int));
    for (i = n; i < n+m; ++i)
    {
        printf("Nhap gia tri %d: ", i+1); 
        scanf("%d", ptr + i);
        printf("\n"); 
        sum += *(ptr + i);
    }

    printf("Tong moi = %d", sum);
    // Giải phóng vùng nhớ cho con trỏ
    free(ptr);
    return 0;
}

Kết quả

Nhap so luong phan tu: 3
Nhap gia tri 1: 1

Nhap gia tri 2: 2

Nhap gia tri 3: 3

Tong = 6
Them so luong phan tu: 2
Nhap gia tri 4: 3

Nhap gia tri 5: 5

Tong moi = 14

Kết 

Việc sử dụng phương thức calloc sẽ an toàn hơn malloc trong lập trình vì vùng nhớ cấp phát động sẽ được gán giá trị bằng 0 thay vì giá trị rác như calloc. Tuy nhiên việc thêm 1 bước gán giá trị các ô nhớ bằng 0 này cũng sẽ khiến nó bị chậm hơn so với malloc do phải thực hiện thêm thao tác.

Sử dụng realloc và free một cách linh hoạt sẽ giúp các bạn điều khiển sự tăng giảm của bộ nhớ 1 cách dễ dàng

Nếu thấy có ích hãy chia sẻ bài viết và tham gia nhóm Nghiện Lập Trình để giao lưu và học hỏi nhé

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *