Bài tập

Làm việc với Cấp phát bộ nhớ trong lập trình C/C++

Huy Erick

Trong lập trình C/C++, việc làm việc với bộ nhớ là một khía cạnh quan trọng mà các lập trình viên cần phải hiểu rõ. Cấp phát và giải phóng bộ nhớ động là hai...

Trong lập trình C/C++, việc làm việc với bộ nhớ là một khía cạnh quan trọng mà các lập trình viên cần phải hiểu rõ. Cấp phát và giải phóng bộ nhớ động là hai hoạt động quan trọng trong quá trình lập trình. Bài viết này sẽ giúp bạn hiểu rõ hơn về cơ chế lưu trữ trong bộ nhớ ảo và cách sử dụng cấp phát động và giải phóng bộ nhớ trong lập trình C/C++.

Tổ chức lưu trữ trong bộ nhớ ảo

Trong máy tính, bộ nhớ ảo được tổ chức thành các phân vùng khác nhau như sau:

Code Segment

Code segment (text segment) là nơi lưu trữ mã máy dạng nhị phân. Đây là phần của bộ nhớ chứa mã máy nhị phân tương ứng với các chương trình được viết bằng ngôn ngữ tự nhiên. Phân vùng này chỉ chịu sự chi phối của hệ điều hành và không thể can thiệp trực tiếp từ phía người lập trình.

Data Segment

Data segment (initialized data segment) là nơi chứa các biến kiểu static và biến toàn cục (global variable). Ở đây, các biến đã được khởi tạo giá trị cụ thể.

BSS Segment

BSS segment (uninitialized data segment) cũng được sử dụng để lưu trữ các biến kiểu static và biến toàn cục, nhưng chưa được khởi tạo giá trị cụ thể.

Heap

Heap là vùng nhớ không do CPU quản lý, do đó người lập trình phải tự quản lý vùng nhớ này. Vùng nhớ heap được sử dụng khi cấp phát bộ nhớ động cho con trỏ.

Stack

Stack (đôi khi được gọi là Call Stack) là vùng nhớ do CPU quản lý và không thể can thiệp từ phía người lập trình. Vùng nhớ stack được sử dụng để cấp phát bộ nhớ cho tham số của các hàm và biến cục bộ.

Tại sao cần cấp phát vùng nhớ?

Cấp phát bộ nhớ cho con trỏ là một bước quan trọng trong việc lưu trữ dữ liệu. Nếu không cấp phát bộ nhớ cho con trỏ, chúng ta sẽ không thể nhập dữ liệu trực tiếp cho con trỏ được. Một cách để hiểu điều này là so sánh việc xây nhà với việc cấp phát bộ nhớ. Khi xây nhà, chúng ta cần có đất để xây. Tương tự, khi chúng ta cần sử dụng con trỏ, cần phải cấp phát bộ nhớ để có chỗ để lưu trữ dữ liệu.

Trong lập trình C++, khi chúng ta khai báo con trỏ, nó sẽ trỏ đến ô nhớ của biến khác, do đó không cần phải cấp phát bộ nhớ. Tuy nhiên, khi sử dụng cấp phát bộ nhớ động, chúng ta cần quản lý vùng nhớ heap và giải phóng nó sau khi sử dụng xong.

Sử dụng cấp phát bộ nhớ động trong lập trình C/C++

Trong lập trình C, chúng ta có ba cách để cấp phát vùng nhớ động:

1. Malloc

Hàm malloc được sử dụng để cấp phát vùng nhớ động. Cú pháp sử dụng như sau:

void* malloc (size_t size);

Trong đó, size là kích thước cần cấp phát.

2. Calloc

Hàm calloc được sử dụng để cấp phát vùng nhớ động và khởi tạo giá trị mặc định cho các phần tử. Cú pháp sử dụng như sau:

void* calloc (size_t num, size_t size);

Hàm này cấp phát 1 vùng nhớ chứa đủ num phần tử, mỗi phần tử có kích thước là size, và các giá trị của các phần tử sẽ được gán mặc định là 0.

3. Realloc

Hàm realloc được sử dụng để cấp phát hoặc thay đổi kích thước vùng nhớ động đã cấp phát trước đó. Cú pháp sử dụng như sau:

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

