我有一个uint64变量,通常只需要高或低32位访问。我使用的是32位ARM Cortex M0,为了提高速度,我试图将uint64变量与C中的两个uint32变量重叠,使用匿名结构,希望避免指针算法访问成员。
我想做的是什么?可能是使用命名联盟同样快,但现在我只是感兴趣,如果它可以没有。以下内容无法成功编译:
#include <stdint.h>
volatile union {
uint64_t ab;
struct { uint32_t a, b; };
};
int main(void)
{
a = 1;
};
答案 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.a
,u.b
,u.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;
问题是:
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);
}
这确保变量只读一次。使用适当的编译器,l
,h
的分配不会生成任何代码,但会使用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;
但是,说实话,这都是浪费精力。访问a
或MyUnion.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