cool. So using that method:
#define ct_assert(condition) ((void)sizeof(char[1 - 2*!(condition)]))
When that fires it gives:
file.c:16: error: size of array 'type name' is negative
Whereas the one on the web page gives:
file.c:16: warning: division by zero
file.c:16: error: enumerator value for 'assert_line_16' is not an integer constant
Take a look at D programming language - it has static asserts and even unit testing at language level (and it's as fast and low-level as C)
The trick of enclosing the enum definition in braces:
#define ct_assert(e) {enum { ct_assert_value = 1/(!!(e)) };}
is a good one, but it's still not completely general. Without the braces, it's a declaration; with the braces, it's a statement, specifically a compound statement.
C99 (which is not widely implemented) allows declarations and statements to be mixed, so the braces aren't necessary if you're using a C99 compiler, or a C++ compiler, or a pre-C99 compiler that allows mixed declarations and statements as an extension.
But in strict C90, all declarations in a block must be followed by all statements in a block; they can't be mixed. So if the version with the braces is used within the declaration part of a block (specifically, if it's followed by a declaration at the same level), then it's an error.
A workaround is to define two versions of the macro, one without the braces for use in a declaration context, and another with the braces for use in a statement context.
And if it is placed on the same line number in two different files ...
Still, very useful.
@Bill good point.
If these files are in the same compilation unit you'll get an error. For e.g. you could hit this if you had multiple header files using ct_assert() on the same line, as confirmed by:
echo "enum { a = 1 }; enum { a = 1 };" | gcc -xc -
I'll look at putting __FILE__ or some other distinguishing id in there.
thanks!
__COUNTER__ on VS could help but even that does not guarantee uniqueness as it always starts in the same place after a pre-compiled header.
Think I'll stick with the old one. VS gets too excited!
d:\work\dmm1_0\fe_dhs\devapp\dmm11\shareddata.h(32) : error C2148: total size of array must not exceed 0x7fffffff bytes
d:\work\dmm1_0\fe_dhs\devapp\dmm11\shareddata.h(32) : error C2148: total size of array must not exceed 0x7fffffff bytes
d:\work\dmm1_0\fe_dhs\devapp\dmm11\shareddata.h(32) : error C2070: 'char [-1]': illegal sizeof operand
d:\work\dmm1_0\fe_dhs\devapp\dmm11\shareddata.h(32) : error C2466: cannot allocate an array of constant size 0
d:\work\dmm1_0\fe_dhs\devapp\dmm11\shareddata.h(55) : error C2556: 'char (*ct_assert(void))[1]' : overloaded function differs only by return type from 'char (*ct_assert(void))[]'
d:\work\dmm1_0\fe_dhs\devapp\dmm11\shareddata.h(32) : see declaration of 'ct_assert'
d:\work\dmm1_0\fe_dhs\devapp\dmm11\shareddata.h(55) : error C2369: 'ct_assert' : redefinition; different subscripts
d:\work\dmm1_0\fe_dhs\devapp\dmm11\shareddata.h(32) : see declaration of 'ct_assert'
d:\work\dmm1_0\fe_dhs\devapp\dmm11\shareddata.h(227) : error C2556: 'char (*ct_assert(void))[1]' : overloaded function differs only by return type from 'char (*ct_assert(void))[]'
d:\work\dmm1_0\fe_dhs\devapp\dmm11\shareddata.h(32) : see declaration of 'ct_assert'
d:\work\dmm1_0\fe_dhs\devapp\dmm11\shareddata.h(341) : error C2556: 'char (*ct_assert(void))[1]' : overloaded function differs only by return type from 'char (*ct_assert(void))[]'
d:\work\dmm1_0\fe_dhs\devapp\dmm11\shareddata.h(32) : see declaration of 'ct_assert'
d:\work\dmm1_0\fe_dhs\devapp\dmm11\stdafx.h(205) : error C2556: 'char (*ct_assert(void))[1]' : overloaded function differs only by return type from 'char (*ct_assert(void))[]'
d:\work\dmm1_0\fe_dhs\devapp\dmm11\shareddata.h(32) : see declaration of 'ct_assert'
In the Microsoft compiler (VS.2008) the expression (1/!!(e)) generates a warning. It must implicitly cast bool to int. An alternate expression, that is only slightly uglier is (1/((e)?1:0)) which has the same effect, but removes the implicit cast.
-Jesse
Argh! Thanks Jesse. So it's giving a warning about dividing an int with a bool? Could I add a cast instead? I'm wary about adding conditionals.
I like it! Thanks!
Adding a statc assert of the non-C11 style immediately after a case statement causes an erro (cannot use label.....) in CCS5.
Consequently the macro requires an extra brace at the start and a semi colon and brace at the end.
What about next macro:
/*
* Simple compile time assertion.
* Example: CT_ASSERT(sizeof foo <= 16, foo_can_not_exceed_16_bytes);
*/
#define CT_ASSERT(exp, message_identifier) \
struct compile_time_assertion { \
char message_identifier : 8 + !(exp); \
}
For example in comment MSVC tells something like:
test.c(42) : error C2034: 'foo_can_not_exceed_16_bytes' : type of bit field too small for number of bits
The double brace version of STATIC_ASSERT(e,m) still not work, if it is placed at the global variable scope. For example, when it is used in the following codes:
STATIC_ASSERT(sizeof(int)==4, "");
int main(int, argc**)
{
return 0;
}
For C++, wrapping your enum {} by a uniquely named struct/class would solve the problem. I am not quite sure how to do it in C.
Yes as stated in the article, for traditional C, i.e. compilers not supporting the 15 year old C99 standard you need separate macros for global scope.
My idea is to use typedef, like this:
#define STATIC_ASSERT(e) \
typedef int _static_assert_type_[1/(!!(e))]
The beauty of this idea is that typedef a same name multiple times to a same type is not an error under C99. You won't need a unique identifier. And typedef can exist in almost any scope. The drawback of this idea is, when assert is raised, it raises two errors: 1. divided by zero, 2. variably modified typedef (i.e. typedef mismatches for a same name).
@Robin this is nearly very nice, but there are 2 issues.
1. If you have multiple STATIC_ASSERTS in the same scope, gcc 4.9 at least will give:
error: redefinition of typedef ‘_static_assert_type_’ with different type
2. Even if you only use the assert once per scope, with -Wall gcc will give:
warning: typedef ‘_static_assert_type_’ locally defined but not used [-Wunused-local-typedefs]
Thanks man, I really don't know how to do this stuff, and I was proving several ways, trying to mix some ideas by the web, but the simple macros resources that you free really helped me with my work.
Just to say: "Thank you to all contributors" for providing some really useful code!
Using VS 12.0.21005.1 REL, it gives `warning C4804: 'operation' : unsafe use of type 'bool' in operation`. Thus, for supressing the warnings, `(int)(!!(e))` will slience it.
With Visual Studio 2015 Update 1, the line
"enum { foo = 1/0 };"
does not throw a compiler error. Hence the assert doesn't fail when it's supposed to fail.
@Vikram, that sounds like a compiler bug? What value does foo have then?
Value is 0 !! No idea how.
I have filed a bug report - http://connect.microsoft.com/VisualStudio/feedback/details/3001747/compiler-error-c2057-not-happening-when-its-expected-to
Also, I just noticed that static_assert works in C code in VS 2012 and 2015. May be they added some C11 support?
Could you provide an example of a non-constant expression that the the GNU version in update 5 does not detect. Thanks :)
Update 11 doesn't work where an expression is required. So e.g. the follow doesn't work:
#define F(x) (STATIC_ASSERT(0xFF >= x), f((uint8_t)x))
because the comma operator needs expressions not statements.
The kernel version:
#define STATIC_ASSERT(condition) \
((void)sizeof(char[1 - 2*!(condition)]))
is an expression, so it would work in this case, but alas it doesn't work at file scope. It would be nice to have one macro that works everywhere. Any ideas?