1.Vấn đề về cấp phát bộ nhớ tĩnh

Nhắc đến bộ nhớ tĩnh là ta nghĩ đến mảng, bao gồm những phần tử nằm ở những địa chỉ liền kề nhau. Để có một mảng ta cần phải chỉ ra kích thước của mảng đó.

Giả sử trong máy của bạn chỉ còn những địa chỉ vùng nhớ nằm xen kẽ hoặc cách xa nhau, không thể tập hợp lại thành những địa chỉ liền kề như yêu cầu của mảng. Như vậy sẽ tồn tại một vấn đề đó là bộ nhớ không đủ để cấp phát cho mảng mặc dù vẫn tồn tại nhiều địa chỉ vùng nhớ nằm xen kẽ cách xa nhau và nếu chúng được tập hợp lại liền kề nhau thì vẫn hoàn toàn đủ để cấp phát cho mảng.

Để dễ hình dung hơn tôi có một ví dụ sau:

Ở một thành phố A bao gồm nhiều mảnh đất khác nhau, một nhà bất động sản cần mua mảnh đất 500m2 , tuy nhiên một mảnh đất có diện tích 500m2 là không có trong thành phố A, mà thành phố A chỉ có rải rác 5 mảnh đất nằm ở 5 vị trí khác nhau mỗi mảnh có diện tích 100m2. Và như vậy thì nhà bất động sản kia không thể mua được mảnh đất có diện tích 500m2 ở thành phố A.

Điều đó cũng tương tự như vấn đề của mảng khi cấp phát bộ nhớ tĩnh (không đủ bộ nhớ). Như vậy cần một giải pháp để tận dụng được 5 mảnh đất có diện tích 100m2 ở 5 vị trí khác nhau trong thành phố A kia hay tận dụng những địa chỉ ô nhớ xen kẽ hoặc cách xa nhau kia để tạo ra mảng.

2.Cấp phát bộ nhớ động

2.1 Cấp phát bộ nhớ động là gì?

Cấp phát bộ nhớ động sẽ tận dụng và tiết kiệm không giạn bộ nhớ, và giải quyết được vấn đề đã nêu ở phần trên về việc cấp phát tĩnh không đủ bộ nhớ. Nghĩa là ta cần dùng bao nhiêu thì cấp phát bây nhiêu không cần cấp phát một vùng nhớ rộng lớn như mảng yêu cầu. Sau khi cấp phát và sử dụng xong thì lập tức giải phóng vùng nhớ đó để tiết kiệm không gian nhớ.

2.2 Cấp phát bộ nhớ động bằng hàm malloc()

Hàm malloc (thuộc thư viện <stdlib.h>) là một hàm thường dùng để sử dụng trong việc cấp phát bộ nhớ.

Cú pháp:

a = (type*) malloc(size * typeof(type));

Trong đó:

  • a: là biến con trỏ
  • Type: là kiểu dữ liệu cần cấp phát
  • Size: là số lượng cần cấp phát
  • Typeof(type): là kích thước của kiểu dữ liệu cần cần phát

Sau khi việc cấp phát bộ nhớ và sử dụng hoàn thành, ta có thể giải phóng chúng bằng:

free(a);

3.Sử dụng hàm malloc cấp phát bộ nhớ động cho mảng một chiều

Ví dụ dưới đây tôi sử dụng con trỏ a và hàm malloc để cấp phát bộ nhớ động cho n (n do người dùng nhập từ bàn phím) phần tử trong mảng một chiều có kiểu int và có kích thước là 4 byte sau khi sử dụng xong sẽ dùng hàm free(a) để giải phóng vùng nhớ cho con trỏ a:

Chú ý: Hàm malloc thuộc thư viện stdlib.h nên ta cần #include <stdlib.h>

#include <stdio.h>
#include <stdlib.h>
int main()
{
    //khai bao n gia tri can cap phat
    int n;
    printf("Nhap n: ");
    scanf("%d",&n);
    //khai bao con tro a
    int *a;
    //cap phat bo nho bang con tro a
    a = (int*)malloc(n * sizeof(int));
    //giai phong vung nho con tro a
    free(a);
}

Sau khi cấp phát bộ nhớ động cho mảng trên thành công, tôi có thể nhập và xuất các giá trị như mảng bình thường (tuy nhiên ở đây có sử dụng con trỏ nên cần tuân theo quy tắc của con trỏ).

#include <stdio.h>
#include <stdlib.h>
int main()
{
    //khai bao n gia tri can cap phat
    int n;
    printf("Nhap n: ");
    scanf("%d",&n);
    //khai bao con tro a
    int *a;
    //cap phat bo nho bang con tro a
    a = (int*)malloc(n * sizeof(int));
    // nhap mang
    for (int i = 0; i < n; i++)
    {
        //nhan gia tri cua nguoi dung nhap vao thong qua scanf va dia chi (a + i)
        printf("a[%d] = ", i);
        scanf("%d", (a + i));
    }
    
    // xuat mang
    for (int i = 0; i < n; i++)
    {
        //hien thi ra gia tri bang toan tu *(a + i)
        printf(" %d \n", *(a + i));
    }
    //giai phong vung nho con tro a
    free(a);
}
Nhap n: 5

a[0] = 1

a[1] = 2

a[2] = 3

a[3] = 4

a[4] = 5

1

2

3

4

5

Viết lại chương trình trên một cách hoàn chỉnh:

#include <stdio.h>
#include <stdlib.h>
void nhap(int *a, int n){
    for (int i = 0; i < n; i++)
    {
        //nhan gia tri cua nguoi dung nhap vao thong qua scanf va dia chi (ptr + i)
        printf("a[%d] = ", i);
        scanf("%d", (a + i));
    }
}

