7

Background

In a microcontroller code, I am using a library provided by the producer where there are many constants defined. I'm trying to give an error if there's a mismatch between some of my constants (shared with components outside the microcontroller, with git-subtree) and the microcontroller constants.

For example, the library defines:

#ifdef SOME_PARTICULAR_MODEL
#define FLASH_BLOCK_SIZE ((uint8_t)64)
/* else, other models */
#endif

And somewhere, in a header shared between the microcontroller code and some code compiled on the PC, I have for example:

#define MYPROG_BLOCK_SIZE 64

And to make sure these constants match, in the microcontroller code, where both constants exist, I have:

#if MYPROG_BLOCK_SIZE != FLASH_BLOCK_SIZE
#error "mismatch between actual block size and defined block size"
#endif

This is to make sure if the code is ever ported to a bigger microcontroller, the shared header would also be updated.

The Problem

The problem is that this gets reduced to:

#if 64 != ((uint8_t)64)

which I'm not sure if is valid C, but nonetheless makes the compiler choke up. Testing, I found out that the problem is not that uint8_t is a typedef and it still chokes up with a cast to int for example.

The Question

Is there a way to remove the (uint8_t) part from a value defined as ((uint8_t)64)? If not, is there any way to change it so the expression turns into one without a cast?

I thought about defining uint8_t as something and undefining it after the #if, but I can't figure out how I can avoid the cast nature of (Y)X and turn it into an arithmetic expression.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Shahbaz
  • 46,337
  • 19
  • 116
  • 182
  • 1
    The preprocessor knows nothing of types, which is why it's failing. – Hasturkun Oct 16 '13 at 14:37
  • @Hasturkun, absolutely right. – Shahbaz Oct 16 '13 at 14:38
  • 3
    Maybe you can trigger compilation error otherwise, for example: `int mismatch_check[(MYPROG_BLOCK_SIZE != FLASH_BLOCK_SIZE)?-1:1];`? – zch Oct 16 '13 at 14:39
  • Is it possible for you to change (or duplicate) the original definition? I think that might be the easiest solution – Hasturkun Oct 16 '13 at 14:39
  • @Hasturkun, it is possible, but I was hoping I could avoid that, since later there could be an updated version of it which I wouldn't have to modify again. – Shahbaz Oct 16 '13 at 14:42
  • @zch, that works, thanks! (you can write it as an answer.) Although I won't be able to give a nice error message about it, but it surely solves the problem. – Shahbaz Oct 16 '13 at 14:43

5 Answers5

12

Here is an improved version (the first version is below). This one does not depend on the cast being uint8_t; it will work with any FLASH_BLOCK_SIZE replacement list of the form ((some type) number).

#define MYPROG_BLOCK_SIZE   64
#define FLASH_BLOCK_SIZE    ((uint8_t)64)

#define B(x)
#define C(x)    B x
#define D(x)    C x

#if MYPROG_BLOCK_SIZE != D(FLASH_BLOCK_SIZE)
    #error "mismatch between actual block size and defined block size"
#endif

This works:

  • In D(FLASH_BLOCK_SIZE), FLASH_BLOCK_SIZE is replaced by ((uint8_t) 64), effectively making D(((uint8_t) 64)).
  • Then D(((uint8_t) 64)) is replaced by C ((uint8_t) 64).
  • This is an invocation of the C macro, which replaces C(x) with B x, so C ((uint8_t) 64) is replaced by B (uint8_t) 64.
  • In that, B (uint8_t) invokes the B macro, which has an empty replacement list, so B (uint8_t) 64 becomes 64.

Here is the original version:

#define MYPROG_BLOCK_SIZE  64
#define FLASH_BLOCK_SIZE   ((uint8_t)64)

#define uint8_t             
#define Helper(x)          x
#define Deparenthesize(x)  Helper x

#if MYPROG_BLOCK_SIZE != Deparenthesize(Deparenthesize(FLASH_BLOCK_SIZE))
    #error "mismatch between actual block size and defined block size"
#endif
#undef uint8_t

When writing code, I would prefer a static assert, but the above does what you requested in the preprocessor.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • Brilliant! I'd use the answer with negative array size in case of error (because maybe in the future they change the header and the constant is not exactly `((uint8_t)64)` anymore), but this is definitely _the_ answer to the question. – Shahbaz Oct 16 '13 at 15:00
  • @Shahbaz: I supplied an improved version that will work even if `uint8_t` is changed to another type. However, it still must have the same general form with parentheses. – Eric Postpischil Oct 16 '13 at 15:14
  • Yes I noticed. It's great, thanks :) I'd award you with some bounty if you don't get more upvotes. – Shahbaz Oct 16 '13 at 15:17
  • 2
    @Shahbaz: Thank you, I appreciate it, but I do not need Stack Overflow reputation. At least not until it can be traded on the securities market. – Eric Postpischil Oct 16 '13 at 15:22
  • Right. You should see what tricks some people (with ~100k rep) use under C++ tag for a few tens of reputation. – Shahbaz Oct 16 '13 at 15:43
