当前位置: 首页 > 工具软件 > c_count > 使用案例 >

浅析_countof宏

庞安晏
2023-12-01
#ifndef _cplusplus
#define _countof(_Array) (sizeof(_Array) / sizeof(_Array[0]))
#else
extern "C++"
{
template <typename _CountofType, size_t _SizeOfArray>
char (*__countof_helper(UNALIGNED _CountofType (&_Array)[_SizeOfArray]))[_SizeOfArray];
#define _countof(_Array) sizeof(*__countof_helper(_Array))
}
#endif
上面是vc中_countof宏的实现。根据条件编译宏的定义,当用在c的代码中,使用的是最常用的方法即 sizeof(_Array) / sizeof(_Array[0])的方式.但用在c++代码时,这个宏的定义就有点让人丈二摸不着头脑了:

template <typename _CountofType, size_t _SizeOfArray>
char (*__countof_helper(UNALIGNED _CountofType (&_Array)[_SizeOfArray]))[_SizeOfArray];
#define _countof(_Array) sizeof(*__countof_helper(_Array))
 

 首先,辅助函数_countof_helper是一个指向一个模板函数的函数指针的声明,乍看起来让人不易弄明白是什么意思,我们可以采用分而治之的方法来解析这个声明。 

我们可以暂时忽略掉最外面的一对大括号中的那一大坨代码,,我们可以用typedef来让它看起来更直观一些:

typedef char array_t [_SizeOfArray];
template <typename _CountofType, size_t _SizeOfArray>
array_t _countof_helper(UNALIGNED _CountofType (&_Array)[_SizeOfArray]);
现在稍微清晰了点吧?!不过就是一个返回类型为 有_SizeOfArray个元素的char数组的函数指针声明么。

好了,现在该看看这个函数声明中的参数了,UNALIGNED这个宏其实定义为空,我们可以直接pass过去,所以函数参数的类型如下:

_CountofType (&_Array)[_SizeOfArray];
 

 

这行代码又表示的是什么意思呢?我们可以再typedef一下就知道了:

typdef _CountofType (&refarray_t) [_SizeOfArray]; // 有_SizeOfArray个元素的数组的引用!
array_t _countof_helper (refarray_t _Array);

一目了然!_countof_helper其实是一个指向返回值类型为char数组,参数类型为_CountofType数组的引用的函数的指针罢了!

现在该看看_countof这个宏的定义了

#define _countof(_Array) sizeof(*__countof_helper(_Array))

当我们调用_countof(arr)时,其实我们是在执行sizeof(*__countof_helper(arr))这个表达式,上面我们可以看到,__countof_helper是一个函数指针的声明,但它的定义又在哪里呢?

真相是,__countof_helper这个函数指针根本就没有被赋值!很多童鞋们可能就会纳闷了,既然__countof_helper这个函数指针只有声明没有赋值,那么当我们的代码调用_countof(arr)时,程序应该运行就会出错了?!

庆幸的是,这些担心是多余的,因为_countof_helper压根就没有机会被调用!我们可以看看_countof_helper是在什么样的上下文中被使用的:

sizeof(*__countof_helper(_Array))
看到了么,是在sizeof中调用的,sizeof操作符有一个非常伟大的特性,那就是它是在编译期获取对象类型信息来产生结果的,既然在编译期获得结果,那么__countof_helper当然就没有机会执行了。童鞋们可能又会疑惑了,那么既然__countof_helper压根就没有被调用过,那么又为什么要声明它呢?

其实,__countof_helper函数指针存在的意义并不在于被调用,而是在于提供类型信息!这就是sizeof的强大之处了,我们可以回顾一下上面分析中,__countof_helper指针指向的函数,其返回类型为有_SizeofArray个元素的的char数组,所以这个sizeof返回的值也就是_SizeofArray了。

我们再来回顾一下,__countof_helper这个所指函数的参数类型为拥有_SizeofArray个元素的的数组的引用,数组元素的类型则是_CountOfType。而_SizeofArray和_CountOfType都是模板自动为我们推演出来的模板参数,当我们将一个数组传递给这个模板函数的时候,_CountOfType是这个数组的元素数目,而_CountOfType则是数组元素的类型了,所以_counof这个宏返回的_SizeofArray就是数组的真实元素数目了,而这所有的工作都是在编译期完成的,没有任何运行期开销。这就是c++模板的无与伦比的强大之处了。

最后的最后,我们要提一个小小的问题了,虽然上面这个模板函数指针的确很强大,但 sizeof(array)/sizeof(array[0]) 这种做法似乎也能实现我们想要的结果,而且也没有任何运行期性能开销啊?!为什么非要把事情弄得这么复杂呢?

我们可以看看如下一段小程序:

char szHello[] = "hello";
char *ptr = szHello;
#define arr_size(_ptr_) sizeof(_ptr_) / sizeof(_ptr_[0])
cout << arr_size(ptr) << endl;

请问arr_size返回的是ptr所指字符串的长度么?

答案是否定的,我们可以分析下,当传入的_ptr_为数组时,arr_size产生的结果正是我们所要的数组元素数,但当_ptr_为普通的指针时,sizeof(_ptr_)其值在32位平台上必为4,即为指针的大小,而sizeof(_ptr_[0])则是_ptr_所指元对象的大小(在这里就是char的大小,这里ptr只是指向char的指针)。故而会产生与我们预期的风马牛不相及的结果。

好吧,让我们用更复杂的_countof宏试试,结果会如何呢?

char szHello[] = "hello";
char *ptr = szHello;
cout << _countof(ptr);

VC2005输出窗口给出的错误消息:

error C2784: “char (*__countof_helper(_CountofType (&)[_SizeOfArray]))[_SizeOfArray]”: 无法从“char *”为“_CountofType (&)[_SizeOfArray]”推导 模板 参数

“啊哈!没编译通过!看来_countof也没我们想象的那么强大么!”

不得不说_countof宏并不是全能的,它上面的arr_size宏一样,只能应用在数组类型,而对于从数组退化而成的指针则是无能为力了。不过,_countof宏相对于arr_size宏有一个优势就是,它可以在编译期识别出参数为普通指针的异常情况,并且会产生编译错误告诉用户——你丫的传个毛指针!而arr_size则是一声不响的在运行时产生错误的结果,所以程序也就一声不响地越跑越不靠谱!所以综合而言,使用_countof宏更为安全!

----------------------------------------------

作者 hanzz2007 , 转载请注明出处

 类似资料: