ARRAYSIZE C ++宏:它是如何工作的?

时间:2010-10-31 17:17:44

标签: c++ c-preprocessor

好的,我不是完全新手,但我不能说我理解下面的宏。最令人困惑的部分是将值转换为size_t:这实际上是什么?特别是,因为我看到一个否定运算符,据我所知,它可能导致零值。这是不是意味着它会导致被零除错误? (顺便说一下,宏正确并且工作得很漂亮。)

#define ARRAYSIZE(a) \
  ((sizeof(a) / sizeof(*(a))) / \
  static_cast<size_t>(!(sizeof(a) % sizeof(*(a)))))

7 个答案:

答案 0 :(得分:12)

第一部分(sizeof(a) / sizeof(*(a)))相当简单;它将整个数组的大小(假设您通过宏传递数组类型的对象,而不是指针)除以第一个元素的大小。这给出了数组中元素的数量。

第二部分不是那么简单。我认为潜在的归零是有意的;如果由于某种原因,数组的大小不是其中一个元素的整数倍,它将导致编译时错误。换句话说,它是某种编译时健全性检查。

但是,我无法看到在什么情况下会发生这种情况......正如人们在下面的评论中提到的那样,它会捕获一些滥用(比如在指针上使用ARRAYSIZE() )。但是,它不会捕获所有这样的错误。

答案 1 :(得分:6)

最后的划分似乎是尝试检测到非数组参数(例如指针)。

它无法检测到,例如char*,但适用于T*,其中sizeof(T)大于指针的大小。

在C ++中,人们通常更喜欢以下函数模板:

typedef ptrdiff_t Size;

template< class Type, Size n >
Size countOf( Type (&)[n] ) { return n; }

此函数模板无法使用指针参数进行实例化,只能使用数组。在C ++ 11中,它也可以用std::beginstd::end来表示,它自动地使它也适用于具有随机访问迭代器的标准容器。

限制:对于C ++ 03中的本地类型数组不起作用,并且不会产生编译时间大小。

对于编译时间大小,您可以改为

template< Size n > struct Sizer { char elems[n]; };

template< class Type, size n >
Sizer<n> countOf_( Type (&)[n] );

#define COUNT_OF( a ) sizeof( countOf_( a ).elems )

免责声明:所有代码都不受编译者的影响。

但一般来说,只需使用第一个功能模板countOf

干杯&amp;第h

答案 2 :(得分:3)

假设我们有

T arr[42];

ARRAYSIZE(arr)将扩展为(rougly)

sizeof (arr) / sizeof(*arr) / !(sizeof(arr) % sizeof(*arr))

在这种情况下给出42 /!0,即42

如果由于某种原因,sizeof数组不能被其元素的sizeof整除,则会出现除零。什么时候可以发生?例如,当您传递动态分配的数组而不是静态数组时!

答案 3 :(得分:1)

它确实导致了一个被零除错误(故意)。这个宏工作的方式是它以字节为单位将数组的大小除以单个数组元素的大小(以字节为单位)。因此,如果您有一个int值数组,其中int是4个字节(在大多数32位机器上),则4个int值的数组将是16个字节。

因此,当您在这样的数组上调用此宏时,它会sizeof(array) / sizeof(*array)。并且由于16/4 = 4,它返回数组中有4个元素。

注意:*array取消引用数组的第一个元素,相当于array[0]

第二个除法进行模除法(得到除法的余数),并且因为任何非零值都被认为是“真”,所以如果剩余的则使用!运算符将导致除以零除法非零(同样,除以1)。

答案 4 :(得分:1)

我写了这个版本的宏。考虑旧版本:

#include <sys/stat.h>
#define ARRAYSIZE(a) (sizeof(a) / sizeof(*(a)))

int main(int argc, char *argv[]) {
  struct stat stats[32];
  std::cout << "sizeof stats = " << (sizeof stats) << "\n";
  std::cout << "sizeof *stats = " << (sizeof *stats) << "\n";
  std::cout << "ARRAYSIZE=" << ARRAYSIZE(stats) << "\n";

  foo(stats);
}

void foo(struct stat stats[32]) {
  std::cout << "sizeof stats = " << (sizeof stats) << "\n";
  std::cout << "sizeof *stats = " << (sizeof *stats) << "\n";
  std::cout << "ARRAYSIZE=" << ARRAYSIZE(stats) << "\n";
}

在64位计算机上,此代码生成此输出:

sizeof stats = 4608
sizeof *stats = 144
ARRAYSIZE=32
sizeof stats = 8
sizeof *stats = 144
ARRAYSIZE=0

发生了什么? ARRAYSIZE是如何从32变为零的?好吧,问题是函数参数实际上是一个指针,即使它看起来像一个数组。所以在foo里面,&#34; sizeof(stats)&#34;是8个字节,&#34; sizeof(* stats)&#34;仍然是144。

使用新宏:

#define ARRAYSIZE(a) \
  ((sizeof(a) / sizeof(*(a))) / \
  static_cast<size_t>(!(sizeof(a) % sizeof(*(a)))))

当sizeof(a)不是sizeof(*(a))的倍数时,%不为零,即!反转,然后static_cast求值为零,导致编译时除以零。所以尽可能在宏中,这个奇怪的部门在编译时捕获了这个问题。

PS:在C ++ 17中,只需使用std :: size,请参阅http://en.cppreference.com/w/cpp/iterator/size

答案 5 :(得分:0)

div-by-zero可能试图捕获由于任何原因引起的对齐错误。就像在某些编译器设置中,数组元素的大小为3但编译器将其四舍五入以便更快地访问数组一样,那么一个包含4个条目的数组将大小为16并且!(16/3)将会为零,在编译时给予除零。然而,我不知道有任何编译器这样做,并且可能违反了C ++规范的sizeof,返回的大小不同于数组中该类型的大小。

答案 6 :(得分:-1)

来到这里的晚会......

谷歌的C ++代码库有arraysize()宏的恕我直言 确定的C ++实现,其中包含了几个不在这里考虑的皱纹。

我无法改进the source,其中有明确而完整的评论。