Is this type punning?

I have a little experiment going. The aim is to collect a number of bits (likely including real world I/O) into one byte and then process them en masse for debouncing, state change detection, etc. and get corresponding individual conditioned bits out the other end for program use.

I suspected my first effort – below – violated the proscription on type punning so I went out on the webs and got wrapped around the axle with all the c++ language spec. chapters and paragraphs and subparagraphs. Went down the arcana rabbit hole with trap representations, common initial sequences, and am still not much more knowledgeable.

So, does this constitute type punning? Does it invoke undefined behavior? The compiler gives no warnings (set to ‘all’). My interpretation says it’s not a trap representation but, what do I know?

Thanks for looking.

// debounce, one-shot, and toggle eight bits at once.

struct eightBits { // gives individual bit access
  bool bit0: 1;  bool bit1: 1;
  bool bit2: 1;  bool bit3: 1;
  bool bit4: 1;  bool bit5: 1;
  bool bit6: 1;  bool bit7: 1;
};

union { // allow mass manipulation and individual testing/setting
  byte allBits;
  eightBits individual;
} inputImage, inputsPreviousState, inputsXORed, inputsANDed, toggledBits, inputRisingOneShots;

const byte buttonOne = A3; // generic small tactile switch
byte counter;

void setup() {
  Serial.begin(115200);
  pinMode(buttonOne, INPUT_PULLUP);
  pinMode(LED_BUILTIN, OUTPUT);
  char shortTitle[30] = "multiple buttons struct/union";
  Serial.println(shortTitle);
  delay(3000);
}

void loop() {
  inputImage.individual.bit2 = !digitalRead(buttonOne);

  inputsXORed.allBits = inputsPreviousState.allBits ^ inputImage.allBits; // detect changed bits
  inputsANDed.allBits = inputsXORed.allBits & inputImage.allBits; // isolate the changed bit(s)
  toggledBits.allBits = toggledBits.allBits ^ inputsANDed.allBits; // if bit went true, toggle
  inputRisingOneShots.allBits = inputsXORed.allBits & inputsANDed.allBits;

  inputsPreviousState.allBits = inputImage.allBits; //update last state so changes can be detected

  if (inputRisingOneShots.individual.bit2) {
    counter++;
  }
  digitalWrite(LED_BUILTIN, toggledBits.individual.bit2);

  Serial.print(inputImage.individual.bit2);
  Serial.print("  ");
  Serial.print(toggledBits.individual.bit2);
  Serial.print("  ");
  Serial.print(counter);
  Serial.println();

}

I next tried using memcpy since that seems to be the approved way of avoiding punning. This version also works and I prefer the aesthetics of it but it uses ~70 bytes more flash and 2 bytes more RAM than the union version. Is there any way to have my cake and eat it too?

// gather eight bits and manipulate as a byte
// using 'address of' operator & makes it work

struct eightBits { // gives individual bit access via bit fields
  bool bit0: 1;  bool bit1: 1;
  bool bit2: 1;  bool bit3: 1;
  bool bit4: 1;  bool bit5: 1;
  bool bit6: 1;  bool bit7: 1;
} inputImage, outputImage, oneShotsOut, inputsANDedOut, toggleBitsOut, temp;

byte internal, inputsXORed, inputsPreviousState, toggledBits, inputsANDed, byteTemp, inputRisingOneShots;

const byte buttonOne = A3; // generic small tactile switch
int counter;

void setup() {
  Serial.begin(115200);
  pinMode(buttonOne, INPUT_PULLUP);
  pinMode(LED_BUILTIN, OUTPUT);
  char shortTitle[30]="multiple buttons memcpy";
  Serial.println(shortTitle);
  delay(3000);
}

void loop() {
  inputImage.bit2 = !digitalRead(buttonOne);
  
  memcpy(&internal, &inputImage, 1); // copy struct inputs to bytes for manipulation
  inputsXORed = inputsPreviousState ^ internal; // detect changed bits

  inputsANDed = inputsXORed & internal; // isolate the changed bit(s)
  toggledBits = toggledBits ^ inputsANDed; // if bit went true, toggle
  inputRisingOneShots = inputsXORed & inputsANDed;
  memcpy(&oneShotsOut, &inputRisingOneShots, 1); // copy one-shots back to legal bit representation
//  byteTemp = inputsANDed ^ toggledBits;
//  memcpy(&inputsANDedOut, &byteTemp, 1);
  memcpy(&toggleBitsOut, &toggledBits, 1); // copy toggles back to legal bit representation
  inputsPreviousState = internal; // update last state so changes can be detected

  if (oneShotsOut.bit2) {
    counter++;
    digitalWrite(LED_BUILTIN, toggleBitsOut.bit2);
  }

  Serial.print(inputImage.bit2);
  Serial.print("\t");
  Serial.print(toggleBitsOut.bit2);
  Serial.print("\t");
  Serial.println(counter);
}

I cannot see how that should be punning. Your problem may be that the data type "bool" is not an 8bit type in all scenarios/platforms, so I would make sure to use and absolute type like "uint8_t" instead of "bool" and "byte".

and if you use this union

union { // allow mass manipulation and individual testing/setting
  byte allBits;
  eightBits individual;
}

by first filling in individual bits and then reading the allBits byte you'll be in UB territory

Why don't you just declare a uint8_t (a byte) and set the bits through bit masking techniques - an OR to set a bit to 1, an AND to clear a bit to 0

uint8_t flag; 

flag |= 0b00001000;   // set bit 3  without touching anything else
flag &= 0b11110111;  // clear bit 3 without touching anything else

if (flag & 0b00001000) { // check if bit 3 is set
  ...
}

if (flag & 0b10001000) { // check if bit 7 OR bit 3 is set
  ...
}

if (flag & 0b10001000 == 0b10001000) { // check if bit 7 AND 3 are both set
  ...
}

there are conveniently written macros under "Bits and Bytes" such as bitSet(), bitClear(), etc if you don't want to play with bitwise operators

I don't don't know if it's type punning. Warnings for that I've seen typically involve pointers.

But, it will invoke undefined behavior because you're storing values in the union as one type and reading them out as another. Note, "Undefined" does not mean it won't work. It means it's not guaranteed to work.

The reason the compiler doesn't complain is that it really has no way of knowing that you're doing this.

What on earth are you guys talking about?

-jim lee

Most of your variables have no business being global. When you memcpy to or from a global variable, the compiler is required to emit actual load or store instructions, because it doesn't know where these variables may be used.
If you make them local, you'll see that the calls to memcpy are completely optimized away.

Memcpy is the correct thing to do when you need type punning. Compilers know this, so type punning using memcpy doesn't generate any instructions and doesn't require any extra memory.
Do not use unions for type punning, it is undefined behavior.

In this case, I see no benefits to creating a struct with bit members. If you really want something to simplify the access of bits, you could use something like std::bitset, see std::bitset<N>::operator[] - cppreference.com. It shouldn't be too hard to make an optimized version if you need to.

Pieter

Danois90:
use and absolute type like "uint8_t" instead of "bool" and "byte".

Easily done -

gfvalvo:
But, it will invoke undefined behavior because you're storing values in the union as one type and reading them out as another. Note, "Undefined" does not mean it won't work. It means it's not guaranteed to work.

So, to stay in bounds the union is out.

J-M-L:
there are conveniently written macros under "Bits and Bytes" such as bitSet(), bitClear(), etc if you don't want to play with bitwise operators.

I did work up a very small demo to test the bitxxx macros. I just like the byte/bit aesthetics of the memcpy().

PieterP:
Most of your variables have no business being global. When you memcpy to or from a global variable, the compiler is required to emit actual load or store instructions, because it doesn't know where these variables may be used.
If you make them local, you'll see that the calls to memcpy are completely optimized away.

