incorrect behavior with [somewhat] complex struct...

I have a somewhat complex structure in my C code, and driver functions to read or write data with it.

The simpler parts of the struct (such as ioflags2 below) and their driver functions work just fine. However, the more complex parts of the struct (such as ioflags3) do not.

The symptom is that only the drivers for the first part of the more complex parts of the structure work right, and the rest don't appear to change the "word" part of the structure.

However, if I step through the code with the simulator, all of the individual parts of the more complex parts of the struct work just fine with the driver functions, but the "word" part doesn't change (well, except for the 1st part of the more complex parts, i.e., module1 works, but module2 - module5 don't).

Finally, I cut/paste the whole struct and drivers to a Visual C++ program and stepped through the code with their debugger. *ALL* of the driver functions had the expected effect on *ALL* of the parts of the whole struct.

Can anyone tell me what, if anything, is wrong with this code, and how to make it work with KEIL's ARM tools?

  union{
    u16 word;
    struct{
      unsigned sensor1:1;
      unsigned sensor2:1;
      unsigned sensor3:1;
      unsigned sensor4:1;
      unsigned sensor5:1;
      unsigned sensor6:1;
      unsigned sensor7:1;
      unsigned sensor8:1;
      unsigned sensor9:1;
      unsigned sensor10:1;
      unsigned reserved_bit_10:1;
      unsigned reserved_bit_11:1;
      unsigned reserved_bit_12:1;
      unsigned reserved_bit_13:1;
      unsigned reserved_bit_14:1;
      unsigned reserved_bit_15:1;
    }bit;
  }ioflags2;

  union{
    u16 word;
    struct{
      struct{
        struct{
          unsigned run:1;
          unsigned direction:1;
        }stepper_motor;
      }module1;

      union{
        struct{
          unsigned extend:1;
          unsigned retract:1;
        }ioctl;
        struct{
          unsigned run:1;
          unsigned direction:1;
        }dc_motor;
      }module2;

      union{
        struct{
          unsigned open:1;
          unsigned close:1;
        }ioctl;
        struct{
          unsigned run:1;
          unsigned direction:1;
        }dc_motor;
      }module3;

      union{
        struct{
          unsigned open:1;
          unsigned close:1;
        }ioctl;
        struct{
          unsigned run:1;
          unsigned direction:1;
        }dc_motor;
      }module4;

      union{
        struct{
          unsigned reserved_bit_0:1;
          unsigned fan:1;
        }ioctl;
        struct{
          unsigned direction:1;
          unsigned run:1;
        }dc_motor;
      }module5;

      unsigned reserved_bit_10:1;
      unsigned reserved_bit_11:1;
      unsigned reserved_bit_12:1;
      unsigned reserved_bit_13:1;
      unsigned reserved_bit_14:1;
      unsigned reserved_bit_15:1;
    }bit;
  }ioflags3;

And here's how my drivers manipulate the structure.

int set_module2_retract(int state){
  int previous_state;

  ASSERT(module2_type() == TYPE_IOCTL);

  if(module2_type() != TYPE_IOCTL){
    return(-1);
  }

  if(state){
    set_module1_extend(0); // protect the hardware!
  }

  previous_state = variable.ioflags3.bit.module2.ioctl.retract;

  variable.ioflags3.bit.module2.ioctl.retract = state ? 1u : 0u;

  return(previous_state);
}

Parents
  • You are making expectations that are not always true.

    Note that the bit fields in C have a number of details that are left to the implementation.

    You have a struct that contains multiple unions that each contain bitfields. Don't expect the compiler to concatenate all this into a single continuous bit field container.

    gcc or M$ Visual C++ that generates 32-bit code will think that sizeof(ioflags2) is 4 bytes (because the bitfields are unsigned). It will think the ioflags3 combo is 24 bytes. Replacing the unsigned bit fields with u16 would shrink ioflags2 to 2 bytes and ioflags3 to 12 bytes.

    Look closer at ioflags3.

    It is a union of a u16 and a struct.
    The u16 is 2 bytes large. But how large is the struct?
    The struct has the following elements:
    module1 - maybe 1, maybe 2, maybe 4, ... bytes
    module2 - maybe 1, maybe 2, ...
    module3 - maybe 1, maybe 2, ...
    module4 - maybe 1, maybe 2, ...
    module5 - maybe 1, maybe 2, ...

    For 32-bit gcc, each of module1..module5 will be four bytes large if the bit fields in them are declared unsigned, since unsigned is a 32-bit integer.

    For 32-bit gcc, each of module1..module5 will be two bytes large if the bit fields in them are declared u16.

    But the bit fields in module1 will not share the same unsigned or u16 as the bitfields in module2 or module3, even if the container is large enough to store the 10 bits.

    Never expect to get portable code when using bit fields. If you do use them - verify with sizeof() that you get the expected size. And verify that each field will affect exactly the bits you think they will affect.

Reply
  • You are making expectations that are not always true.

    Note that the bit fields in C have a number of details that are left to the implementation.

    You have a struct that contains multiple unions that each contain bitfields. Don't expect the compiler to concatenate all this into a single continuous bit field container.

    gcc or M$ Visual C++ that generates 32-bit code will think that sizeof(ioflags2) is 4 bytes (because the bitfields are unsigned). It will think the ioflags3 combo is 24 bytes. Replacing the unsigned bit fields with u16 would shrink ioflags2 to 2 bytes and ioflags3 to 12 bytes.

    Look closer at ioflags3.

    It is a union of a u16 and a struct.
    The u16 is 2 bytes large. But how large is the struct?
    The struct has the following elements:
    module1 - maybe 1, maybe 2, maybe 4, ... bytes
    module2 - maybe 1, maybe 2, ...
    module3 - maybe 1, maybe 2, ...
    module4 - maybe 1, maybe 2, ...
    module5 - maybe 1, maybe 2, ...

    For 32-bit gcc, each of module1..module5 will be four bytes large if the bit fields in them are declared unsigned, since unsigned is a 32-bit integer.

    For 32-bit gcc, each of module1..module5 will be two bytes large if the bit fields in them are declared u16.

    But the bit fields in module1 will not share the same unsigned or u16 as the bitfields in module2 or module3, even if the container is large enough to store the 10 bits.

    Never expect to get portable code when using bit fields. If you do use them - verify with sizeof() that you get the expected size. And verify that each field will affect exactly the bits you think they will affect.

Children
More questions in this forum