Xin chào mọi người, Lâu lắm rồi không dùng C.
Nói chung trong C, đây là cách chúng ta tìm độ dài của một mảng arr:
n = sizeof(arr) / sizeof(arr[0]);
Ở đây chúng ta lấy kích thước của mảng theo byte; sau đó chia cho kích thước của một phần tử mảng riêng lẻ.
Thế nếu tôi nói với bạn rằng chúng ta có thể loại bỏ sizeof và có một cách tuyệt vời hơn để tính kích thước thì sao? như thế này:
n = (&arr)[1] - arr;
Được rồi, chúng ta hãy xem điều đó có thể đúng như thế nào!
Bạn đã bao giờ nghĩ sự khác biệt giữa arr và &arr là gì chưa?
Chúng ta hãy kiểm tra điều đó bằng cách in địa chỉ bộ nhớ của cả hai:
int arr[5] = {1, 2, 3, 4, 5};
printf("Address of arr is %p\n", (void*)arr);
printf("Address of &arr is %p\n", (void*)&arr);
và đây là kết quả:
$ gcc -o a size.c
$ ./a
Address of arr is 0x7fff57266870
Address of &arr is 0x7fff57266870
Như bạn có thể thấy trong đầu ra, cả arr và &arr đều trỏ đến cùng một vị trí bộ nhớ chính xác 0x7fff57266870.
Bây giờ, hãy tăng cả hai con trỏ lên 1 và kiểm tra địa chỉ bộ nhớ của chúng.
Sau đây là mã để kiểm tra địa chỉ bộ nhớ của arr + 1 và &arr + 1:
int arr[5] = {1, 2, 3, 4, 5};
printf("Address of arr is %p\n", (void*)arr);
printf("Address of &arr is %p\n", (void*)&arr);
printf("Address of arr + 1 is %p\n", (void*)(arr + 1));
printf("Address of &arr + 1 is %p\n", (void*)(&arr + 1));
và đầu ra:
$ gcc -o a size.c
$ ./a
Address of arr is 0x7fff57266870
Address of &arr is 0x7fff57266870
Address of arr + 1 is 0x7fff57266874
Address of &arr + 1 is 0x7fff57266884
Chúng tôi thấy rằng:
(arr + 1) trỏ đến 874, cách arr 4 byte, trỏ đến 870 (tôi đã xóa các bit có thứ tự cao hơn của địa chỉ để ngắn gọn). Một int trên máy của tôi chiếm 4 byte, vì vậy (arr + 1) trỏ đến phần tử thứ hai của mảng.
(&arr + 1) trỏ đến 884, cách arr 20 byte (trỏ đến 870).
(884 - 870 = 14 trong hệ thập lục phân = 20 trong hệ thập phân)
Xét đến kích thước của int, (&arr + 1) cách đầu mảng 5 kích thước int. 5 cũng là kích thước của mảng. Vì vậy, (&arr + 1) trỏ đến địa chỉ bộ nhớ sau khi kết thúc mảng.
Tại sao (arr + 1) và (&arr + 1) lại khác nhau mặc dù arr và &arr trỏ đến cùng một vị trí?
Câu trả lời
- Trong khi (arr + 1) và (&arr + 1) có các giá trị, chúng là các kiểu khác nhau.
arr có kiểu int *, trong khi as &arr có kiểu int (*)[size].
Vì vậy, &arr trỏ đến toàn bộ mảng trong khi as arr trỏ đến phần tử đầu tiên của mảng.
Điều này đưa chúng ta đến một điều hữu ích - độ dài của mảng.
* (&arr + 1) cung cấp cho chúng ta địa chỉ sau khi kết thúc mảng và arr là địa chỉ của phần tử đầu tiên của mảng.
Trừ phần sau khỏi phần trước sẽ cung cấp độ dài của mảng.
n = *(&arr + 1) - arr;
Chúng ta có thể đơn giản hóa điều này bằng cách sử dụng chỉ số mảng (vì x[1] giống như *(x+1)) và đây là kết quả:
n = (&arr)[1] - arr;
Khá tuyệt phải không? Nhưng, xin đừng thực sự sử dụng điều này trong cuộc sống thực, trừ khi có lý do chính đáng. sizeof là một toán tử được đánh giá tại thời điểm biên dịch (trừ các mảng có độ dài biến đổi C99), vì vậy nó cũng khá tuyệt, mặc dù trông không giống vậy!
PS:
Điều này chỉ có tác dụng với các mảng, không phải khi bạn sử dụng con trỏ (như trong char *str đối với chuỗi).
void reverse_str(char *str)
{
//wrong
int strlength = (&str)[1] - str;
}
Trong trường hợp này &str là một con trỏ trỏ đến con trỏ str. Hãy nhớ rằng, trong C, mảng không phải là con trỏ.
Q&A
Đây là một Câu hỏi/Câu trả lời thú vị mà tôi tìm thấy trên stackoverflow liên quan đến cùng chủ đề.
Q: Truy cập địa chỉ đầu tiên sau một mảng có vẻ là hành vi không xác định. Ví dụ: nếu mảng của bạn nằm ở cuối không gian địa chỉ, địa chỉ tham chiếu gây tràn và kích thước kết quả của bạn có thể là bất kỳ thứ gì. Vậy thì làm sao bạn có thể truy cập (&arr)[1]<\code>?
A: C không cho phép truy cập vào bộ nhớ ngoài phần cuối của mảng. Tuy nhiên, nó cho phép một con trỏ trỏ đến một phần tử ngoài phần cuối của mảng. Sự khác biệt này rất quan trọng.
Tài liệu học lập trình C tại đây:
1. GIÁO TRÌNH LẬP TRÌNH CƠ BẢN
2.Bài giảng học lập trình C pdf
3.Bài tập lệnh mảng 1 chiều ngôn ngữ lập trình C#