14

Finding the size of a static array is a common operation. see: C find static array size - sizeof(a) / sizeof((a)[0])

This can be wrapped into a macro, eg:

#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))

however its possible to accidentally pass in a regular pointer.

eg: void func(SomeArray **foo) { int i = ARRAY_SIZE(foo); }

While its valid C, but often ends up being a logical error.

Its possible to prevent this mistake (taking advantage of per-processor to fail on a zero length bit-field).


#define ARRAY_SIZE(a) \
    ((sizeof(struct { int isnt_array : \
     ((const void *)&(a) == &(a)[0]); }) * 0) + \
     (sizeof(a) / sizeof(*(a))))

I've found this macro works with GCC, but fails with Clang, for indirectly referenced members. with error: expression is not an integer constant expression

eg:

  • char word[8]; int i = ARRAY_SIZE(word); ok.
  • struct Bar { word[8]; }
    void func(struct Bar *foo) { int i = ARRAY_SIZE(foo->word); } fails.

Is there a more portable way to to implement this? (working with Clang is good of course though Im interested in general portability... other compilers too).

This seems such a common task that it would be good to have a re-usable portable macro.

Community
  • 1
  • 1
ideasman42
  • 42,413
  • 44
  • 197
  • 320
  • 2
    1. That's the problem of macros and why many people hate them: they lead to bugs which are hard to find. 2. What does clang say about your example? – idmean Apr 28 '15 at 16:21
  • 1
    I don't think this is specifically a problem with macros, accidental misuse of `sizeof(a) / sizeof((a)[0])` can happen without macros too. Could happen if you reply a local fixed width string with an argument for eg. *(Edited to include Clang's error message)* – ideasman42 Apr 28 '15 at 16:30
  • @ideasman42: are you sure bitfield lengths can be non-constant expresions? Or am I misreading your code?... –  Apr 28 '15 at 16:32
  • @Mints97, Clang and GCC can evaluate this at compile time as constant. *(though Clang fails to for a specific case explained in the question)* – ideasman42 Apr 28 '15 at 16:35
  • @ideasman42: see http://stackoverflow.com/questions/14081737/clarification-on-integer-constant-expressions. I'm really not sure that it qualifies as an integer constant expression... Anyway, you'd be better off with some other static assertion, I guess... –  Apr 28 '15 at 16:37
  • @Mints97, this is effectively a static assertion, irraspective, thats not the root of the problem. This issue is that comparing `&(a) == &(a)[0]` isn't reliable at compile time, on some compilers. – ideasman42 Apr 28 '15 at 16:42
  • 1
    @Steve Fallows, ^^^ not a duplicate because its a different language, answers involve using C++ specific features. – ideasman42 Apr 28 '15 at 17:25
  • 1
    @ideasman42 - Doh! - not reading closely. I'll leave the comment in case a future reader is looking for C++ solution. – Steve Fallows Apr 28 '15 at 17:28

4 Answers4

2

Try this:

#define ASSERT_ARRAY(a) \
    sizeof(char[1-2*__builtin_types_compatible_p(__typeof__(a), __typeof__(&(a)[0]))])

#define ARRAY_SIZE(a) \
    (ASSERT_ARRAY(a)*0 + sizeof(a)/sizeof((a)[0]))

It is not portable, but works with both gcc and clang and has fewer side effects than n.m.'s proposal.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
1

This macro works (in my tests anyway) for clang and gcc. I'm almost sure there's no portable solution.

