bit manipulation - C++ bit compaction - Stack Overflow

admin2025-04-27  3

I'm working on a 16 byte array or 128 bits fully populated with the following bit fields:

struct data {
  uint ttg    : 16;   // time to go, units:mins
   int volts  : 16;   // units:10mV
  uint alarms : 16;   // alarm flags
   int mid_mv : 16;   // mid-point volts, units:10mV
   int aux_in :  2;   // 0:Aux 1:Mid 2:Kelvin 3:none
   int amps   : 22;   // units:ma
  uint Ah     : 20;   // units:0.1Ah
  uint soc    : 10;   // units:0.1%
  uint pad    : 10;   // (pad to 16 bytes)
  };

data datex;

void loadVals(){
  datex.ttg    = 0xFFFE;    // mins = 45.5 days 
  datex.volts  = 0x7FFE;    // * 10mv = 327.66 volts
  datex.alarms = 0x0000;    // no alarms
  datex.mid_mv = 0xFFFE;    // * 10mv = 656.34 V (mid-point voltage)
  datex.aux_in = 0x2;       // mid point V
  datex.amps   = 0x1FFFFE;  // 2097.150A
  datex.Ah     = 0xFFFFE;   // 104857.4Ah
  datex.soc    = 0x320;     // 80% (state of charge)
  datex.pad    = 0;         // padding
}

If I then print:

  #include <Streaming.h>

  Serial  << _HEX(datex.ttg   ) << ',' 
          << _HEX(datex.volts ) << ','
          << _HEX(datex.alarms) << ','
          << _HEX(datex.mid_mv) << '/'
          << _HEX(datex.aux_in) << ',' 
          << _HEX(datex.amps  ) << ','
          << _HEX(datex.Ah    ) << ','
          << _HEX(datex.soc   ) << ','
          << _HEX(datex.pad) << '\n';

I get this output:

FFFE,7FFE,0,FFFFFFFE/FFFFFFFE,1FFFFE,FFFFE,320,0

Questions:

  1. am I approaching this the right way?
  2. Why are only mid_mv, aux_in reporting incorrectly?
  3. how best to combine these into a single 16 byte/128 bit array?

TIA

I'm working on a 16 byte array or 128 bits fully populated with the following bit fields:

struct data {
  uint ttg    : 16;   // time to go, units:mins
   int volts  : 16;   // units:10mV
  uint alarms : 16;   // alarm flags
   int mid_mv : 16;   // mid-point volts, units:10mV
   int aux_in :  2;   // 0:Aux 1:Mid 2:Kelvin 3:none
   int amps   : 22;   // units:ma
  uint Ah     : 20;   // units:0.1Ah
  uint soc    : 10;   // units:0.1%
  uint pad    : 10;   // (pad to 16 bytes)
  };

data datex;

void loadVals(){
  datex.ttg    = 0xFFFE;    // mins = 45.5 days 
  datex.volts  = 0x7FFE;    // * 10mv = 327.66 volts
  datex.alarms = 0x0000;    // no alarms
  datex.mid_mv = 0xFFFE;    // * 10mv = 656.34 V (mid-point voltage)
  datex.aux_in = 0x2;       // mid point V
  datex.amps   = 0x1FFFFE;  // 2097.150A
  datex.Ah     = 0xFFFFE;   // 104857.4Ah
  datex.soc    = 0x320;     // 80% (state of charge)
  datex.pad    = 0;         // padding
}

If I then print:

  #include <Streaming.h>

  Serial  << _HEX(datex.ttg   ) << ',' 
          << _HEX(datex.volts ) << ','
          << _HEX(datex.alarms) << ','
          << _HEX(datex.mid_mv) << '/'
          << _HEX(datex.aux_in) << ',' 
          << _HEX(datex.amps  ) << ','
          << _HEX(datex.Ah    ) << ','
          << _HEX(datex.soc   ) << ','
          << _HEX(datex.pad) << '\n';

I get this output:

FFFE,7FFE,0,FFFFFFFE/FFFFFFFE,1FFFFE,FFFFE,320,0

Questions:

  1. am I approaching this the right way?
  2. Why are only mid_mv, aux_in reporting incorrectly?
  3. how best to combine these into a single 16 byte/128 bit array?

TIA