5

The solution is to use static assert. With a good STATIC_ASSERT macro, you can put a static assert at file-scope in your header file:

STATIC_ASSERT(FLASH_BLOCK_SIZE == MYPROG_BLOCK_SIZE);

Here is an exemple of definition for the STATIC_ASSERT macro:

#define CAT(x, y) CAT_(x, y)
#define CAT_(x, y) x ## y

#define STATIC_ASSERT(expr)  \
    extern int CAT(static_assert_failed_, __LINE__)[(expr) ? 1 : -1]

With some compilers (e.g., IAR), you have static_assert as a compiler builtin. static_assert is also present in C11 but unfortunately not a lot of embeddded C compiler support C11.

ouah
  • 142,963
  • 15
  • 272
  • 331
  • thanks this is great. Now that you mentioned it, I recall something else (involving `struct { int x:((expr)?1:-1); }`) that was found in the Linux kernel and asked in SO, but in the end has the same idea. I would make it `static` though instead of `extern`. No point in exposing the functions to the linker. – Shahbaz Oct 16 '13 at 14:59
  • Making it `static` is not a good idea as first it will define an object and then you are likely to get a warning for unused variable in your compiler. Note that there are a lot of different ways to define a static assert macro, you can also declare an external function or declare a `typedef`. – ouah Oct 16 '13 at 15:04
  • Should the compiler complain even if you just say `static func(...);`? It's just declared but not defined and never used. Anyway, you're right. It's also possible to do it with a `typedef` which seems more reasonable. – Shahbaz Oct 16 '13 at 15:09
  • @Shahbaz `gcc` would complain for a `static` function declared but not defined. And at block-scope you could not use the macro as a `static` function cannot be declared at block-scope. – ouah Oct 16 '13 at 15:24
  • Here's a static assert in GCC C11 that prints the values if it fails: https://stackoverflow.com/questions/53310844/static-assert-replacement-to-show-value-in-c – Jetski S-type Mar 20 '19 at 05:53
2

For any typedef you can get arrount this by changing to

#define FLASH_BLOCK_SIZE ((uint8_t)+64)

notice the little plus in there?

The preprocessor replaces the typename it doesn't know anything about by 0, so this works out in both contexts. (The preprocessor is supposed to do all arithmetic in [u]intmax_t, anyhow)

For the actual type you are using all of this makes not much sense. There is no such thing as a uint8_t constant. As soon they are evaluated all expressions of a type that is narrower than int are converted to int. So it probably doesn't makes any difference.

Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
1

After macro replacement, a preprocessor expression may have subexpressions of the form defined identifier or defined ( identifier ). Other than that, identifiers and keywords have no meaning; each identifier or keyword is replaced by 0. So this:

#if 64 != ((uint8_t)64)

is equivalent to this:

#if 64 != ((0)64)

which is a syntax error. The fact that it's a typedef isn't the problem; a keyword like int gets the same treatment.

So given:

#define FLASH_BLOCK_SIZE ((uint8_t)64)

you can't use FLASH_BLOCK_SIZE in a preprocessor expression.

The best solution I can think of is to change your definition:

#define FLASH_BLOCK_SIZE_NUM 64
#define FLASH_BLOCK_SIZE ((uint8_t)FLASH_BLOCK_SIZE_NUM)

You can then use FLASH_BLOCK_SIZE_NUM in preprocessor expressions and FLASH_BLOCK_SIZE elsewhere.

On the other hand, do you really need the (int8_t) cast in the first place? Arithmetic expressions are implicitly converted in many contexts, usually to the appropriate type. In many contexts, (uint8_t) will be promoted to int anyway. It's very likely you can just drop the cast and use:

#define FLASH_BLOCK_SIZE 64

To be sure, you'll need to examine all the code that refers to FLASH_BLOCK_SIZE. (Evaluating sizeof FLASH_BLOCK_SIZE would be a problem, but I'm betting you never do that.)

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
  • I don't need the cast. I understand it's uselessness. The developers of that library were not so bright though (I have seen things!). – Shahbaz Oct 16 '13 at 14:52
0

I know the issue thread is old, however I encountered it today... If you do not want to or cannot change the #define, the other answers are great, but if you have access to the #define, I recommend using for pre-processor operations (#if - #else - #endif) instead of:

#define FLASH_BLOCK_SIZE ((uint8_t)64)

use this define:

#define FLASH_BLOCK_SIZE (64U)

This way the cast is still there, and the compiler won't be "confused".

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
SebTm
  • 1