C语言中不使用sizeof且非main函数内获取数组大小的方法及代码失效原因分析
首先咱们来拆解你遇到的问题:为什么同样的代码在main里能正常得到数组大小,放到arrSize函数里就输出垃圾值了?
核心原因:数组参数的退化
当你把数组作为函数参数传递时,C语言会自动把它退化为指向数组首元素的指针。也就是说,在arrSize函数里,int a[]这个写法本质上等价于int* a——它不再是数组类型,只是个普通的指针。
那咱们看你在函数里写的*(&a+1) - a:
&a是指向int*类型的指针(因为a是int*),&a+1就是偏移一个int*大小的内存地址。- 用这个地址减去
a(int*类型),得到的是两个指针之间的字节数除以sizeof(int*),这显然和原数组的元素个数毫无关系,所以结果就是无意义的垃圾值。
而在main函数里,arr是实实在在的数组类型(int[4]),&arr是指向整个数组的指针(类型为int(*)[4])。&arr+1就是偏移一个int[4]大小的地址,减去arr(此时数组名退化为首元素指针int*),指针减法会自动按元素类型计算差值,结果正好是数组的元素个数4,这和sizeof(arr)/sizeof(arr[0])的效果一致。
如何在非main函数中获取数组大小(不用sizeof)
要在其他函数里安全获取数组大小,关键是让函数能识别出数组的完整类型(包括大小),这里有两种靠谱的方法:
方法1:使用数组引用作为函数参数(C99及以上支持)
你可以把函数参数声明为指向固定大小数组的引用,这样函数就能知道数组的大小:
#include <stdio.h> // 这里的N是数组的大小,调用时编译器会自动推导 void arrSize(int (&a)[4], int (&b)[5]) { // 此时a是数组类型,和main里的用法一致 int size_a = *(&a + 1) - a; int size_b = *(&b + 1) - b; printf("%d,%d\n", size_a, size_b); } int main() { int arr[] = {2,12,14,16}; int brr[] = {8,53,2,4,16}; arrSize(arr, brr); // 编译器会匹配数组大小 return 0; }
不过这个方法的局限性是,函数只能处理固定大小的数组,如果要支持任意大小,得用宏或者泛型(C11的_Generic)。
方法2:使用宏来封装数组大小计算
宏是在预处理阶段展开的,能直接获取数组的原始类型,所以可以写一个通用的宏来计算数组大小,然后在任何函数里都能使用:
#include <stdio.h> // 宏定义:计算数组元素个数,不用sizeof #define ARRAY_SIZE(arr) (*(&arr + 1) - arr) void arrSize(int a[], int b[], int size_a, int size_b) { printf("%d,%d\n", size_a, size_b); } int main() { int arr[] = {2,12,14,16}; int brr[] = {8,53,2,4,16}; // 在main里用宏计算大小,再传递给函数 arrSize(arr, brr, ARRAY_SIZE(arr), ARRAY_SIZE(brr)); return 0; }
这种方式更灵活,支持任意大小的数组,而且完全避开了sizeof。
补充:关于char数组的特殊情况
你提到char数组的情况能理解,其实原理是一样的:当char数组作为函数参数传递时也会退化为指针,只是sizeof(char)是1,所以有时候可能巧合得到看似合理的值,但本质还是错误的,和其他数据类型的问题根源完全相同。
内容的提问来源于stack exchange,提问作者benny hassan

