分类: C/C++
2011-07-12 11:32:49
比较为程序员熟知的做法是
#define arrsz(a) (sizeof(a)/sizeof(a[0]))
检查下面的代码:
std::vector
printf("sizeof v is %d\n", arrsz(v));
编译和运行都没有报错,但得到了一个错误的结果——3。
因为sizeof(v)是12,sizeof(int)是4。这里sizeof(v)并不是数组全部元素的总的尺寸。
在《Unix编程艺术》中,Eric Raymond提出Unix社区中得到广泛认可的一条原则:
Principle of Most Surprise: When you must fail, fail noisily and as soon as possible.
当代码应当失败时,要尽早而且尽可能惹人注意地失败。
而不是像上面我们遇到的例子那样,不声不响地失败,同时隐藏了一个严重的错误。
问题有了。解决问题的第一个尝试:
#define arrsz2(a) (sizeof(a)/sizeof(0[a]))
0[a]符合C++的语法规范。根据在C++标准(ISO/IEC 14882:2003(E))的8.3.4小节的第6点,原文是:
Except where it has been declared for a class (13.5.5), the subscript operator [] is interpreted in such a way that E1[E2] is identical to *((E1)+(E2)). Because of the conversion rules that apply to +, if E1 is anarray and E2 an integer, then E1[E2] refers to the E2-th member of E1. Therefore, despite its asymmetric appearance, subscripting is a commutative operation.
对数组来说,e1[e2]和e2[e1]是等价的,普通数组的下标运算符满足交换律。但是下面的代码编译时就会出错:
std::vector
printf("sizeof v is %d\n", arrsz2(v));
因为vector模板提供了operator[],v[0]没问题。但0[v]被解释为0.operator[](v),这个操作符是未定义的;*(0+v)当然也不是合法的表达式。目标达到了……吗?
long longArr[11];
long *p = longArr;
printf("sizeof v is %d\n", arrsz2(p));
静默失败的问题再次出现。结果是1。现在的要求是,求数组长度的这个宏,不能默默地接受vector或指针类型后默默输出一个错误的结果。Google的牛人们在Chrome的源码里这样解决的:
template
char (&ArraySizeHelper(T (&array)[N]))[N];
#define arraysize(array) (sizeof(ArraySizeHelper(array)))
这模板是个函数模板。
函数参数是一个引用,这个引用指向一个数组,长度为N,数组元素类型为T。
函数返回一个引用,这个引用也指向一个数组,长度为N,元素类型为char。
对这个长度为N的char数组使用sizeof即得到N。
最后的这个宏,如果把指针或vector类型传入,都会引起编译错误。另外注意如果写成
T &array[N]
就不是一个“数组的引用”了,而是一个元素类型为引用的数组(这在C++中是非法的)。
实际上这个做法背后还有一个不小的话题,就是数组形参,《C++必知必会》中有专题讲解。
这个话题的原始材料来自: