6

This is the code,

#include<stdio.h>
#include<stdbool.h>

#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))                                                               

struct my_struct {
    int a, b;
//  char c;
};

int main() {
    bool cond = 1;
    BUILD_BUG_ON((sizeof(struct my_struct) % 8) != 0);
    BUILD_BUG_ON(cond);
    return 0;
}

first use of BUILD_BUG_ON(condition) macro is throwing compilation error if sizeof struct is not equal to 8 as the condition will evaluate to true. but second use of macro is not throwing compilation error even if i am providing true condition. I am not able to understand this behaviour. Can someone explain?

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
Jagdish
  • 1,848
  • 3
  • 22
  • 33
  • 8
    It seems like the condition needs to be a compile-time constant expression in order for that error to "work". – Kerrek SB Jul 18 '15 at 18:25
  • 1
    `printf("%ld\n", sizeof(char[1 - 2 * !!(cond)]));` gives `-1`. I didn’t know that was possible. – Ry- Jul 18 '15 at 18:27
  • 2
    https://stackoverflow.com/questions/10078283/how-sizeofarray-works-at-runtime – Ry- Jul 18 '15 at 18:28
  • See also [perl.h](http://perl5.git.perl.org/perl.git/blob/4d5d35d9def298c0adab2e34a187efa998cea923:/perl.h#l3508) for a way to avoid the problem with arrays. – melpomene Jul 18 '15 at 18:34
  • 1
    Moreover, this code simply has undefined behaviour when the condition is dynamically true, since the sizes of variable-length arrays must be greater than zero (cf. 6.7.6.2/5). – Kerrek SB Jul 18 '15 at 18:36
  • 2
    You seem to be missing the difference between BUILD_BUG_ON and a normal assert(). – Jonathon Reinhart Jul 18 '15 at 19:16
  • @minitech Well, probably that UB because when `cond` is true array length becomes `-1` – user007 Jul 18 '15 at 19:59
  • Related: http://stackoverflow.com/q/6765770/827263 – Keith Thompson Jul 18 '15 at 21:18

3 Answers3

8

The BUILD_BUG_ON macro is intended to implement a compile-time assertion.

Given an argument that can be evaluated at compile time, it causes a compile-time failure if the argument is non-zero (true), and does nothing if the argument is non-zero (false).

It does not work for an argument that is evaluated at run time.

#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))

The !! is two logical "not" operators; they have the effect of normalizing a value of 0 to 0, and any non-zero value to 1.

If the resulting condition is 1 (true), then the value of 1 - 2*!!(condition) is -1. If the condition is 0 (false), the value is 1.

An array may not have a negative (or zero) size. Some compilers might support zero-length arrays as an extension; this macro ensures that even such a compiler with diagnose an error. If the size is a constant expression, an array with a negative size is a constraint violation, requiring a compile-time diagnostic.

If the expression is false, then there's no error; the macro expands to an expression that does nothing. If the expression is true and is a constant expression, then the expansion of the macro attempts to define an array of negative size, resulting in a compile-time error.

If the expression is not constant, the macro doesn't work. C (C99 and later) permits variable-length arrays (VLAs). VLAs of zero or negative length are not permitted, but defining such a VLA cannot in general be detected at compile time. It is undefined behavior -- and in this case, it's likely to do nothing. (Just to complicate things VLAs are not permitted at file scope.)

The macro should, ideally, be accompanied by documentation that explains how to use it. That documentation should explain that the argument must be a compile-time expression.

Bottom line: You should only use this macro with a constant expression argument. (To test a run-time expression, you can use assert().) If you use a non-constant expression with a zero value, the behavior is undefined; the most likely result is that the intended "assertion" will not fire and the error will not be detected.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
  • Is there any requirement that sizeof has to be evaluated at compile-time? All compilers I've used do so, but is it enforced by standard? – P.P Jul 18 '15 at 20:57
  • @BlueMoon: Yes, for a non-VLA operand. N1570 6.5.3.4p2: "If the type of the operand is a variable length array type, the operand is evaluated; otherwise, the operand is not evaluated and the result is an integer constant." – Keith Thompson Jul 18 '15 at 21:12
  • I meant if a hypothetical compiler, for example, evaluates `sizeof(int)` at run-time, does it violate any constraints of C standard? – P.P Jul 18 '15 at 21:15
  • @BlueMoon: Only if it affects the behavior of the program. The standard says it's an "integer constant". (How would it evaluate it at run-time?) – Keith Thompson Jul 18 '15 at 21:17
  • Is normalizing with !! for stupid but possible use cases like BUILD_BUG_ON(-1); ? – grenix Jul 23 '20 at 19:05
  • 1
    @grenix Yes, but such cases aren't stupid. There are plenty of cases where you want to test whether a condition is "true" or "false", with "true" being defined as non-zero. `isdigit()` and friends are one example; they're defined to return non-zero, not necessarily `1`, for a matching argument. That's run-time, but there could be similar compile-time cases. – Keith Thompson Jul 23 '20 at 21:09
  • @Keith: Many thanks for the feedback. Just wondering if I would want to force condition to be 0 or 1. Would "#define BUILD_BUG_ON_NON_BOOLEAN_RESULT(condition) ((void)sizeof(char[1 - 2*!(condition==1 || condition==0)]))" work? Or combined "#define BUILD_BUG_ON((void)sizeof(char[1 - 2*!(condition==1 || condition==0)]));((void)sizeof(char[1 - 2*!!(condition)]))" ? – grenix Aug 04 '20 at 15:38
  • @grenix Any scalar value in C, when used as a condition, is treated as true if it's zero, false if it's non-zero. Usually you only care whether it's true or false. What would be the purpose of testing if it's true but not equal to 1? – Keith Thompson Aug 04 '20 at 17:51
  • @Keith: On the other hand the result of a boolean expresion like (a == 0) is definitly either 0 or 1. The wishful thinking (resp. the question if such thing might be possible) was to enforce that 'condition' is a boolean expression . As I now realized enforcing a boolean result is only half way since BUILD_BUG_ON(1) would still be possible. – grenix Aug 05 '20 at 15:39
  • @grenix C doesn't really have a concept of "boolean expression". The equality and comparison operators (< <= > >= == !=) yield a result of type `int` with value `0` or `1`. But any scalar expression can be used as a condition, with zero treated as false and non-zero true. (C99 added type `_Bool`, but it's not deeply integrated into the language.) – Keith Thompson Aug 05 '20 at 16:12