This isn't a final version, just testing a few ideas to be able to select something that works and is legal/correct. I did modify the memcpy() sketch to use local variables and saw no change in flash or ram usage. Would a reduction of flash usage be expected by doing this?

PieterP:
If you really want something to simplify the access of bits, you could use something like std::bitset, see std::bitset<N>::operator[] - cppreference.com.

Is there an Arduino compatible version of the utilities library?

PieterP:
It shouldn't be too hard to make an optimized version if you need to.

Easy for you to say! :smiley:

dougp:
Would a reduction of flash usage be expected by doing this?

Not necessarily, but when using local variables, type punning using memcpy emits no additional instructions and requires no extra RAM.

When you make global variables local, you should see a decrease in static RAM usage (unless the compiler/linker optimized away the globals before).

dougp:
Is there an Arduino compatible version of the utilities library?

You have access to the header on all Arduino boards except AVR.
If you're stuck on AVR, you can either run your own implementation or use a third-party port of the standard library. I maintain the Arduino-Helpers library as a support library for my other libraries. It includes such a port of a portion of GCC's standard library implementation, on AVR, it uses this port as a fallback, on all other platforms, it includes the compiler's standard library. You can include it using #include <AH/STL/utility>, after adding the library to your sketch first using #include <Arduino_Helpers.h>.

To create your own version, simply create a class with an array of bytes, and a proxy class that sets and clears bits in this array. For example: Compiler Explorer

PieterP:
You have access to the header on all Arduino boards except AVR.

Is this referring to the below?

PieterP:
You can include it using #include <AH/STL/utility>, after adding the library to your sketch first using #include <Arduino_Helpers.h>.

This small sketch:

// https://github.com/tttapa/Arduino-Helpers

#include <Arduino_Helpers.h>
#include<AH/STL/utility>

void setup() {
  Serial.begin(115200);
  bitset<8> eightbits;
}

void loop() {
}

yields these errors:

C:\Users\User\OneDrive\Documents\Arduino\arduino_helpers\arduino_helpers.ino: In function 'void setup()':
arduino_helpers:8:3: error: 'bitset' was not declared in this scope
bitset<8> eightbits;
^~~~~~
C:\Users\User\OneDrive\Documents\Arduino\arduino_helpers\arduino_helpers.ino:8:3: note: suggested alternative: 'bitSet'
bitset<8> eightbits;
^~~~~~
bitSet
arduino_helpers:8:13: error: 'eightbits' was not declared in this scope
bitset<8> eightbits;
^~~~~~~~~
C:\Users\User\OneDrive\Documents\Arduino\arduino_helpers\arduino_helpers.ino:8:13: note: suggested alternative: 'signbit'
bitset<8> eightbits;
^~~~~~~~~
signbit
Using library Arduino-Helpers-master at version 2.0.0 in folder: C:\Users\User\OneDrive\Documents\Arduino\libraries\Arduino-Helpers-master
exit status 1
'bitset' was not declared in this scope

Using IDE v 1.8.13 and bitset format at https://www.cplusplus.com/reference/bitset/bitset/set/. Prefixing with STD:: doesn't help either.

Ah, I concluded from your reply that bitset was in the header, but it's in the header:

// https://github.com/tttapa/Arduino-Helpers
#include <Arduino_Helpers.h>
#include <AH/STL/bitset>

void setup() {
  Serial.begin(115200);
  while(!Serial);
  std::bitset<8> eightbits;
  eightbits[3] = 1;
  Serial << eightbits << endl;
}

void loop() {}

Prints 00001000 as expected.

PieterP:
Ah, I concluded from your reply that bitset was in the header, but it's in the header:

Nice! I'll have to tinker with this.

Thanks!

 Serial << eightbits << endl;

This alone is worth it. I see many snippets and examples on the web I'd like to try out but, not wanting the hassle of converting and reformatting all the display statements to Arduino style I just move on.

Karma++

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.