sizeof
Sau bài này bạn sẽ làm được
- Dùng sizeof đúng cú pháp và in kết quả bằng %zu
- Giải thích vì sao sizeof không đánh giá biểu thức bên trong
- Chỉ ra bẫy sizeof trên tham số mảng của hàm
- Viết được macro đếm số phần tử của mảng
Nhìn qua thì sizeof chỉ là một hàm nhỏ trả về kích thước. Thực ra nó là toán tử, được tính lúc biên dịch, và có một cạm bẫy khiến rất nhiều chương trình C chạy sai âm thầm.
#Ba điều phải nhớ
- Nó là toán tử, không phải hàm. Vì vậy kết quả được tính ngay lúc biên dịch, không tốn một chu kỳ CPU nào lúc chạy. Ngoại lệ duy nhất là mảng có độ dài thay đổi.
- Kết quả có kiểu
size_t. Nên in bằng%zu, không phải%d. Dùng sai đặc tả là hành vi không xác định. sizeof(char)luôn bằng 1. Đây là định nghĩa, không phải quan sát. Mọi kích thước khác đều tính theo đơn vị char.
#include <stdio.h>
int main(void)
{
printf("%zu\n", sizeof(int)); /* đúng */
printf("%d\n", sizeof(int)); /* SAI: hành vi không xác định */
return 0;
}#Cú pháp và cách dùng
Có hai dạng. Với tên kiểu thì bắt buộc có ngoặc. Với biểu thức thì ngoặc là tùy chọn.
int a = 5;
int arr[10];
sizeof(int) /* tên kiểu: BẮT BUỘC có ngoặc */
sizeof a /* biểu thức: không cần ngoặc */
sizeof(a) /* cũng được, và dễ đọc hơn */
sizeof arr /* 40, kích thước cả mảng */
sizeof arr[0] /* 4, kích thước một phần tử */
sizeof "hello" /* 6, tính cả ký tự kết thúc chuỗi */| Biểu thức | Kết quả | Giải thích |
|---|---|---|
| sizeof "abc" | 4 | 3 ký tự cộng ký tự kết thúc chuỗi |
| sizeof 'a' | 4 | Hằng ký tự trong C có kiểu int, không phải char |
| sizeof(char) | 1 | Luôn luôn, theo định nghĩa |
| sizeof(void *) | 8 | Mọi con trỏ cùng kích thước trên 64 bit |
| sizeof(struct S) | tùy | Có thể lớn hơn tổng các thành viên do đệm |
#Không đánh giá biểu thức
Vì sizeof được tính lúc biên dịch, biểu thức bên trong nó không hề chạy. Trình biên dịch chỉ cần biết kiểu của biểu thức, không cần biết giá trị.
#include <stdio.h>
int gay_hai(void)
{
printf("Hàm này KHÔNG bao giờ chạy\n");
return 0;
}
int main(void)
{
int i = 5;
size_t s = sizeof(i++); /* i++ KHÔNG chạy */
printf("s = %zu, i = %d\n", s, i); /* s = 4, i = 5 */
size_t t = sizeof(gay_hai()); /* hàm KHÔNG được gọi */
printf("t = %zu\n", t);
return 0;
}#Bẫy sizeof trên mảng
Đây là cạm bẫy quan trọng nhất của bài này, và nó gây ra rất nhiều lỗi thật trong các chương trình C.
void in_mang(int arr[10])
{
size_t n = sizeof arr / sizeof arr[0];
printf("%zu\n", n); /* in 2, không phải 10 */
for (size_t i = 0; i < n; ++i) {
printf("%d ", arr[i]); /* chỉ in 2 phần tử */
}
}int main(void)
{
int arr[10] = {0};
size_t n = sizeof arr / sizeof arr[0];
printf("%zu\n", n); /* in đúng 10 */
return 0;
}Cách viết đúng
Vì hàm không thể tự biết mảng dài bao nhiêu, độ dài phải được truyền vào. Đây là quy tắc bất di bất dịch trong C.
void in_mang(const int *arr, size_t n)
{
for (size_t i = 0; i < n; ++i) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main(void)
{
int a[5] = {1, 2, 3, 4, 5};
in_mang(a, sizeof a / sizeof a[0]); /* tính ở NƠI mảng còn là mảng */
return 0;
}#Macro đếm phần tử
#define ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))
int a[7];
printf("%zu\n", ARRAY_LEN(a)); /* 7 */Macro này nên có trong mọi dự án C. Nhưng nó thừa hưởng nguyên cạm bẫy ở trên: nếu truyền vào một con trỏ, nó cho ra con số sai mà không báo gì.
void f(int *p)
{
printf("%zu\n", ARRAY_LEN(p)); /* SAI, ra 2, không có cảnh báo nào */
}Thành ngữ nên dùng với malloc
Khi cấp phát bộ nhớ, hãy viết sizeof *p thay vì lặp lại tên kiểu. Nếu sau này bạn đổi kiểu của p, dòng cấp phát không cần sửa và không thể sai.
int *p = malloc(n * sizeof(int));
/* Đổi int thành long ở dòng khai báo
mà quên sửa dòng này, chương trình hỏng. */int *p = malloc(n * sizeof *p);
/* Đổi kiểu của p bao nhiêu lần cũng được,
dòng này luôn đúng. */Tự làm thử
- In
sizeofcủa mọi kiểu cơ bản và con trỏ tương ứng của chúng. Nhận xét: mọi con trỏ có cùng kích thước không? - Viết macro
ARRAY_LENrồi chứng minh nó sai khi dùng cho tham số hàm, bằng cách in kết quả ở cả hai nơi. - Viết chương trình chứng minh
sizeof(i++)không làmithay đổi. - Giải thích vì sao
sizeof(struct { char a; int b; })bằng 8 chứ không phải 5. - So sánh
sizeofcủa một chuỗi khai báo bằngchar s[] = "hello"và bằngchar *s = "hello". Giải thích khác biệt.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
sizeoflà toán tử tính lúc biên dịch, kết quả kiểusize_t, in bằng%zu.- Biểu thức bên trong sizeof không được đánh giá, hàm bên trong không được gọi.
- Tham số mảng của hàm luôn là con trỏ, nên sizeof trên nó cho kết quả vô nghĩa.
- Hàm nhận mảng thì luôn phải nhận thêm độ dài.
- Khi cấp phát, viết
sizeof *pthay vì lặp lại tên kiểu.