Chuỗi C-style và ký tự null terminator
Vấn đề
Phần tiêu đề “Vấn đề”Một chuỗi ký tự C-style (char[]) không “biết” độ dài của chính nó - nó chỉ là một mảng ký tự, và quy ước là chuỗi phải kết thúc bằng ký tự đặc biệt '\0' (null terminator). Nếu thiếu ký tự này, mọi hàm xử lý chuỗi sẽ đọc tràn ra ngoài phạm vi mảng.
#include <cstring>#include <iostream>
int main() { char name[5] = {'A', 'l', 'i', 'c', 'e'}; // ĐỦ 5 ký tự, KHÔNG CÒN CHỖ cho '\0'!
std::cout << name << std::endl; // NGUY HIỂM! Đọc tràn ra ngoài mảng để tìm '\0' std::cout << strlen(name) << std::endl; // Kết quả không xác định
return 0;}Kích thước thực tế cần thiết
Phần tiêu đề “Kích thước thực tế cần thiết”char correct_name[6] = {'A', 'l', 'i', 'c', 'e', '\0'}; // Đủ chỗ cho null terminator
char easier[] = "Alice"; // Cách viết dễ hơn - trình biên dịch TỰ ĐỘNG thêm '\0'std::cout << sizeof(easier) << std::endl; // 6, KHÔNG PHẢI 5! (5 ký tự + 1 null terminator)strlen vs sizeof với chuỗi C-style
Phần tiêu đề “strlen vs sizeof với chuỗi C-style”#include <cstring>#include <iostream>
int main() { char text[20] = "Hello";
std::cout << sizeof(text) << std::endl; // 20 - kích thước TOÀN BỘ mảng đã khai báo std::cout << strlen(text) << std::endl; // 5 - số ký tự THỰC SỰ, tính đến trước '\0'
return 0;}Nhầm lẫn giữa hai hàm này là lỗi rất phổ biến khi làm việc với chuỗi C-style: sizeof là thông tin biết được lúc biên dịch (kích thước mảng), còn strlen phải quét từng ký tự lúc chạy chương trình cho đến khi gặp '\0'.
Nguy hiểm khi copy chuỗi không đủ chỗ
Phần tiêu đề “Nguy hiểm khi copy chuỗi không đủ chỗ”#include <cstring>
char destination[5];const char* source = "Hello, World!"; // Dài hơn destination rất nhiều
strcpy(destination, source); // TRÀN BỘ ĐỆM (buffer overflow)! Ghi đè ra ngoài mảng destinationĐây là một trong những lỗi bảo mật kinh điển nhất trong lịch sử lập trình C/C++ (buffer overflow) - nhiều lỗ hổng bảo mật nghiêm trọng trong phần mềm thực tế bắt nguồn từ chính lỗi này.
Giải pháp: ưu tiên std::string
Phần tiêu đề “Giải pháp: ưu tiên std::string”#include <string>#include <iostream>
int main() { std::string name = "Alice"; // Tự quản lý bộ nhớ, không cần lo về null terminator
std::string destination; std::string source = "Hello, World! Bat ke do dai bao nhieu"; destination = source; // Luôn an toàn - std::string tự cấp phát đủ chỗ
std::cout << destination << std::endl;
return 0;}std::string xử lý toàn bộ vấn đề cấp phát bộ nhớ và null terminator một cách tự động và an toàn - đây là lý do nó được khuyến khích dùng thay cho char[] trong hầu hết trường hợp.
Tóm tắt
Phần tiêu đề “Tóm tắt”Chuỗi C-style cần một ký tự
'\0'(null terminator) ở cuối để các hàm xử lý chuỗi biết đâu là điểm kết thúc.
sizeofcho biết kích thước mảng đã khai báo;strlenđếm số ký tự thực sự đến trước'\0'- Copy chuỗi vào một mảng không đủ chỗ gây tràn bộ đệm (buffer overflow) - lỗi bảo mật kinh điển
- Ưu tiên
std::stringđể tránh toàn bộ lớp lỗi này