Conditional define in H files - possible?

Nope this isn’t as simple as the title suggests! :slight_smile:

Hi all,

As a lot of us know, the definition of _BV() in the Atmel AVR toolchain contains a bug which causes it to fail for any bit value above 15.

One solution is to redefine it like this:

#ifdef _BV
#undef _BV
#define _BV(b)(1UL<<b)
#endif

This works, but then fails if the bit value is larger than 31 (don’t yell at me to use bit(b) – I know about that and that’s not what I am asking!)

Changing the value of 1 to from “1UL” to “1ULL” makes it work all the way up to 64

Unfortunately, using “1ULL” takes up a bit more flash than “1UL”. So, what I want to do is something like this (pseudo-code):

#ifdef _BV
#undef _BV // undefine any current _BV macro
#if sizeof(b)==1
#define _BV(b)(((uint8_t)(1))<<b) // if 8 bit
#elseif sizeof(b)==2
#define _BV(b)(((uint16_t)(1))<<b) // if 16 bit
#elseif sizeof(b)==4
#define _BV(b)(((uint32_t)(1))<<b) // if 32 bit
#elseif sizeof(b)==8
#define _BV(b)(((uint64_t)(1))<<b) // if (unlikely but possible) 64 bit
#endif

This way, the macro will only take up as much space as necessary. Is this kind of thing possible? I know that it could be done with a small function, but I would rather have the “no overhead” qualities of a macro.

My gut says “hope it can’t be done” because there is no “b” to test until after the macro is defined (can it reside IN the macro?)

Thanks!

AFAIK you can’t do that with a #define. Why must you use _BV macro though? I don’t touch that, it’s just so easy to do 1<<n when needed - and then I can pick whatever datatypes I want if I’m really scrouging for bytes.

I suspect the logic behind using a 16bit datatype was that for the most common use cases, it was plenty, and that datatype didn’t impose speed/memory costs that a larger one would. I am surprised that you are using it with larger datatypes often enough that you find this issue to be significant. \

If you converted it to a function instead of a define, that would let you do that though - and the compiler is probably smart enough to inline that when appropriate without you having to do anything special… What is wrong with this approach?

You are really obsessing about that!

I have to ask: Why? Why is a 32 bit macro version not good enough?

Because I may, at some time, need more than 32 bits (and if you believe that one.......)

You could define it as a byte array. It’d be a bit icky…
#define _bv(num, bit) ((byte *)&num)[bit/8] & (1<<bits%8)

Or something like that

I suppose you could:

#define _BV(b)   (  (b<8)? ((uint8_t)(1) << b) : ((b<16)? ((uint16_t)(1) << b) : ((uint32_t)(1) << b)) )
  • that made me cringe just typing it in, so I stopped at 32bit.

If you like macro’s, separate ones would make more readable sense:

#define bit8_t(b)   ((uint8_t)(1) << b)
#define bit16_t(b) ((uint16_t)(1) << b)
#define bit32_t(b) ((uint32_t)(1) << b)
#define bit64_t(b) ((uint64_t)(1) << b)

Although I prefer either #defining a specific bitmask or doing the “<< bitno” inline.
I think that’s easier to read and a lot less likely to lead to hard-to-find bugs.

Yours,
TonyWilk

P.S. There is the danger that redefining _BV could break existing code. Not too likely in this case, but not impossible.

There is a well known problem with

#define _BV(b)   (  (b<8)? ((uint8_t)(1) << b) : ((b<16)? ((uint16_t)(1) << b) : ((uint32_t)(1) << b)) )

when you write

x = _BV(y++);

or anything that you do not want evaluated more than once.

updated code tags :slight_smile:

robtillaart:
There is a well known problem...

Oh yes. cringe. hard to find bugs. :slight_smile:

Yours,
TonyWilk

Sounds like a job for C++ overloaded inline functions.
Maybe even a template.

@westfw nailed it. I had started the templates but my motivation is low with no specific need. Templates work well for static values. Functions should work well for variable values (inline to eliminate the call / return). An inline function with a GCC built-in should work to select between the two.

I will dig up the templates some time tonight.

No guarantee. Only works with compile-time constants. There are probably better algorithms. Bla. Bla. Bla.

template <unsigned N, unsigned F>
struct Mask2
{
  static auto const value = Mask2<N,F-1>::value;
};

template <unsigned N>
struct Mask2 <N, 1>
{
  static uint8_t const value = (1U << N);  
};

template <unsigned N>
struct Mask2 <N, 9>
{
  static uint16_t const value = (1U << N);  
};

template <unsigned N>
struct Mask2 <N, 17>
{
  static uint32_t const value = (1UL << N);  
};

template <unsigned N>
struct Mask2 <N, 33>
{
  static uint64_t const value = (1ULL << N);  
};

#define BV(b) (Mask2<b,b+1>::value)
/**
template <unsigned int N>
struct Mask
{
    static unsigned int const value = (1U << N);
};

template <>
struct Mask<0>
{
    static uint8_t const value = (1U << 0);
};
**/

void setup( void ) 
{ 
  Serial.begin( 250000 );
  
  Serial.println();
  Serial.println();
  
  auto m1 = BV(31);
  Serial.print( sizeof(m1) );
  Serial.write( '\t' );
  Serial.print( m1, HEX );
  Serial.println();

  auto m2 = BV(16);
  Serial.print( sizeof(m2) );
  Serial.write( '\t' );
  Serial.print( m2, HEX );
  Serial.println();

  auto m3 = BV(15);
  Serial.print( sizeof(m3) );
  Serial.write( '\t' );
  Serial.print( m3, HEX );
  Serial.println();

  auto m4 = BV(8);
  Serial.print( sizeof(m4) );
  Serial.write( '\t' );
  Serial.print( m4, HEX );
  Serial.println();

  auto m5 = BV(7);
  Serial.print( sizeof(m5) );
  Serial.write( '\t' );
  Serial.print( m5, HEX );
  Serial.println();

  auto m6 = BV(0);
  Serial.print( sizeof(m6) );
  Serial.write( '\t' );
  Serial.print( m6, HEX );
  Serial.println();
}

void loop( void ) { }