5

I want to use the C preprocessor to count the amount of lines between two code locations. The basic idea is something like this:

#define START __LINE__
static char* string_list[] = {
    "some string",
    "another string",
    ...
    "last string"
};
#define END __LINE__
#if END - START > 42
    #error Too many entries
#endif

Of course this doesn't work because in this case START and END are merely redefinitions of the __LINE__ macro.
I was playing around a bit with the # and ## operators, but I could not get the preprocessor to evaluate START and END while the preprocessor is running.

My question is: is this possible at all?

Checking the size of the array during runtime is not an option.
Thanks for any hints or ideas in advance!

AstroCB
  • 12,337
  • 20
  • 57
  • 73
Chris
  • 1,613
  • 1
  • 18
  • 27
  • Advise `static const char* string_list[]...` – Clifford Jul 03 '14 at 10:58
  • “Checking the size of the array during runtime”—that's impossible in C. Are you aware that `sizeof` yields a compile-time constant? And is “while the preprocessor is running” really a requirement (what `sizeof` wouldn't fulfill)? – mafso Jul 03 '14 at 11:30

3 Answers3

8

You shouldn't use those macros for that purpose: the code will become completely unmaintainable if you introduce an extra line somewhere. And what if there are too few lines?

Instead of macros, use a static assert:

static_assert(sizeof(string_list) / sizeof(*string_list) == SOME_CONSTANT,
               "Wrong number of entries in string list.");

If you aren't using C11 which has support for static_assert, you can write such an assert yourself, for example like this:

#define COMPILE_TIME_ASSERT(expr) {typedef uint8_t COMP_TIME_ASSERT[(expr) ? 1 : 0];}
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • +1. That COMPILE_TIME_ASSERT macro is cuter than the one I've been using for the last googleplex years. – Bathsheba Jul 03 '14 at 11:08
  • @Bathsheba There's certainly a lot of such ugly macros out there :) Personally I think the standardized `static_assert` is reason enough for upgrading to C11. – Lundin Jul 03 '14 at 11:11
  • Thank you very much. This seems like a reasonable way of accomplishing what I want – Chris Jul 03 '14 at 11:26
  • 2
    I would suggest to have the array be of size -1 instead of 0 to make the assertion fail. Just tested with GCC 4.9.0 only with -std=c99 and no other special switches specified and the code compiles fine with above macro as is. Array size of 0 only breaks if -pedantic is set. Array size of -1 does break with or without the switch so it makes it more reliable. – PuerNoctis Jul 03 '14 at 14:11
  • 1
    @PuerNoctis Test with a compliant C compiler instead. GCC is not a compliant C compiler unless you compile with `std=c99 -pedantic-errors`. GCC has a non-standard extension that allows arrays with size zero. This is not allowed by the C standard. [See this](http://stackoverflow.com/questions/9722632/what-happens-if-i-define-a-0-size-array-in-c-c/9723093#9723093) – Lundin Jul 03 '14 at 14:19
  • @Lundin Thanks for the clarification, I assumed that GCC is C-compliant by default (which is wrong, obviously). But proof-of-concept aside, wouldn't -1 still be more reliable to cover more scenarios, e.g. in exactly this case where someone does NOT use a C-compliant compiler that allow zero sized arrays? – PuerNoctis Jul 03 '14 at 14:27
  • @PuerNoctis The C standard clearly states black on white that it must be a number greater than zero, so -1 makes no difference. The solution is to switch to a compliant compiler instead. Luckily in the case of GCC, this is easy to do by adding two compiler options. – Lundin Jul 03 '14 at 14:32
6

[Acknowledge @Lundin who points out that you can use a typedef]

This is one way

typedef uint8_t START[__LINE__]; /*put this on one line*/

typedef uint8_t END[__LINE__]; /*put this on another line*/

sizeof(END) - sizeof(START) is a compile-time expression giving the line offset.

Put in macros and rename START and END to taste.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • Or use `typedef uint8_t n1[__LINE__];` so that you don't have to actually allocate anything. Still, this doesn't solve the core problem, which is that `__LINE__` macros are unsuitable for static checking of array sizes. – Lundin Jul 03 '14 at 11:02
  • @Lundin: that's elegant and works for me (gcc and msvc2012). I've amended. – Bathsheba Jul 03 '14 at 11:04
0

If you declare the array thus:

static const char* string_list[42] = {...

The compiler will tell you if you provide too many initialisers. You can provide a sentinel entry to mark the end if necessary:

static char* string_list[43] = { ..., NULL } ;

And perhaps write a function to determine the length at runtime if you need to know that.

Clifford
  • 88,407
  • 13
  • 85
  • 165