Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Array-size macro that rejects pointers

The standard array-size macro that is often taught is

#define ARRAYSIZE(arr) (sizeof(arr) / sizeof(arr[0])) 

or some equivalent formation. However, this kind of thing silently succeeds when a pointer is passed in, and gives results that can seem plausible at runtime until things mysteriously fall apart.

It's all-too-easy to make this mistake: a function that has a local array variable is refactored, moving a bit of array manipulation into a new function called with the array as a parameter.

So, the question is: is there a "sanitary" macro to detect misuse of the ARRAYSIZE macro in C, preferably at compile-time? In C++ we'd just use a template specialized for array arguments only; in C, it seems we'll need some way to distinguish arrays and pointers. (If I wanted to reject arrays, for instance, I'd just do e.g. (arr=arr, ...) because array assignment is illegal).

like image 379
nneonneo Avatar asked Oct 18 '13 15:10

nneonneo


1 Answers

Linux kernel uses a nice implementation of ARRAY_SIZE to deal with this issue:

#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr)) 

with

#define __must_be_array(a) BUILD_BUG_ON_ZERO(__same_type((a), &(a)[0])) 

and

#define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b)) 

Of course this is portable only in GNU C as it makes use of two instrinsics: typeof operator and __builtin_types_compatible_p function. Also it uses their "famous" BUILD_BUG_ON_ZERO macro which is only valid in GNU C.

Assuming a compile time evaluation requirement (which is what we want), I don't know any portable implementation of this macro.

A "semi-portable" implementation (and which would not cover all cases) is:

#define ARRAY_SIZE(arr)  \     (sizeof(arr) / sizeof((arr)[0]) + STATIC_EXP(IS_ARRAY(arr))) 

with

#define IS_ARRAY(arr)  ((void*)&(arr) == &(arr)[0]) #define STATIC_EXP(e)  \     (0 * sizeof (struct { int ARRAY_SIZE_FAILED:(2 * (e) - 1);})) 

With gcc this gives no warning if argument is an array in -std=c99 -Wall but -pedantic would gives a warning. The reason is IS_ARRAY expression is not an integer constant expression (cast to pointer types and subscript operator are not allowed in integer constant expressions) and the bit-field width in STATIC_EXP requires an integer constant expression.

like image 92
ouah Avatar answered Sep 20 '22 17:09

ouah