4 ms·
A word of caution to anyone writing C macros like the examples in the article... There is a potential error in this macro: #define COUNTOF(array) \ (si
by Stratoscope 7y ago
A word of caution to anyone writing C macros like the examples in the article... There is a potential error in this macro:
#define COUNTOF(array) \
(sizeof(array) / sizeof(array[0]))
Generally, when you write a C macro, you should parenthesize each use of any argument inside the macro. The exceptions would be cases like token pasting where you want the literal argument text and only the text.
I don't have a specific failure example in mind for the COUNTOF macro (maybe someone can post one?) - but here is a macro where it is easy to see the problem:
#define TIMESTWO( value ) ( value * 2 )
int ShouldBeEight = TIMESTWO( 2 + 2 ); // Oops, it's 6!
This happens because the macro call expands to:
2 + 2 * 2
Parentheses prevent this problem:
#define TIMESTWO( value ) ( (value) * 2 )
The macro also illustrates a common misconception: sizeof is not a macro or function call, it's an operator. So wrapping the argument to sizeof in parens isn't necessary unless they are needed for other reasons. (They don't hurt anything, they just aren't needed.)
So when I've written a COUNTOF macro (and it is a very handy macro in C/C++ code if your compiler doesn't already provide a similar one like __countof in MSVC), I write it like this:
#define COUNTOF( array ) \
( sizeof (array) / sizeof (array)[0] )
The space after each sizeof is my attempt to indicate that the following parens are not function/macro call parens, but are there specifically to wrap the array argument safely.
- hedora 7y agoYou also shouldn’t repeat the argument: “while(COUNTOF(ptr++) > 0) { }” would do horrible things (if it compiles; didn’t check) for example. There are various preprocessor tricks to avoid this stuff (eg “do{ x = (array); ... } while (0)”), but, honestly, if you want this sort of memory safety, C++ is your friend.
- Stratoscope 7y agoAgreed, although I don't know of a straight C way of implementing COUNTOF without repeating the argument. So it becomes a lesser-of-two-evils question: is the macro (with its flaws) better than doing the calculation explicitly every time you need it? The __countof macro in MSVC does go to some extra work to make it more safe, so it's definitely recommended to use that instead of rolling your own COUNTOF in that environment.
- saagarjha 7y agosizeof does not perform evaluation of its argument in the traditional sense, so this is not an issue here.
- Stratoscope 7y agoAh yes, thanks for the reminder. This is also the reason why COUNTOF is valid for a zero-length array. Even though array[0] is non-existent in this case, it's fine to pass it to sizeof to get the size of an array element as if there were one. After all, a pointer can point one element past the end of an array.
- saagarjha 7y ago> After all, a pointer can point one element past the end of an array. Hmm, I wouldn’t put it that way, since sizeof(array[1]) would work just as well. I like to think of sizeof running the type checker on the expression provided and then looking up the size of the type of the expression: this makes it clear that the thing inside can be completely bogus as long as it is syntactically correct.
- Stratoscope 7y agoThat is a much better way to look at it, thanks!
- firethief 7y agoEven plain `COUNTOF(ptr)` is wrong, unless `ptr` is an array.
- MaulingMonkey 7y ago-Werror-sizeof-pointer-div Or in C++: #include <type_traits> template < typename T, std::size_t N > constexpr std::size_t count_of(const T (&)[N]) { return N; } #define COUNTOF(a) std::integral_constant<std::size_t, count_of(a)>::value (C++03 friendly versions are entirely possible, but left as an exercise to the reader)
- MaulingMonkey 7y agosizeof doesn't actually evaluate the expression, so in this case ptr++ won't get evaluated at all (nevermind twice). Good thing to be aware of in general though.
- ndesaulniers 7y agoSubexpressions with side effects being passed to macros is a great way of having the side effect occur repeatedly through repeated evaluation of the operands to the macro. Be careful!
- ndesaulniers 7y agoAlso, I've seen a buggy linux kernel driver use the above macro, then pass it a pointer. Only caught it because -Wsizeof-pointer-div in a recent compiler.