Search code examples
cstructalignmentpaddingpragma

Are structs containing only chars paddingless?


I have some legacy code (target architecture armv5tejl and armv7l) that declares a struct like the one below:

#pragma pack(push,1)
struct modbus_pkt_s
{
    uint8_t d_addr;
    uint8_t cmd;
    uint8_t payload[250];
    uint8_t payload_length;
    uint8_t pkt_length;
    modbus_erc_t erc;
};
#pragma pack(pop) 

where modbus_erc_t is an enumeration:

typedef enum modbus_erc_e modbus_erc_t;

I also have a function that computes the CRC of the modbus packet (it uses d_addr, cmd and payload) and it assumes that these first three struct fields are packed.

I would like to remove #pragma directives since I had issues with unaligned memory access (causing SIGBUS).

Can I safely remove those #pragma directives and assume the first three fields to be packed, given the fact that they are unsigned chars?


Solution

  • The C standard does not specify padding in structures; a compiler is permitted to insert padding between members for any reason. There is generally no reason for a compiler to insert padding before members whose size is one byte or arrays of such, and GCC and Clang do not. (Conceivably, there could be a slight benefit to aligning an array in some architectures, to make the base address simpler. This is not of practical concern here.)

    Even if the earlier members were not one-byte types, you could get the layout you request by removing the #pragma directives that surround the structure and marking each earlier element with __attribute__((__packed__)). For example, this code:

    #include <stdio.h>
    
    
    int main(void)
    {
        typedef struct
        {
            char a __attribute__((__packed__));
            int  b __attribute__((__packed__));
            int  c;
        } foo;
        foo x = {0x1, 0x02030405, 0x06070809};
        unsigned char *p = (void *) &x;
        for (size_t i = 0; i < sizeof x; ++i)
            printf("%02hhx ", p[i]);
        printf("\n");
    }
    

    with Apple Clang 11 shows the first int is packed but the second is aligned normally:

    01 05 04 03 02 00 00 00 09 08 07 06
    

    However, while removing the pragma directives will still leave your earlier members packed in GCC and Clang, it will presumably move the erc member. The structure was packed for a reason, and it is likely the reason is not just so that the CRC could assume the earlier members were packed. This sort of packing is typically done for a structure that is transmitted over some communication channel, and changing the structure on only one side of the channel will break the communication—the sender and the receiver will be using different layouts.

    Further, the fact you got unaligned access faults indicates your program was doing something wrong other than using a packed structure. When a structure is packed, the compiler generates proper code to access its unaligned members. To get a fault, a program must do something else, such as assigning the address of the packed modbus_erc_t member to a modbus_erc_t * that is not marked packed and then attempting to dereference that pointer. If so, that code is broken and should be fixed.