Macro definition ARRAY_SIZE

前端 未结 5 2180
借酒劲吻你
借酒劲吻你 2021-02-19 10:55

I encountered the following macro definition when reading the globals.h in the Google V8 project.

// The expression ARRAY_SIZE(a) is a compile-time constant of t         


        
相关标签:
5条回答
  • 2021-02-19 11:27

    If sizeof(a) / sizeof(*a) has some remainder (i.e. a is not an integral number of *a) then the expression would evaluate to 0 and the compiler would give you a division by zero error at compile time.

    I can only assume the author of the macro was burned in the past by something that didn't pass that test.

    0 讨论(0)
  • 2021-02-19 11:33

    As explained, this is a feeble (*) attempt to secure the macro against use with pointers (rather than true arrays) where it would not correctly assess the size of the array. This of course stems from the fact that macros are pure text-based manipulations and have no notion of AST.

    Since the question is also tagged C++, I would like to point out that C++ offers a type-safe alternative: templates.

    #ifdef __cplusplus
       template <size_t N> struct ArraySizeHelper { char _[N]; };
    
       template <typename T, size_t N>
       ArraySizeHelper<N> makeArraySizeHelper(T(&)[N]);
    
    #  define ARRAY_SIZE(a)  sizeof(makeArraySizeHelper(a))
    #else
    #  // C definition as shown in Google's code
    #endif
    

    Alternatively, will soon be able to use constexpr:

    template <typename T, size_t N>
    constexpr size_t size(T (&)[N]) { return N; }
    

    However my favorite compiler (Clang) still does not implement them :x

    In both cases, because the function does not accept pointer parameters, you get a compile-time error if the type is not right.

    (*) feeble in that it does not work for small objects where the size of the objects is a divisor of the size of a pointer.


    Just a demonstration that it is a compile-time value:

    template <size_t N> void print() { std::cout << N << "\n"; }
    
    int main() {
      int a[5];
      print<ARRAY_SIZE(a)>();
    }
    

    See it in action on IDEONE.

    0 讨论(0)
  • 2021-02-19 11:43

    The second part wants to ensure that the sizeof( a ) is divisible of by sizeof( *a ).

    Thus the (sizeof(a) % sizeof(*(a))) part. If it's divisible, the expression will be evaluated to 0. Here comes the ! part - !(0) will give true. That's why the cast is needed. Actually, this does not affect the calculation of the size, just adds compile time check.

    As it's compile time, in case that (sizeof(a) % sizeof(*(a))) is not 0, you'll have a compile-time error for 0 division.

    0 讨论(0)
  • 2021-02-19 11:45

    latter part will always evaluates to 1, which is of type size_t,

    Ideally the later part will evaluate to bool (i.e. true/false) and using static_cast<>, it's converted to size_t.

    why such promotion is necessary? What's the benefit of defining a macro in this way?

    I don't know if this is ideal way to define a macro. However, one inspiration I find is in the comments: //You should only use ARRAY_SIZE on statically allocated arrays.

    Suppose, if someone passes a pointer then it would fail for the struct (if it's greater than pointer size) data types.

    struct S { int i,j,k,l };
    S *p = new S[10];
    ARRAY_SIZE(p); // compile time failure !
    

    [Note: This technique may not show any error for int*, char* as said.]

    0 讨论(0)
  • 2021-02-19 11:48

    In the Linux kernel, the macro is defined as (GCC specific):

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

    where __must_be_array() is

    /* &a[0] degrades to a pointer: a different type from an array */
    #define __must_be_array(a) BUILD_BUG_ON_ZERO(__same_type((a), &(a)[0]))
    

    and __same_type() is

    #define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
    
    0 讨论(0)
提交回复
热议问题