C全局匿名结构/联合

时间:2015-10-02 11:55:17

标签: c struct arm embedded anonymous

我有一个uint64变量,通常只需要高或低32位访问。我使用的是32位ARM Cortex M0,为了提高速度,我试图将uint64变量与C中的两个uint32变量重叠,使用匿名结构,希望避免指针算法访问成员。

我想做的是什么?可能是使用命名联盟同样快,但现在我只是感兴趣,如果它可以没有。以下内容无法成功编译:

http://goo.gl/ejx37y

#include <stdint.h>

volatile union {
  uint64_t ab;
  struct { uint32_t a, b; };
};

int main(void)
{
  a = 1;
};

6 个答案:

答案 0 :(得分:6)

您定义没有实例的联合,这意味着没有包含成员的联合对象。你可能会做这样的事情:

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; };

如果你真的想(在main.h中):

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

如果你确实使用#define,请小心它们会影响任何声明/使用标识符(a,b,ab),甚至是那些不同范围的标识符。我建议您只需通过u对象(u.au.bu.ab)明确访问这些值。

我已从声明中删除了volatile因为我非常怀疑你真的需要它。但如果你愿意,你当然可以加回来。

(注意:这个问题最初的代码分为两个文件,main.h和main.c.我的答案相应地有两个文件的代码。但是,这些可以很容易地组合成一个。)

答案 1 :(得分:5)

根本不可能这样做。全局变量(就像您在头文件中声明的那样)与匿名结构或联合的成员不同,它们根本不起作用。

由于匿名结构或联合对指针算法没有帮助,结构仍然位于内存中的某个位置,编译器使用结构base-address的偏移来找出成员的位置。但是,由于在编译时都知道基址和成员偏移,因此编译器通常能够生成代码以直接访问成员,就像任何其他变量一样。如果您不确定,请查看生成的代码。

因此,跳过带有匿名结构的废话,在头文件中正确定义它们,并在头文件中声明这些结构的变量,同时在某些源文件中定义这些变量。

所以对于头文件:

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

extern union your_union your_union;

在源文件中

union your_union your_union;

答案 2 :(得分:4)

实际上几乎没有理由使用联盟。相反,使用shift / mask,可能在inline函数中提取两半:

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; }

这很可能由编译器优化为与union-approach相同的代码。

但是,当您引用匿名结构/联合成员 时:省略名称是结构/联合的一个特征,包括成员,而不是成员/结合的成员。所以你可以使用:

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

问题是:

  • 非便携式,可能取决于所使用的ABI / PCS(虽然AAPCS对此非常清楚,但其他可能有所不同)。
  • 依赖于小端(某些ARM CPU /实现也允许使用big-endian)。
  • 不能用于已定义为uint64_t的实例。
  • 不如功能负载明确。
  • 可能会增加复合类型的开销(非寄存器参数传递,堆栈放置而不是寄存器等等。

volatile的正常使用是以完整大小加载/存储它。如果情况并非如此,那么竞争条件可能会发生,并且您处于锁定/互斥等世界,这使得非常的事情变得更加复杂。如果两个字段只是松散相关,那么最好使用两个32位变量或struct变量。

典型用法:

volatile uint64_t v64;

void int_handler(void)
{
    uint64_t vt = v64;

    uint32_t l = lower(vt);
    uint32_t h = higher(vt);

}

这确保变量只读一次。使用适当的编译器,lh的分配不会生成任何代码,但会使用vt的寄存器。这当然取决于您的代码,但即使如果有一些开销,也可以忽略不计。

(Sitenote:这是我自己作为长期嵌入式程序员的做法)

答案 3 :(得分:3)

不,当然您无法访问非实例化类型的成员。数据存储在哪里?

您应该只使union可见,并信任编译器以生成有效的访问。我不认为它会成为任何&#34;指针算术&#34;,至少不是在运行时。

答案 4 :(得分:0)

你可能会喜欢并使用程序集来实现你想要的东西,或者使用C ++。

在汇编

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

在C:

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

然后做你想做的事。

在C ++中,类似这样:

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;

但是,说实话,这都是浪费精力。访问aMyUnion.a将使用编译器生成的相同代码。它知道所有内容的大小和偏移量,因此它不会在运行时计算任何内容,而只是从提前知道的正确地址加载。

答案 5 :(得分:0)

澄清我所学到的东西:

事实证明,C不允许匿名结构/联盟的全球成员。那好吧。但是使用命名的结构/联合无论如何都会生成有效的代码,因为成员偏移在编译时是已知的,并且可以在不产生额外指令的情况下添加。

关于使用轮班&amp;掩码而不是联合,这可能适用于读取,但对于写入,它会产生额外的指令(9对5)和对低uint32的无意义访问。另一方面,它比联合更容易移植,但在我的应用程序中这并不重要。

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