Share Improve this question edited Jan 11 at 13:53 ChrisJ asked Jan 11 at 13:43 ChrisJChrisJ 231 silver badge4 bronze badges 18
  • 1 You need to describe what the _HEX macro/function is doing. But obviously the root cause is that you are somehow converting signed quantities into unsigned quantities. A value of -2 converted to a 32 bit unsigned value is 0xFFFFFFFE. Which is exactly what you see. – john Commented Jan 11 at 13:47
  • 1 Warning, bitfields probably do NOT do what you think they do. The do NOT have to map onto individual bits. Their behavior is "implementation defined" and can/will vary from compiler to compiler. So my advice do NOT use bitfields at all – Pepijn Kramer Commented Jan 11 at 13:51
  • 2 @ChrisJ Is this some kind of Arduino thing? You should mention that in the question. – john Commented Jan 11 at 13:52
  • 1 Well IDK, but I guess Serial.print(x, HEX) just assumes that x is unsigned. But I am guessing. Perhaps perform your own formatting instead of relying on Serial to do it for you. There's nothing wrong with your structure, the problem is the formatting of the output. – john Commented Jan 11 at 13:56
  • 2 You have datex.mid_mv = 0xFFFE where mid_mv is declared as a signed 16-bit bitfield. 0xFFFE is not representable in a signed 16-bit integer; your program exhibits undefined behavior by way of integer overflow. Same with aux_in - a 2-bit signed integer can only represent values -2, -1, 0 and 1, but not 2. The comment of // 0:Aux 1:Mid 2:Kelvin 3:none suggests you meant the field to be unsigned, but that aint how you declared it. – Igor Tandetnik Commented Jan 11 at 15:02
 |  Show 13 more comments

1 Answer 1

Reset to default 2

am I approaching this the right way?

If you're trying to reduce the overall size of the struct, then, yes, this is a reasonable approach.

However, bitfields in C and C++ structs have traps and pitfalls though. Subtle mistakes are easy to make, which is why some will advice against ever using this approach.

If you're trying to specify precisely what every bit represents, then no, this is not the right approach.

Bitfields in C and C++ structs do not provide enough control to guarantee (in the general case). The same definition may yield a different layout when compiled by a different compiler. In these cases, you must use bitwise operations access individual fields.

Why are only mid_mv, aux_in reporting incorrectly?

They appear to be reporting incorrectly.

datex.mid_mv = 0xFFFE;

The literal 0xFFFE requires 16 bits, which matches the size of the field, but it cannot be represented as a 16-bit signed int because that leaves no room for the sign.

In an ideal world, assigning a value to a variable that cannot represent that value would have been flagged by the compiler as a bug. But this isn't an ideal world. So the representation of the large positive number aliased to a negative number, and then that was sign-extended to a larger type, and then that was (possibly) reinterpreted as an unsigned number.

The solution is to define mid_mv as a type that can represent the entire range of values it needs to. If you'll never need negative voltages, you could use uint instead. If you do need to store negative voltages and also store positive ones larger than INT_MAX, you'll need to use a larger integer type and ensure you don't overly restrict its size in the bitfield specification.

The problem with aux_in is essentially the same, which may be surprising given that 2 is rather small. But here, it's the fact that the field is limited to 2 bits. The solution here is to use an unsigned type.

how best to combine these into a single 16 byte/128 bit array?

Keep in mind, the standards give the compiler more latitude than you might expect. Let me re-iterate that this is fine for packing a struct into less memory, but struct definitions do not provide enough guarantee interoperable binary formats (e.g., on disk or over a connection).

Generally, you have to think about the type, size, and alignment.

I recommend using the explicitly sized types when defining integer bitfields in a struct. This makes it easier to check the struct definition without having to know which compiler and processor is being used.

For flags and simple enumerations, choose an unsigned type.

For integer values, consider both ends of the the range of values you'll need. If you need [0..x], then you can use an unsigned type and you'll need at least n bits where 2^n > x. If you need [x..y] where x < 0, then you need at least n+1 bits where 2^n >= |x| and 2^n > y.

The type of the field determines its alignment requirement even if the allocated bit size would otherwise require less. This means you can get internal padding where you might not expect. Sometimes it makes sense to use a larger type that has the same alignment requirement as one or both of its neighboring fields. Sometimes you have to put the fields in a different order than may seem natural.

转载请注明原文地址:http://www.anycun.com/QandA/1745709336a91133.html