Bạn đã bao giờ nghe về vấn đề "tràn số" trong lập trình chưa? Phạm vi giá trị các kiểu dữ liệu trong ngôn ngữ lập trình là một khái niệm quan trọng mà các lập trình viên nên hiểu rõ. Trong bài viết này, chúng ta sẽ tìm hiểu về vấn đề này và cách tránh bị tràn số trong lập trình C/C++.
Lỗi tràn số trong lập trình
Tràn số xảy ra khi một biến được gán giá trị nằm ngoài phạm vi của kiểu dữ liệu của nó. Đây là một lỗi rất phổ biến đối với những người mới học lập trình. Hãy xem hình minh họa dưới đây để hiểu rõ hơn về lỗi này.
Để giúp bạn hiểu rõ hơn về hiện tượng tràn số, hãy xem ví dụ mã nguồn C++ sau đây với kiểu dữ liệu char. Một số bạn có thể đã biết rằng char có kích thước 1 byte = 8 bits, tức là có thể biểu diễn 28 giá trị khác nhau. Chúng ta hãy chia đôi miền âm và dương. Tức là từ -27 đến 27 - 1, bởi vì phía bên nửa dương cần biểu diễn cả số 0.
Ví dụ về lỗi tràn số
#include using namespace std; int main(){ char s = 97; cout (int)s '\n'; cout "Kiểm tra char overflow c++\n"; for(char i = 127; i = 150; ++i){ cout (int)i '\n'; } }
Các bạn nghĩ chương trình này sẽ in ra gì? Vòng lặp này sẽ lặp bao nhiêu lần?
char s = 97; cout (int)s '\n'; >>> 97 cout s '\n'; >>> a
Với s = 97, nếu ép kiểu về int, chương trình sẽ in ra số 97; nếu không, nó sẽ in ra chữ ‘a’. Do mã ASCII của a là 97.
Quay lại với câu hỏi trên. Đáp án là vòng lặp này sẽ lặp vô tận. Bởi vì giá trị lớn nhất của char là 127, khi nó nhận giá trị từ 128 trở lên, sẽ xảy ra hiện tượng tràn số.
Nếu không tin, bạn hãy chạy thử. Hoặc đơn giản hơn, hãy thử các dòng code này để xem chúng in ra gì:
#include using namespace std; int main(){ char s = 128; cout (int)s '\n'; s = 129; cout (int)s '\n'; s = 130; cout (int)s '\n'; }
Đây là output bạn sẽ nhận được. Đó chính là hậu quả của việc tràn số.
-128 -127 -126
Chú ý: Hiện tượng tràn số có thể xảy ra trong tất cả các ngôn ngữ lập trình. Tuy nhiên, với các ngôn ngữ lập trình bậc cao, chúng đã có cơ chế kiểm soát nên bạn có thể không gặp phải hiện tượng này.
Nếu bạn quan tâm đến lập trình C/C++, hãy đọc thêm các bài viết hay khác tại chuyên mục lập trình C/C++ trên trang web của chúng tôi.
Phạm vi giá trị các kiểu dữ liệu trong C/C++
Để tránh bị tràn số trong lập trình, bạn cần phải nắm rõ phạm vi giá trị của các kiểu dữ liệu trong mỗi ngôn ngữ lập trình. Về cơ bản, nhiều kiểu dữ liệu trong các ngôn ngữ lập trình là giống nhau. Tuy nhiên, trong bài viết này, chúng ta chỉ tìm hiểu về phạm vi giá trị của các kiểu dữ liệu trong C/C++.
Vậy làm gì để tránh bị tràn số trong lập trình nói chung? Bạn cần phải nắm được phạm vi giá trị của các kiểu dữ liệu trong mỗi ngôn ngữ lập trình. Về cơ bản, nhiều kiểu dữ liệu trong các ngôn ngữ lập trình là giống nhau. Do đó, trong bài viết này, mình chỉ tập trung trình bày về phạm vi giá trị của các kiểu dữ liệu trong C/C++.
Bảng phạm vi giá trị các kiểu dữ liệu trong C/C++:
Data Type | Range | Macro for min value | Macro for max value |
---|---|---|---|
char | -128 to +127 | CHAR_MIN | CHAR_MAX |
signed char | -128 to +127 | SCHAR_MIN | SCHAR_MAX |
unsigned char | 0 to 255 | 0 | UCHAR_MAX |
short int | -32768 to +32767 | SHRT_MIN | SHRT_MAX |
unsigned short int | 0 to 65535 | 0 | USHRT_MAX |
int | -2147483648 to +2147483647 | INT_MIN | INT_MAX |
unsigned int | 0 to 4294967295 | 0 | UINT_MAX |
long int | -9223372036854775808 to +9223372036854775807 | LONG_MIN | LONG_MAX |
unsigned long int | 0 to 18446744073709551615 | 0 | ULONG_MAX |
long long int | -9223372036854775808 to +9223372036854775807 | LLONG_MIN | LLONG_MAX |
unsigned long long int | 0 to 18446744073709551615 | 0 | ULLONG_MAX |
float | 1.17549e-38 to 3.40282e+38 | FLT_MIN | FLT_MAX |
negative float | -1.17549e-38 to -3.40282e+38 | -FLT_MIN | -FLT_MAX |
double | 2.22507e-308 to 1.79769e+308 | DBL_MIN | DBL_MAX |
negative double | -2.22507e-308 to 1.79769e+308 | -DBL_MIN | -DBL_MAX |
Để xem phạm vi của các kiểu dữ liệu này, bạn có thể sử dụng các Macro trong bảng trên. Dưới đây là một đoạn code ví dụ:
// C++ code để thể hiện các Macro của các kiểu dữ liệu #include #include // for int, char macros #include // for float, double macros using namespace std; int main() { // Hiển thị phạm vi giá trị bằng Macro cout "char ranges from : " CHAR_MIN " to " CHAR_MAX; cout "\n\nshort char ranges from : " SCHAR_MIN " to " SCHAR_MAX; cout "\n\nunsigned char ranges from : " 0 " to " UCHAR_MAX; cout "\n\nshort int ranges from : " SHRT_MIN " to " SHRT_MAX; cout "\n\nunsigned short int ranges from : " 0 " to " USHRT_MAX; cout "\n\nint ranges from : " INT_MIN " to " INT_MAX; cout "\n\nunsigned int ranges from : " 0 " to " UINT_MAX; cout "\n\nlong int ranges from : " LONG_MIN " to " LONG_MAX; cout "\n\nunsigned long int ranges from : " 0 " to " ULONG_MAX; cout "\n\nlong long int ranges from : " LLONG_MIN " to " LLONG_MAX; cout "\n\nunsigned long long int ranges from : " 0 " to " ULLONG_MAX; cout "\n\nfloat ranges from : " FLT_MIN " to " FLT_MAX; cout "\n\nnegative float ranges from : " -FLT_MIN " to " -FLT_MAX; cout "\n\ndouble ranges from : " DBL_MIN " to " DBL_MAX; cout "\n\nnegative double ranges from : " -DBL_MIN " to " +DBL_MAX; return 0; }
Kết quả hiển thị:
char ranges from : -128 to 127 short char ranges from : -128 to 127 unsigned char ranges from : 0 to 255 short int ranges from : -32768 to 32767 unsigned short int ranges from : 0 to 65535 int ranges from : -2147483648 to 2147483647 unsigned int ranges from : 0 to 4294967295 long int ranges from : -9223372036854775808 to 9223372036854775807 unsigned long int ranges from : 0 to 18446744073709551615 long long int ranges from : -9223372036854775808 to 9223372036854775807 unsigned long long int ranges from : 0 to 18446744073709551615 float ranges from : 1.17549e-38 to 3.40282e+38 negative float ranges from : -1.17549e-38 to -3.40282e+38 double ranges from : 2.22507e-308 to 1.79769e+308 negative double ranges from : -2.22507e-308 to 1.79769e+308
Giải thích phạm vi của kiểu dữ liệu
Chúng ta sẽ lấy ví dụ với kiểu char để minh họa. Để hiểu phạm vi giá trị, chúng ta cần biết về khái niệm hoán vị.
Như các bạn đã biết, 1 bit trong lập trình có thể biểu diễn 2 giá trị: 0 hoặc 1, tức là biểu diễn được 2^1 giá trị.
Vậy 2 bits sẽ biểu diễn được 4 giá trị: 00, 01, 10 và 11, tức là biểu diễn được 2^2 giá trị.
Như vậy, 1 byte = 8 bits sẽ biểu diễn được 2^8 giá trị khác nhau.
Máy tính lưu giá trị mã bit 0 và 1, tức là dạng nhị phân. Do đó, 00 = 0, 01 = 1, 10 = 2, 11 = 3 trong cơ số 10. Như vậy, giá trị lớn nhất mà 1 byte có thể biểu diễn là 11111111 = 2^7 + 2^6 + 2^5 + 2^4 + 2^3 + 2^2 + 2^1 + 2^0 = 255.
Đó là lý do kiểu unsigned char có phạm vi giá trị từ 0 đến 255. Còn kiểu char do có cả miền âm nên sẽ bị chia đôi (-128 đến 127). Phần dương bị hụt 1 giá trị do cần biểu diễn cả số 0.
Kết luận
Qua bài viết này, bạn đã hiểu được phạm vi giá trị của các kiểu dữ liệu trong ngôn ngữ C++. Đồng thời, bạn cũng đã biết về hiện tượng tràn số và cách tránh bị tràn số. Nếu bạn còn thắc mắc, hãy để lại câu hỏi trong phần bình luận.
Chúc bạn học tập tốt!
Tài liệu tham khảo: [1]. https://www.geeksforgeeks.org/data-type-ranges-and-their-macros-in-c/