Hàm này có hai trường hợp sử dụng:

  • Đối với vùng nhớ chưa được khởi tạo, realloc sẽ tạo mới vùng nhớ cho nó.
  • Đối với vùng nhớ đã có sẵn, realloc sẽ gia tăng hoặc giảm bớt ô nhớ trên vùng nhớ đó.

Sau đây là một ví dụ minh họa về cách sử dụng cấp phát bộ nhớ động trong lập trình C:

Sử dụng mảng trong lập trình C bằng malloc:

int* arr; int n;  printf("Nhap kich thuoc mang: "); scanf("%d", &n);  arr = (int*) malloc(n * sizeof(int));  if (arr == NULL) {     printf("Khong the cap phat bo nho");     return 1; }  for (int i = 0; i  n; i++) {     printf("Nhap phan tu thu %d: ", i);     scanf("%d", &arr[i]); }  for (int i = 0; i  n; i++) {     printf("Phan tu thu %d: %d\n", i, arr[i]); }  free(arr);

Ví dụ sử dụng calloc trong lập trình C:

int* arr; int n;  printf("Nhap kich thuoc mang: "); scanf("%d", &n);  arr = (int*) calloc(n, sizeof(int));  if (arr == NULL) {     printf("Khong the cap phat bo nho");     return 1; }  for (int i = 0; i  n; i++) {     printf("Nhap phan tu thu %d: ", i);     scanf("%d", &arr[i]); }  for (int i = 0; i  n; i++) {     printf("Phan tu thu %d: %d\n", i, arr[i]); }  free(arr);

Ví dụ sử dụng realloc trong lập trình C:

int* arr; int n;  printf("Nhap kich thuoc ban dau cua mang: "); scanf("%d", &n);  arr = (int*) malloc(n * sizeof(int));  if (arr == NULL) {     printf("Khong the cap phat bo nho");     return 1; }  for (int i = 0; i  n; i++) {     printf("Nhap phan tu thu %d: ", i);     scanf("%d", &arr[i]); }  int m;  printf("Nhap kich thuoc moi cua mang: "); scanf("%d", &m);  arr = (int*) realloc(arr, m * sizeof(int));  if (arr == NULL) {     printf("Khong the cap phat bo nho");     return 1; }  for (int i = 0; i  m; i++) {     printf("Phan tu thu %d: %d\n", i, arr[i]); }  free(arr);

Tại sao cần giải phóng bộ nhớ?

Bộ nhớ được cấp phát cho con trỏ thuộc vùng nhớ heap, mà không được quản lý bởi CPU. Nếu không giải phóng bộ nhớ sau khi sử dụng, những ô nhớ đó sẽ không bao giờ được sử dụng lại, dẫn đến tình trạng tràn bộ nhớ (memory leak) và có thể làm máy tính hoạt động không ổn định. Dù máy tính ngày nay có cấu hình mạnh hơn nên tình trạng này ít xảy ra, nhưng việc tiết kiệm bộ nhớ vẫn là điều cần thiết. Khi giải phóng bộ nhớ, giá trị của con trỏ không bị mất nếu không có tiến trình khác chiếm hữu ô nhớ đó.

Trong lập trình C, chúng ta sử dụng hàm free(void*) để giải phóng vùng nhớ đã cấp phát.

Sử dụng cấp phát và giải phóng bộ nhớ trong lập trình C++

Trong lập trình C++, chúng ta sử dụng từ khóa new để cấp phát vùng nhớ và từ khóa delete để giải phóng vùng nhớ đã cấp phát. Dưới đây là một ví dụ minh họa về cách sử dụng cấp phát động cho mảng trong lập trình C++:

int* arr; int n;  cout  "Nhap kich thuoc mang: "; cin >> n;  arr = new int[n];  for (int i = 0; i  n; i++) {     cout  "Nhap phan tu thu "  i  ": ";     cin >> arr[i]; }  for (int i = 0; i  n; i++) {     cout  "Phan tu thu "  i  ": "  arr[i]  endl; }  delete[] arr;

Kết quả chạy chương trình như sau:

Hy vọng qua bài viết này, bạn đã hiểu rõ hơn về cơ chế lưu trữ trên bộ nhớ của máy tính và cách thức cấp phát vùng nhớ động và giải phóng vùng nhớ trong lập trình C và lập trình C++.

1