void xuat(int *a, int n){
    for (int i = 0; i < n; i++)
    {
        //hien thi ra gia tri bang toan tu *(ptr + i)
        printf(" %d \n", *(a + i));
    }
}
int main()
{
    //khai bao n gia tri can cap phat
    int n;
    printf("Nhap n: ");
    scanf("%d",&n);
    //khai bao con tro a
    int *a;
    //cap phat bo nho bang con tro a
    a = (int*)malloc(n * sizeof(int));
    //nhap mang
    nhap(a,n);
    //xuat mang
    xuat(a,n);
    //giai phong vung nho con tro a
    free(a);
}
Nhap n: 5

a[0] = 1

a[1] = 2

a[2] = 3

a[3] = 4

a[4] = 5

1

2

3

4

5

4.Sử dụng hàm malloc cấp phát bộ nhớ động cho mảng hai chiều

Ví dụ dưới đây tôi sử dụng con trỏ a và hàm malloc để cấp phát bộ nhớ động cho mảng hai chiều có kích thước m x n (m và n do người dùng nhập từ bàn phím) phần tử trong mảng hai chiều có kiểu int và có kích thước là 4 byte sau khi sử dụng xong sẽ dùng hàm free(a) để giải phóng vùng nhớ cho con trỏ a:

Nếu như ở ví dụ trên ta sử dụng câu lệnh dưới đây để cấp phát cho mảng một chiều:

a = (int*)malloc(n * sizeof(int));

Thì đối với mảng hai chiều ta cần có thêm một kích thước m nữa trong mảng (vì mảng hai chiều có kích thước m x n). Vì thế câu lệnh trên cần được thay đổi như sau:

a = (int*)malloc(m * n * sizeof(int));

Chú ý: Hàm malloc thuộc thư viện stdlib.h nên ta cần #include <stdlib.h>

#include <stdio.h>
#include <stdlib.h>
int main()
{
    //khai bao m * n gia tri can cap phat
    int m, n;
    printf("Nhap m: ");
    scanf("%d",&m);
    printf("Nhap n: ");
    scanf("%d",&n);
    //khai bao con tro a
    int *a;
    //cap phat bo nho bang con tro a
    a = (int*)malloc(m * n * sizeof(int));
    //giai phong vung nho con tro a
    free(a);
}

Tương tự như cấp phát bộ nhớ động cho mảng một chiều, tôi cũng có thể nhập xuất giá trị vào cho mảng hai chiều vừa được cấp phát bộ nhớ động.

Hàm nhập:

void nhap(int *a, int m, int n){
    //duyet qua cac hang
    for(int i = 0; i < m; i++){
        //duyet qua cac cot
        for(int j = 0; j < n; j++){
            //nhap gia tri cho cac phan tu tai vi tri a[i][j] bang (a + n * i + j)
            printf("Nhap A[%d][%d]: ", i, j);
            scanf("%d", (a + n * i + j));
        }
    }
}

Hàm xuất:

void xuat(int *a, int m, int n){
    //duyet qua cac hang
    for(int i = 0; i < m; i++){
        //duyet qua cac cot
        for(int j = 0; j < n; j++){
            //hien thi gia tri cua phan tu tai vi tri a[i][j] bang *(a + n * i + j)
            printf("%d \t", *(a + n * i + j));
        }
        //xuong dong
        printf("\n");
    }
}

Viết lại chương trình một cách hoàn chỉnh:

#include <stdio.h>
#include <stdlib.h>
void nhap(int *a, int m, int n){
    //duyet qua cac hang
    for(int i = 0; i < m; i++){
        //duyet qua cac cot
        for(int j = 0; j < n; j++){
            //nhap gia tri cho cac phan tu tai vi tri a[i][j] bang (a + n * i + j)
            printf("Nhap A[%d][%d]: ", i, j);
            scanf("%d", (a + n * i + j));
        }
    }
}
void xuat(int *a, int m, int n){
    //duyet qua cac hang
    for(int i = 0; i < m; i++){
        //duyet qua cac cot
        for(int j = 0; j < n; j++){
            //hien thi gia tri cua phan tu tai vi tri a[i][j] bang *(a + n * i + j)
            printf("%d \t", *(a + n * i + j));
        }
        //xuong dong
        printf("\n");
    }
}
int main()
{
    //khai bao m * n gia tri can cap phat
    int m, n;
    printf("Nhap m: ");
    scanf("%d",&m);
    printf("Nhap n: ");
    scanf("%d",&n);
    //khai bao con tro a
    int *a;
    //cap phat bo nho bang con tro a
    a = (int*)malloc(m * n * sizeof(int));
    //goi ham nhap
    nhap(a,m,n);
    //goi ham xuat
    xuat(a,m,n);
    //giai phong vung nho con tro a
    free(a);
}
Nhap m: 3

Nhap n: 3

Nhap A[0][0]: 1

Nhap A[0][1]: 2

Nhap A[0][2]: 3

Nhap A[1][0]: 4

Nhap A[1][1]: 5

Nhap A[1][2]: 6

Nhap A[2][0]: 7

Nhap A[2][1]: 8

Nhap A[2][2]: 9

1 2 3

4 5 6

7 8 9

Chú ý: Vì có sử dụng con trỏ cho việc cấp phát bộ nhớ nên ta cần tuân theo quy tắc sau:

  1. (a + n * i + j) là việc lấy địa chỉ của phần tử hàng thứ i cột thứ j trong mảng hai chiều – việc này tương đương với &a[i][j] nếu ta không sử dụng con trỏ.
  2. *(a+ n * i + j) là việc lấy ra giá trị của phần tử hàng thứ i cột thứ j trong mảng hai chiều – việc này tương đương với a[i][j] nếu ta không sử dụng con trỏ.