C全局匿名结构/联合

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

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

http://goo.gl/ejx37y

#include  volatile union { uint64_t ab; struct { uint32_t a, b; }; }; int main(void) { a = 1; }; 

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

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

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

所以对于头文件:

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

并在源文件中

 union your_union your_union; 

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

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 ua #define b ub #define ab u.ab 

如果您确实使用#define ,请注意它们会影响标识符(a,b,ab)的任何声明/使用,即使是那些不同范围的标识符。 我建议您只需通过u对象显示访问值(如uaubu.ab )。

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

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

实际上几乎没有理由使用工会。 相反,使用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相同的代码。

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

 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实例上使用。
  • 不如function负载明确。
  • 可能会增加复合类型的开销(非寄存器参数传递,堆栈放置而不是寄存器等)

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:这是我自己的长期嵌入式程序员的做法)

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

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

您可以使用组件来实现您想要的function,或者使用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将具有由编译器生成的相同代码。 它知道所有内容的大小和偏移量,因此它不会在运行时计算任何内容,而只是从提前知道的正确地址加载。

澄清我所学到的东西:

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

关于使用移位和掩码而不是并集,这可能适用于读取,但对于写入,它会产生额外的指令(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