我有3个非常相似的数据结构。它们长6,7和8个字节。我想把它们全部放在uint64_t变量中。目标是比较和分配将非常有效。 (这些值在几个(大)树中用作键)。
示例:我为7字节长的数据结构定义了以下数据结构。
typedef struct {
union {
uint64_t raw;
struct {
uint8_t unused;
uint8_t node_number;
uint8_t systemid[SYSTEMID_LENGTH]; /* SYSTEMID_LENGTH is 6 bytes. */
} nodeid;
};
} nodeid_t;
我现在可以通过原始联盟成员快速分配和复制。
nodeid_t x, y;
x.raw = y.raw
if (x.raw > y.raw) {
Etc等。
我的问题是关于在功能和作业中的使用。 当我按值传递此结构时,编译器(gcc)是否会识别出这些结构长度为8个字节。因此,好像他们是int64_t?
示例::
之间是否存在效率/性能差异?int64_t my_function();
nodeid_t my_function();
换句话说,gcc会使用1条指令将nodeid_t放在堆栈上,好像它是一个整数吗?或者它会创建一个循环,并逐个复制8个字节?这取决于-O优化吗?
同样的转让问题。
int64_t a, b;
nodeid_t x, y;
a = b; /* One machine instruction, I hope. */
x = y; /* Also one instruction, or will it do a loop ? */
答案 0 :(得分:1)
您无法确定union
的尺寸与uint64_t
相同。
这是由于nodeid struct
中的打包:编译器通常会在struct
成员之间插入空白。有些编译器允许您更改打包安排,但您的代码将无法移植。
拥有uint8_t
的数组会更安全:那么内存将是连续的。
编译器只需在赋值时复制内存,因此您也可以使用nodeid_t
作为函数返回类型。
您的第二项工作是重命名nodeid_t
:POSIX C中保留_t
个后缀。
答案 1 :(得分:1)
__attribute__((packed))
执行此操作。我认为MSVC使用#pragma push pack(1)...#pragma pop
3. Gcc曾经在这个区域有错误(位字段之间的间隙,错误的对齐......)所以我强烈建议使用编译时检查,例如STATIC_ASSERT(sizeof(nodeid_t) == sizeof(uint64_t))
答案 2 :(得分:1)
这取决于您的架构。但假设您使用x86_64(这是最有可能的),您不需要为复制和函数参数进行联合黑客攻击(您仍需要它进行比较)。
struct foo {
char a;
char b;
short c;
int d;
};
void
foo_copy(struct foo *a, struct foo *b)
{
*a = *b;
}
extern void bar(struct foo a);
void
foo_value(void)
{
struct foo f = { .a = 1 };
bar(f);
}
$ cc -fomit-frame-pointer -O2 -S foo.c
$ cat foo.s
[... cleaned up ...]
_foo_copy: ## @foo_copy
movq (%rsi), %rax
movq %rax, (%rdi)
retq
_foo_value: ## @foo_value
movl $1, %edi
jmp _bar ## TAILCALL
不同的体系结构将有不同的要求,例如严格的对齐体系结构,除非ABI需要比通常的对齐更大,否则无法进行复制。其他ABI可能对结构有不同的调用约定。所以这一般很难回答。但是,如果您使用的是x86_64,那么您可能要么不需要浪费时间进行此优化,要么您希望比较效率高,这可能会像您想要的那样工作。
答案 3 :(得分:0)
对于这样的事情,效率可能不会成为一个问题。
那就是说,这可能不符合你的意图:
if (x.raw > y.raw) {
如果您在具有little-endian体系结构的计算机上运行,则首先存储最低有效字节。如果是这种情况,那么如果你有这个:
x.nodeid.systemid[0] = 1;
x.nodeid.systemid[1] = 2;
y.nodeid.systemid[0] = 2;
y.nodeid.systemid[1] = 1;
然后(x.raw > y.raw)
会评估为真。
答案 4 :(得分:0)
我现在倾向于将我的3个数据结构定义为typedef uint64_t。
typedef uint64_t isis_simple_item_t;
typedef struct isis_complex_item_t {
byte_t unused;
byte_t node_number;
byte_t systemid[ISIS_SYSTEMID_SIZE];
};
byte_t number;
isis_simple_item_t nodeid;
number = ((isis_complex_item) nodeid).node_number;
通过这种方式,我可以快速进行比较,分配,函数返回,函数参数值等。
然后当我需要访问结构中的一个成员时,发生的情况要少得多,我将使用包装函数。在其中使用强制转换,从uint64_t到更复杂的结构。这也意味着我不再需要工会了。
答案 5 :(得分:0)
而不是所有这些花哨的步法,为什么不简单地使用memcmp
,这适用于任何连续的数据类型可能实现为编译内在,所以应该快速并且绝对正确地避开严格的别名规则。