Con trỏ và bộ nhớ

Sử dụng const đúng cách trong C, C++

Trong bài học này, bạn sẽ dùng từ khóa const để khai báo dữ liệu chỉ đọc và phân biệt các cách dùng const với con trỏ.

Giới thiệu

Từ khóa const cho phép một hàm cam kết sẽ không thay đổi dữ liệu được truyền vào. Khi có ai đó vô tình sửa dữ liệu được đánh dấu const, trình biên dịch sẽ báo lỗi ngay. Nhờ vậy, code an toàn hơn và người đọc cũng dễ hiểu hàm nào được phép thay đổi dữ liệu.

Những điểm quan trọng

  • Con trỏ chỉ được trỏ tới đối tượng còn tồn tại, đúng kiểu dữ liệu, hoặc mang giá trị null.
  • Mỗi vùng nhớ được cấp phát chỉ được giải phóng đúng một lần.
  • Thể hiện rõ hàm nào sở hữu vùng nhớ (ownership) và dữ liệu nào chỉ được đọc (const).

Code ví dụ bằng C và C++

C
Chạy code →
main.c
#include <stdio.h>

int sum(const int *values, size_t count) {
    int total = 0;
    for (size_t i = 0; i < count; ++i) total += values[i];
    return total;
}

int main(void) {
    const int values[] = {2, 4, 6};
    printf("%d\n", sum(values, 3));
    return 0;
}
Kết quả khi chạy
12
C++
Chạy code →
main.cpp
#include <array>
#include <iostream>

int sum(const std::array<int, 3>& values) {
    int total = 0;
    for (int value : values) total += value;
    return total;
}

int main() {
    const std::array values{2, 4, 6};
    std::cout << sum(values) << '\n';
}
Kết quả khi chạy
12

So sánh C và C++

Tham số const int *values cho biết hàm sum chỉ đọc các phần tử của mảng mà không sửa chúng. Bạn nên đọc khai báo từ tên biến ra ngoài: con trỏ đến dữ liệu const (const int *) bảo vệ giá trị được trỏ tới, còn con trỏ const (int * const) thì không thể trỏ sang địa chỉ khác. C++ còn có thêm hàm thành viên const, cam kết không thay đổi trạng thái của đối tượng.

C

Việc sở hữu và giải phóng vùng nhớ hoàn toàn do lập trình viên tự đảm bảo.

C++

Container và RAII tự động giải phóng tài nguyên khi đối tượng hết thời gian tồn tại.

Bài tập mở rộng

Hãy chạy cả hai phiên bản, sau đó thử thay đổi code để quan sát sự khác biệt về mức độ an toàn mà mỗi ngôn ngữ mang lại.

  • Thử với con trỏ null và trường hợp cấp phát bộ nhớ thất bại.
  • Chạy chương trình với AddressSanitizer và UndefinedBehaviorSanitizer để phát hiện lỗi bộ nhớ.
  • Ghi chú rõ con trỏ nào sở hữu vùng nhớ và con trỏ nào chỉ dùng để đọc.