#define ARRAY_SIZE(a) \
    (({ static __typeof__(a) _aa; \
        static __typeof__(&(a)[0]) _pa = _aa; (void)_pa; }), \
           sizeof(a)/sizeof((a)[0]))
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • Any reason for these to be static? I get this, `error: initializer element is not constant`, for `_pa` assignment (GCC 5.1) – ideasman42 Apr 28 '15 at 17:23
  • I couldn't get this working (after making it compile by removing `static`). A function argument of `char *foo` succeeded, (when it shouldnt't have) as did a locally defined `char foo[10]`. – ideasman42 Apr 28 '15 at 17:35
  • I tested with gcc-4.9 and clang-3.5, will look at newer versions soon. – n. m. could be an AI Apr 28 '15 at 17:49
  • I get multiple warnings, especially: `unused variable '_pa' [-Wunused-variable]` – chqrlie Apr 28 '15 at 17:50
  • This makes use of "statement expressions", and, as such, will not work under several compilers, including some versions of Clang! Some details here: http://stackoverflow.com/questions/6440021/compiler-support-of-gnu-statement-expression –  Apr 28 '15 at 18:01
  • @chqrlie Warnings are easy to suppress. Have to use `(void)_pa` somewhere. Will update code. – n. m. could be an AI Apr 28 '15 at 18:09
  • The idea is that you get an error with a non-array, and no error with an array. This works with gcc-5.1 in my tests, do you have an example where it breaks? (The version by @chqrlie is nicer anyway). – n. m. could be an AI Apr 28 '15 at 18:40
  • 1
    The unused variable warning can be suppressed, not the one on statement expression. I'm afraid your version does creates static data for all instances of the macro, not a desirable side-effect. – chqrlie Apr 28 '15 at 19:01
  • @chqrlie yes it does create static data. I hoped it would be optimized away by the compiler, as it is not used, but no :( One can compile with this version for debug, and with the simpler, non-checking version for deployment. One can also play with sections, weak references and whatnot to eliminate the statics. Or just use your version. – n. m. could be an AI Apr 28 '15 at 20:13
1

This is what I use, with a solution for both C and C++.

It was a requirement for me that both work even on VLAs.

#ifndef __cplusplus
int _ptr_used_(void) __attribute__((error("Pointer used in place of array") ));
#define ARRAY_SIZEOF(arr) ( \
__builtin_types_compatible_p(typeof(arr), typeof((arr)[0])*) \
? _ptr_used_() \
: sizeof(arr)/sizeof((arr)[0]) \
)
#else
/// A type that exists
struct _true_ {};

template <bool constant>
struct is_an_array
{
    /// Used when a constant sized (non-VLA) object is passed in
    /// only allow arrays past
    template<class B, size_t n>
    static _true_ test( B(&)[n] );
};

template <>
struct is_an_array<false>
{
    /// This happens only for VLAs; force decay to a pointer to let it work with templates
    template <class B>
    static _true_ test(B *n);
};

# define ARRAY_SIZEOF(arr) ({ typedef decltype(is_an_array<static_cast<bool>(__builtin_constant_p(sizeof(arr)))>::test(arr)) type; sizeof(arr) / sizeof((arr)[0]); })
#endif
o11c
  • 15,265
  • 4
  • 50
  • 75
  • attribute error does not seem to be supported by `clang`. I get this warning on MacOS: `warning: unknown attribute 'error' ignored [-Wunknown-attributes]` and just a linker failure on `ARRAY_SIZEOF(p)`. – chqrlie Apr 28 '15 at 20:36
  • @chqrlie That part is just an implementation of static assertions. You can drop in any other implementation such as `sizeof(char[1-2*b])`, at the cost of giving up nice error messages. – o11c Apr 28 '15 at 22:43
1

Do you really need a compile-time assertion? If yes, I'm afraid there's no portable way, you can only get this to work on Clang and GCC, or some other compiler, with implementation-specific tricks.

But if you decide to go for portability, you can use a run-time error (which can be just as effective, depending on your testing strategy). Suppose you have an error-reporting function void error(char *errorText). The macro could look something like this (untested, but I hope you get the idea):

#ifdef DEBUG /* Place your debug-mode-flag macro here.
 You won't want the extra branch in the release build */
#define ARRAY_SIZE(a) \
    ((const void *)&(a) == &(a)[0]) ? \
        (sizeof(a) / sizeof(*(a))) : (error("Tried to treat pointer as array!"), 0)
#else
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
#endif