This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

Compiler/MSP432P401R: Inconsistent operation of __attribute__ ((__packed__)) depending on where it is placed and whether the struct is named

Part Number: MSP432P401R


Tool/software: TI C/C++ Compiler

Hi all,

Using compiler TI v16.9.7.LTS.

I've come across a problem where the packed attribute doesn't seem to work depending on exactly where it is placed. 

Here is a code example that illustrates the issue

/* DriverLib Includes */
#include <ti/devices/msp432p4xx/driverlib/driverlib.h>

/* Standard Includes */
#include <stdint.h>
#include <stdbool.h>


typedef struct st_outerStruct_1
{
    union
    {
        uint8_t rawData[6];

        struct st_innerSt1
        {
            // Byte 1
            uint8_t byte1_bit1 : 1;
            uint8_t byte1_bit2 : 1;
            uint8_t byte1_bit3 : 1;
            uint8_t byte1_bit4 : 1;
            uint8_t byte1_bit5 : 1;
            uint8_t byte1_bit6 : 1;
            uint8_t byte1_bit7 : 1;
            uint8_t byte1_bit8 : 1;

            // Byte 2
            uint8_t byte2_bit1 : 1;
            uint8_t byte2_bit2 : 1;
            uint8_t byte2_bit3 : 1;
            uint8_t byte2_bit4 : 1;
            uint8_t byte2_bit5 : 1;
            uint8_t byte2_bit6 : 1;
            uint8_t byte2_bit7 : 1;
            uint8_t byte2_bit8 : 1;

            // Byte 3,4,5,6
            uint32_t number_u32;
        } innerSt1 __attribute__ ((__packed__));
    } __attribute__ ((__packed__));
} outerStruct_1;

typedef struct st_outerStruct_2
{
    union
    {
        uint8_t rawData[6];

        struct __attribute__ ((__packed__)) st_innerSt2
        {
            // Byte 1
            uint8_t byte1_bit1 : 1;
            uint8_t byte1_bit2 : 1;
            uint8_t byte1_bit3 : 1;
            uint8_t byte1_bit4 : 1;
            uint8_t byte1_bit5 : 1;
            uint8_t byte1_bit6 : 1;
            uint8_t byte1_bit7 : 1;
            uint8_t byte1_bit8 : 1;

            // Byte 2
            uint8_t byte2_bit1 : 1;
            uint8_t byte2_bit2 : 1;
            uint8_t byte2_bit3 : 1;
            uint8_t byte2_bit4 : 1;
            uint8_t byte2_bit5 : 1;
            uint8_t byte2_bit6 : 1;
            uint8_t byte2_bit7 : 1;
            uint8_t byte2_bit8 : 1;

            // Byte 3,4,5,6
            uint32_t number_u32;
        } innerSt2;
    } __attribute__ ((__packed__));
} outerStruct_2;

