GCC 4.3 (but not by default) and VS 10 support
static_assert
as part of the C++x0 changes, but it's still not supported in C.I poked around and came up with a gimmicky solution but it works pretty well.
#define ASSERT_LINE_HELP_CONCAT(a, b) a##b
#define ASSERT_LINE_HELP(a, b) ASSERT_LINE_HELP_CONCAT(a, b)
#define CT_ASSERT(expression) \
enum { \
ASSERT_LINE_HELP(COMPILE_TIME_ASSERT_ON_LINE_, __LINE__) \
= 1/((expression)) \
}
It creates an enum called
COMPILE_TIME_ASSERT_ON_LINE_###
and sets it to 1 on a valid constant expression and 1/0 on an invalid expression. Setting an enum constant to 1/0 will cause it to fail compilation (that's what we want!). The extra indirection is to allow the __LINE__
macro to be expanded to show the line number.As an example:
#include <stdio.h>
#define ASSERT_LINE_(a, b) a##b
#define ASSERT_LINE(a, b) ASSERT_LINE_(a, b)
#define CT_ASSERT(expression) \
enum { \
ASSERT_LINE(COMPILE_TIME_ASSERT_ON_LINE_, __LINE__) \
= 1/((expression)) \
}
// as defined in the Liunx kernel
struct tcphdr {
unsigned short source;
unsigned short dest;
unsigned int seq;
unsigned int ack_seq;
unsigned short res1:4;
unsigned short doff:4;
unsigned short OOPS:1; // this is the problem
unsigned short fin:1;
unsigned short syn:1;
unsigned short rst:1;
unsigned short psh:1;
unsigned short ack:1;
unsigned short urg:1;
unsigned short res2:2;
unsigned short window;
unsigned short check;
unsigned short urg_ptr;
};
CT_ASSERT(sizeof(struct tcphdr) == 20);
int main()
{
return 0;
}
bandken@six6six:~$ gcc temp.c
temp.c:33: warning: division by zero
temp.c:33: error: enumerator value for ‘COMPILE_TIME_ASSERT_ON_LINE_33’ is not an integer constant
bandken@six6six:~$
This lets us know that our TCP header size isn't 20.
There's a few caveats. It will be confusing to have multiple instances of the
CT_ASSERT(...)
on the same line. That's pretty simple to remedy, but the bigger problem exists when CT_ASSERT(...)
is used in a header file. It won't work if there aren't #include
guards. If multiple CT_ASSERT
s are included in a header file, they will all resolve to the same line. If that is the case, it might be preferable to use a separate .c file that includes the various asserts that could be put into the header files.This is a bit kludgy, but it does provide checking that wouldn't normally be available. In cases where data structure size is vital, it provides a compile time check for these cases. Unit tests that checked the sizes could be used as an alternative to the compile time assert, but that requires the unit tests to be a) written and b) run every time. Depending on the environment, that's not guaranteed, where as
CT_ASSERT(...)
will force the issue to be dealt with before it's committed to the source code repo.