0

Change

BUILD_BUG_ON(cond);

to

BUILD_BUG_ON(1);

to obtain the expected behaviour.

When the cond value is available only in run time then a code that calculates the required sizeof is generated (and its result discarded) in unoptimized builds, or the whole expression is completely ignored in optimized builds.

dlask
  • 8,776
  • 1
  • 26
  • 30
0

I think your method is completely unnatural.
It's bad code, even if it would worked.
You can try more natural approaches to handle errors.

assert

The macro assert tests for a condition in runtime and shows an error message if the test fails.

 #include <assert.h>
 int main(void)
 {
     assert((sizeof(struct my_struct) % 8) != 0);
 }

static_assert

In C11 complaint compilers, static_assert(condition, "Error message") does what you want for constant expressions:

static_assert((sizeof(struct my_struct) % 8) != 0, "Wrong struct");  

#if and #error

Also the #if and #error compiler directives could be used, but I don't advice you in this case, since sizeof is not understood by #if.

The syntax would be:

#if some_condition // Put an integer constant expression readable by an #if.
#  error This is wrong.
#endif

Some of these three methods would have to satisfy your needs.

pablo1977
  • 4,281
  • 1
  • 15
  • 41
  • 2
    It's entirely possible that none of these three methods would have worked. `assert` is evaluated at run time; the OP wants the error to be detected at compile time. `static_assert` was added only recently; the OP may well be using a compiler that doesn't support it. `#if` and `#error`, as you say, do not recognize `sizeof`. I've seen similar techniques used to emulate compile-time assertions. They're ugly but the ugliness is isolated in the macro definition. It's a perfectly legitimate technique. Furthermore, you haven't answered the OP's question, which is how the code works. – Keith Thompson Jul 18 '15 at 20:37
  • You can call this bad code, but even linux kernel uses similar trick for compile-time assertions. – HolyBlackCat Jan 12 '16 at 20:21
  • @HolyBlackCat: You are falling in the fallacy of appeal to authority. – pablo1977 Jan 12 '16 at 23:15
  • @pablo1977 Sorry, my wording was not entirely clear. I'm not saying that the OP's code is good. But this king of tricks is the only way to make compile-time assertions in pre-C11. The fact that similar code exists in linux kernel just shows that it's used in some real software. While OP's approach is unavoidable, it's possible to improve his code. I would rewrite it as `#define BUILD_BUG_ON(x) do {struct {int:-!!(x);} a; sizeof a;} while (0)`. This version works in C++, and emits error if non-const value was used in expression. – HolyBlackCat Jan 13 '16 at 11:58
  • That seems to work better than the negative array approach, mainly because it won't allow runtime evaluation (as mentioned). To avoid warnings about unused values (GCC), cast the result of sizeof to void, i.e. `#define BUILD_BUG_ON(x) do {struct {int:-!!(x);} a; (void) sizeof a;} while (0)` – Jesper Matthiesen Feb 05 '19 at 15:22