typedef struct st_outerStruct_3
{
    union
    {
        uint8_t rawData[6];

        struct
        {
            // Byte 1
            uint8_t byte1_bit1 : 1;
            uint8_t byte1_bit2 : 1;
            uint8_t byte1_bit3 : 1;
            uint8_t byte1_bit4 : 1;
            uint8_t byte1_bit5 : 1;
            uint8_t byte1_bit6 : 1;
            uint8_t byte1_bit7 : 1;
            uint8_t byte1_bit8 : 1;

            // Byte 2
            uint8_t byte2_bit1 : 1;
            uint8_t byte2_bit2 : 1;
            uint8_t byte2_bit3 : 1;
            uint8_t byte2_bit4 : 1;
            uint8_t byte2_bit5 : 1;
            uint8_t byte2_bit6 : 1;
            uint8_t byte2_bit7 : 1;
            uint8_t byte2_bit8 : 1;

            // Byte 3,4,5,6
            uint32_t number_u32;
        } __attribute__ ((__packed__));
    } __attribute__ ((__packed__));
} outerStruct_3;
int main(void) { outerStruct_1 st1; // Inner struct is named, packed attribute at the END of the inner struct outerStruct_2 st2; // Inner struct is named, packed attribute at the START of the inner struct outerStruct_3 st3; // Inner struct is NOT named, packed attribute at the END of the inner struct st1.innerSt1.number_u32 = 0xaabbccdd; // rawData field is wrong - the upper 2 bytes have been dropped - looks like number_u32 has been 32bit aligned. st2.innerSt2.number_u32 = 0xaabbccdd; // rawData field is right st3.number_u32 = 0xaabbccdd; // rawData field is right while(1){} }

In the above code I have three nearly identically defined structs.  Within each struct I use a union so that I can look at the raw bytes.

In this particular example, the outer struct is probably redundant, but in my full code I have additional fields inside the outer struct (not just the union) so I've left the outer struct in place.

After running the above code, I would execpt rawData[2] = 0xdd, rawData[3] = 0xcc, rawData[4] = 0xbb rawData[5] = 0xaa for st1, st2 and st3

But for st1 it seems the compiler has 32bit aligned number_u32 so I get rawData[2] = 0x00, rawData[3] = 0x00, rawData[4] = 0xdd rawData[5] = 0xcc.

This problem only seems to happen if you (a) name the inner structure AND (b) put the attribute at the end of the inner structure definition. 

Is this expected behaviour?  If it is, could you please point me to appropriate documentation on how the packed attribute works?  I'd like to understand just what is going on so I can avoid this issue in future.

Cheers

Julian

  • I cannot run the code.  I compile it and inspect the compiler generated assembly.  When I compile that example, I see the same assembly code for all the assignment statements.  But I had to guess at the build options.  

    Please submit a test case as described in the article How to Submit a Compiler Test Case.  That should allow me to see different assembly code for one of those assignments.

    Thanks and regards,

    -George

  • Sorry about that,

    I've attached the entire test project (CCS 7.4), and the preprocessed file.

    Compiler is TI v16.9.7LTS

    Build command for the file  is


    "C:/ti/ccs740/ccsv7/tools/compiler/ti-cgt-arm_16.9.7.LTS/bin/armcl" -mv7M4 --code_state=16 --float_support=FPv4SPD16 -me --include_path="C:/Users/Kolodko Julian/workspace_v7/structPackProblem" --include_path="C:/ti/simplelink_msp432p4_sdk_1_60_00_12/source" --include_path="C:/ti/simplelink_msp432p4_sdk_1_60_00_12/source/third_party/CMSIS/Include" --include_path="C:/ti/ccs740/ccsv7/tools/compiler/ti-cgt-arm_16.9.7.LTS/include" --advice:power=none --define=__MSP432P401R__ --define=DeviceFamily_MSP432P401x -g --preproc_with_comment --preproc_with_compile --diag_warning=225 --diag_warning=255 --diag_wrap=off --display_error_number --gen_func_subsections=on  "../structPackProblem.c"

    Assembly for main() is as follows - st1 is definately being handled differently to st2 & st3

    main():
    000003c4:   F1AD0D18            sub.w      sp, sp, #0x18
    000003c5:   18F1               .word       0x000018f1
    000003c7:   040D               .word       0x0000040d
    113         st1.innerSt1.number_u32 = 0xaabbccdd;  // rawData field is wrong - the upper 2 bytes have been dropped - looks like number_u32 has been 32bit aligned.
    000003c8:   4804                ldr        r0, [pc, #0x10]
    000003c9:   0148               .word       0x00000148
    000003ca:   9001                str        r0, [sp, #4]
    114         st2.innerSt2.number_u32 = 0xaabbccdd;  // rawData field is right
    000003cc:   4803                ldr        r0, [pc, #0xc]
    000003cd:   CD48               .word       0x0000cd48
    000003ce:   F8CD000A            str.w      r0, [sp, #0xa]
    115         st3.number_u32 = 0xaabbccdd;           // rawData field is right
    000003d2:   4802                ldr        r0, [pc, #8]
    000003d4:   F8CD0012            str.w      r0, [sp, #0x12]
    117         while(1){}

    /* Standard Includes */
    /*****************************************************************************/
    /* STDINT.H v16.9.7                                                          */
    /*                                                                           */
    /* Copyright (c) 2002-2018 Texas Instruments Incorporated                    */
    /* http://www.ti.com/                                                        */
    /*                                                                           */
    /*  Redistribution and  use in source  and binary forms, with  or without    */
    /*  modification,  are permitted provided  that the  following conditions    */
    /*  are met:                                                                 */
    /*                                                                           */
    /*     Redistributions  of source  code must  retain the  above copyright    */
    /*     notice, this list of conditions and the following disclaimer.         */
    /*                                                                           */
    /*     Redistributions in binary form  must reproduce the above copyright    */
    /*     notice, this  list of conditions  and the following  disclaimer in    */
    /*     the  documentation  and/or   other  materials  provided  with  the    */
    /*     distribution.                                                         */
    /*                                                                           */
    /*     Neither the  name of Texas Instruments Incorporated  nor the names    */
    /*     of its  contributors may  be used to  endorse or  promote products    */
    /*     derived  from   this  software  without   specific  prior  written    */
    /*     permission.                                                           */
    /*                                                                           */
    /*  THIS SOFTWARE  IS PROVIDED BY THE COPYRIGHT  HOLDERS AND CONTRIBUTORS    */
    /*  "AS IS"  AND ANY  EXPRESS OR IMPLIED  WARRANTIES, INCLUDING,  BUT NOT    */
    /*  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR    */
    /*  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT    */
    /*  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,    */
    /*  SPECIAL,  EXEMPLARY,  OR CONSEQUENTIAL  DAMAGES  (INCLUDING, BUT  NOT    */
    /*  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,    */
    /*  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY    */
    /*  THEORY OF  LIABILITY, WHETHER IN CONTRACT, STRICT  LIABILITY, OR TORT    */
    /*  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE    */
    /*  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.     */
    /*                                                                           */
    /*****************************************************************************/
    
    /* 7.18.1.1 Exact-width integer types */
    
        typedef   signed char   int8_t;
        typedef unsigned char  uint8_t;
        typedef          short  int16_t;
        typedef unsigned short uint16_t;
        typedef          int    int32_t;
        typedef unsigned int   uint32_t;
    
    
        typedef          long long  int64_t;
        typedef unsigned long long uint64_t;
    
    /* 7.18.1.2 Minimum-width integer types */
    
        typedef  int8_t   int_least8_t;
        typedef uint8_t  uint_least8_t;
    
        typedef  int16_t  int_least16_t;
        typedef uint16_t uint_least16_t;
        typedef  int32_t  int_least32_t;
        typedef uint32_t uint_least32_t;
    
    
        typedef  int64_t  int_least64_t;
        typedef uint64_t uint_least64_t;
    
    /* 7.18.1.3 Fastest minimum-width integer types */
    
        typedef  int32_t  int_fast8_t;
        typedef uint32_t uint_fast8_t;
        typedef  int32_t  int_fast16_t;
        typedef uint32_t uint_fast16_t;
    
        typedef  int32_t  int_fast32_t;
        typedef uint32_t uint_fast32_t;
    
    
        typedef  int64_t  int_fast64_t;
        typedef uint64_t uint_fast64_t;
    
    /* 7.18.1.4 Integer types capable of holding object pointers */
        typedef          int intptr_t;
        typedef unsigned int uintptr_t;
    
    /* 7.18.1.5 Greatest-width integer types */
        typedef          long long intmax_t;
        typedef unsigned long long uintmax_t;
    
    /* 
       According to footnotes in the 1999 C standard, "C++ implementations
       should define these macros only when __STDC_LIMIT_MACROS is defined
       before <stdint.h> is included." 
    */
    
    /* 7.18.2 Limits of specified width integer types */
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    /* 7.18.3 Limits of other integer types */
    
    
    
    
    
    
    
    /* 7.18.4.1 Macros for minimum-width integer constants */
    
    /*
       There is a defect report filed against the C99 standard concerning how 
       the (U)INTN_C macros should be implemented.  Please refer to --
       http://wwwold.dkuug.dk/JTC1/SC22/WG14/www/docs/dr_209.htm 
       for more information.  These macros are implemented according to the
       suggestion given at this web site.
    */
    
    
    
    
    /* 7.18.4.2 Macros for greatest-width integer constants */
    
    
    
    
    
    typedef struct st_outerStruct_1
    {
        union
        {
            uint8_t rawData[6];
    
            struct st_innerSt1
            {
                // Byte 1
                uint8_t byte1_bit1 : 1;
                uint8_t byte1_bit2 : 1;
                uint8_t byte1_bit3 : 1;
                uint8_t byte1_bit4 : 1;
                uint8_t byte1_bit5 : 1;
                uint8_t byte1_bit6 : 1;
                uint8_t byte1_bit7 : 1;
                uint8_t byte1_bit8 : 1;
    
                // Byte 2
                uint8_t byte2_bit1 : 1;
                uint8_t byte2_bit2 : 1;
                uint8_t byte2_bit3 : 1;
                uint8_t byte2_bit4 : 1;
                uint8_t byte2_bit5 : 1;
                uint8_t byte2_bit6 : 1;
                uint8_t byte2_bit7 : 1;
                uint8_t byte2_bit8 : 1;
    
                // Byte 3,4,5,6
                uint32_t number_u32;
            } innerSt1 __attribute__ ((__packed__));
        } __attribute__ ((__packed__));
    } outerStruct_1;
    
    typedef struct st_outerStruct_2
    {
        union
        {
            uint8_t rawData[6];
    
            struct __attribute__ ((__packed__)) st_innerSt2
            {
                // Byte 1
                uint8_t byte1_bit1 : 1;
                uint8_t byte1_bit2 : 1;
                uint8_t byte1_bit3 : 1;
                uint8_t byte1_bit4 : 1;
                uint8_t byte1_bit5 : 1;
                uint8_t byte1_bit6 : 1;
                uint8_t byte1_bit7 : 1;
                uint8_t byte1_bit8 : 1;
    
                // Byte 2
                uint8_t byte2_bit1 : 1;
                uint8_t byte2_bit2 : 1;
                uint8_t byte2_bit3 : 1;
                uint8_t byte2_bit4 : 1;
                uint8_t byte2_bit5 : 1;
                uint8_t byte2_bit6 : 1;
                uint8_t byte2_bit7 : 1;
                uint8_t byte2_bit8 : 1;
    
                // Byte 3,4,5,6
                uint32_t number_u32;
            } innerSt2;
        } __attribute__ ((__packed__));
    } outerStruct_2;
    
    typedef struct st_outerStruct_3
    {
        union
        {
            uint8_t rawData[6];
    
            struct
            {
                // Byte 1
                uint8_t byte1_bit1 : 1;
                uint8_t byte1_bit2 : 1;
                uint8_t byte1_bit3 : 1;
                uint8_t byte1_bit4 : 1;
                uint8_t byte1_bit5 : 1;
                uint8_t byte1_bit6 : 1;
                uint8_t byte1_bit7 : 1;
                uint8_t byte1_bit8 : 1;
    
                // Byte 2
                uint8_t byte2_bit1 : 1;
                uint8_t byte2_bit2 : 1;
                uint8_t byte2_bit3 : 1;
                uint8_t byte2_bit4 : 1;
                uint8_t byte2_bit5 : 1;
                uint8_t byte2_bit6 : 1;
                uint8_t byte2_bit7 : 1;
                uint8_t byte2_bit8 : 1;
    
                // Byte 3,4,5,6
                uint32_t number_u32;
            } __attribute__ ((__packed__));
        } __attribute__ ((__packed__));
    } outerStruct_3;
    
    int main(void)
    {
      outerStruct_1 st1;  // Inner struct is named, packed attribute at the END of the inner struct
      outerStruct_2 st2;  // Inner struct is named, packed attribute at the START of the inner struct
      outerStruct_3 st3;  // Inner struct is NOT named, packed attribute at the END of the inner struct
    
      st1.innerSt1.number_u32 = 0xaabbccdd;  // rawData field is wrong - the upper 2 bytes have been dropped - looks like number_u32 has been 32bit aligned.
      st2.innerSt2.number_u32 = 0xaabbccdd;  // rawData field is right
      st3.number_u32 = 0xaabbccdd;           // rawData field is right
    
      while(1){}
    }
    

    StructPackProblem.zip

  • Thank you for submitting a test case.  I can reproduce the problem.  I filed CODEGEN-4757 in the SDOWP system to have this investigated.  You are welcome to follow it with the SDOWP link below in my signature.

    Thanks and regards,

    -George

  • There is no compiler error here.  Rather, the placement of the attribute in the first case is incorrect.  Please note that according to GCC Type Attributes, you may specify the packed attribute only past the closing brace of a definition, or between the struct or union tag and the name of the type. In the example, when the attribute is moved between the closing brace of the definition and its name (rather than after the name), the offset is correct.  

    The correction is in bold below:

    struct st_innerSt1
    {
    // Byte 1
    uint8_t byte1_bit1 : 1;
    uint8_t byte1_bit2 : 1;
    uint8_t byte1_bit3 : 1;
    uint8_t byte1_bit4 : 1;
    uint8_t byte1_bit5 : 1;
    uint8_t byte1_bit6 : 1;
    uint8_t byte1_bit7 : 1;
    uint8_t byte1_bit8 : 1;

    // Byte 2
    uint8_t byte2_bit1 : 1;
    uint8_t byte2_bit2 : 1;
    uint8_t byte2_bit3 : 1;
    uint8_t byte2_bit4 : 1;
    uint8_t byte2_bit5 : 1;
    uint8_t byte2_bit6 : 1;
    uint8_t byte2_bit7 : 1;
    uint8_t byte2_bit8 : 1;

    // Byte 3,4,5,6
    uint32_t number_u32;
    } __attribute__ ((__packed__)) innerSt1;

    See:

    gcc.gnu.org/.../Type-Attributes.html

  • Could you please clarify a couple of points

    1. You talk about GCC, but I'm using the TI compiler. Does that make any difference?

    2. If my placement of the attribute is wrong, why don't I get an an error or warning?

    3. If my placement of the attribute is correct, but has a different meaning, where is the documentation that specifies that meaning?

    4. In https://gcc.gnu.org/onlinedocs/gcc-4.0.4/gcc/Type-Attributes.html I see examples where attribute is placed

    • after the struct keyword (e.g. "struct __attribute__ ((__packed__)) my_packed_struct").  This is consistent with all the examples in TI document SPNU151N section 5.16.4.
    • between the closing brace and semicolon (for cases with no name at the end of the definition)  (e.g. "struct S { short f[3]; } __attribute__ ((aligned));")
    • after the name and before the semicolon (e.g. "} wait_status_ptr_t __attribute__ ((__transparent_union__));").  This is the placement which you say is incorrect (but maybe this is incorrect only for the __packed__ attribute????).
    • I can't find an example where the attribute is placed between closing brace and name as you show.

    5. gcc.gnu.org/.../Attribute-Syntax.html says "An attribute specifier list may appear immediately before the comma, = or semicolon terminating the declaration of an identifier other than a function definition. At present, such attribute specifiers apply to the declared object or function".

    I don't 100% follow this, but it does **seem** to say that my positioning is correct.

    Thank you for your assistance in understanding this issue.
    Julian

  • Hi,

    1) The packed attribute is a GCC type attribute that our compiler supports.  GCC attributes follow GCC attribute rules.

    2-3) I can see no GCC documentation that specifies placing a packed attribute after the name and before the semicolon is correct.  Our parser does not apply the attribute correctly in this case, and the documentation I have found does not permit it.  Therefore, I can only conclude this is undefined behavior.  Our parser does not emit an error in this case, but does not apply the attribute.  We use the EDG parser in our compiler tools.  The parser does not emit an error for every possible human error.  I suggest following the documented rules in applying the GCC attributes you use.  We can file a request with EDG to emit an error or warning when the attribute comes after the name and before the semicolon, but we can't guarantee whether or when they would implement it.

    4) Different attributes allow different placement rules.  Please refer to the following links.  Both documents specify that the packed attribute must come just after the closing brace of the definition, or between the tag and the name.  I am copying and pasting directly from the linked documentation here:

    From the GCC 4.6.3 type attributes specs:

    For an enum, struct or union type, you may specify attributes either between the enum, struct or union tag and the name of the type, or just past the closing curly brace of the definition. The former syntax is preferred.

    See link:

    gcc.gnu.org/.../Type-Attributes.html

    From the GCC 3.3 type attributes specs: (As you can see, different type attributes allow different placement): 

    You may specify the aligned and transparent_union attributes either in a typedef declaration or just past the closing curly brace of a complete enum, struct or union type definition and the packed attribute only past the closing brace of a definition.

    You may also specify attributes between the enum, struct or union tag and the name of the type rather than after the closing brace.

    See link:

    gcc.gnu.org/.../Type-Attributes.html

  • Sorry, I overlooked #5.

    For #5, even the link you provide states:

    An attribute specifier list may appear as part of a struct, union or enum specifier. It may go either immediately after the struct, union or enum keyword, or after the closing brace. It is ignored if the content of the structure, union or enumerated type is not defined in the specifier in which the attribute specifier list is used—that is, in usages such as struct __attribute__((foo)) bar with no following opening brace. Where attribute specifiers follow the closing brace, they are considered to relate to the structure, union or enumerated type defined, not to any enclosing declaration the type specifier appears in, and the type defined is not complete until after the attribute specifiers.

    As for the passage you quote, the reason it is not relevant to packed is that it states in this case, the attribute applies to the declared object. Packed only applies to the struct or union type, not the object. That is, all structs of that type must have the same layout in their actual type definition to be packed. You cannot specify a specifically declared struct object as packed. This is because the compiler must know how to consistently access any member of any struct of that type in the same manner.