C global anonymous struct / union

前端 未结 6 1070
一个人的身影
一个人的身影 2021-01-26 14:53

I have a uint64 variable which often only requires high or low 32 bit access. I am using a 32-bit ARM Cortex M0, and to help with speed and I am trying to overlap the uint64 var

相关标签:
6条回答
  • 2021-01-26 14:59

    You could get fancy and use assembly to achieve what you want, or use C++.

    In assembly

                    EXPORT AB
                    EXPORT A
                    EXPORT B
    AB
    A               SPACE 4
    B               SPACE 4
    

    In C:

    extern uint64_t AB;
    extern uint32_t A;
    extern uint32_t B;
    

    Then do what you want.

    In C++, something like this:

    union MyUnionType
    {
        uint64_t ab;
        struct 
        {
            uint32_t a;
            uint32_t b;
        };
    } ;
    
    MyUnionType MyUnion;
    
    uint64_t &ab = MyUnion.ab;
    uint32_t &a  = MyUnion.a;
    uint32_t &b  = MyUnion.b;
    

    But, to be honest, this is all wasted effort. Accessing a or MyUnion.a is going to have the same code generated by the compiler. It knows the size and offsets of everything, so it's not going to calculate anything at runtime, but just load from the right address it knows ahead of time.

    0 讨论(0)
  • 2021-01-26 14:59

    To clarify what I've learned:

    It turns out C doesn't allow global members of anonymous structs/unions. Oh well. But using named structs/unions generates efficient code anyway, as the member offset is known at compile time and can be added without incurring extra instructions.

    Regarding using shifts & masks instead of a union, this may work well for reads, but for writes it results in extra instructions (9 vs 5) and pointless accesses to the low uint32. On the other hand, it is more endian portable than a union, but that isn't important in my application.

    union status {
       struct { uint32_t user, system; };
       uint64_t all;
    };
    volatile union status status;
    
    status.system |= 1u;                // write to high uint32 member directly
    2301        movs r3, #1
    4A02        ldr r2, 0x00002910
    6851        ldr r1, [r2, #4]
    430B        orrs r3, r1
    6053        str r3, [r2, #4]
    
    status.all |= ((uint64_t)1)<<32;    // write to full uint64
    2001        movs r0, #1
    4905        ldr r1, 0x00002910
    680C        ldr r4, [r1]
    684D        ldr r5, [r1, #4]
    4328        orrs r0, r5
    1C22        adds r2, r4, #0
    1C03        adds r3, r0, #0
    600A        str r2, [r1]            // this is not atomic, and pointless
    604B        str r3, [r1, #4]        // this is the important part
    
    0 讨论(0)
  • 2021-01-26 15:01

    You define a union without an instance, which means that there is no union object containing the members. You could probably do something like this:

    main.h:

    typedef union {
      uint64_t ab;
      struct { uint32_t a, b; };
    } u_t;
    
    extern u_t u;
    

    main.c:

    u_t u = { .a = 1; };
    

    And if you really wanted to (in main.h):

    #define a u.a
    #define b u.b
    #define ab u.ab
    

    If you do use #defines, be wary that they affect any declaration/use of the identifiers (a, b, ab), even those in a different scope. I recommend that instead you just access the values explictly via the u object (as u.a, u.b, u.ab).

    I have removed volatile from the declarations because I highly doubt you really needed it. But you can certainly add it back if you wish.

    (Note: the question originally had code split across two files, main.h and main.c. My answer correspondingly has code for two files. However, these could be combined into one easily).

    0 讨论(0)
  • 2021-01-26 15:08

    There is actually little to no reason to use a union. Instead, use shift/mask, possibly in inline functions to extract the two halves:

    static inline uint32_t upper(uint64_t val)
        { return (uint32_t)(val >> 32); }
    
    static inline uint32_t lower(uint64_t val)
        { return (uint32_t)val; }
    

    This will be very likely optimized by the compiler to the same code as the union-approach.

    However, as you are refering to anonymous struct/union members: Omitting the name is a feature of the struct/union which includes the member, not the member being a struct/union. So you can use:

    union {
        uint64_t v64;
        struct {
            uint32_t l32;
            uint32_t h32;
        };  // here the name can been omitted
    } my_flex_var;
    

    Problems are:

    • non-portable, possibly depends on ABI/PCS used (although the AAPCS is quite clear about this, there are others which might differ).
    • depends on little endian (some ARM CPUs/implementations allow for big-endian, too).
    • cannot be used on instances defined already as uint64_t.
    • less explicit than function loads.
    • might add overhead for compound types (non-register argument passing, placement on stack instead of registers, etc.

    Normal usage of a volatile is to load/store it always with full size. If that is not the case, a race condition might uccur and you are in the world of locking/mutex, etc. which makes things very much more complicated. If the two fields are only loosely related, you are very likely better off with two 32 bit variables or a struct thereof.

    Typical usage:

    volatile uint64_t v64;
    
    void int_handler(void)
    {
        uint64_t vt = v64;
    
        uint32_t l = lower(vt);
        uint32_t h = higher(vt);
    
    }
    

    This ensures the variable is read only once. With a proper compiler, the assignments to l, h will not generate any code, but the registers with vt are used. That depends, of course on your code, but even if there is some overhead, that will be negligible.

    (Sitenote: This is from my own practise as a long time embedded programmer)

    0 讨论(0)
  • 2021-01-26 15:10

    It's simply not possible to do like that. A global variable (like the ones you declare in the header file) are not the same as members of an anonymous structure or union, that simply wont work.

    And having anonymous structures or unions won't help with pointer arithmetic, the structure will still sit somewhere in memory, and the compiler uses offsets from the structures base-address to find out where the members are. However, since both the base-address and member offsets are know at time of compilation, the compiler will generally be able to generate code to access the member directly, just like any other variable. Look at the generated code if you are unsure.

    So skip the nonsense with anonymous structures, define them properly in the header files, and declare variables of those structures in the header files too, while defining those variables in some source file.

    So for the header file:

    union you_union
    {
        uint64_t ab;
        struct { uint32_t a, b; };
    };
    
    extern union your_union your_union;
    

    And in a source file

    union your_union your_union;
    
    0 讨论(0)
  • 2021-01-26 15:22

    No, of course you can't access members of a non-instantiated type. Where would the data be stored?

    You should just make the union visible, and trust the compiler to generate efficient accesses. I don't think there's going to be any "pointer arithmetic", at least not at run-time.

    0 讨论(0)
提交回复
热议问题