[英]Optimal way to variable aliasing in C++
ITNOA ITNOA
I want to leaern variable aliasing in C++ for make much code lesser than before without pay any cost (Zero Cost), for I can better discribe my purpose, please see below details我想在 C++ 中学习变量别名,以便在不支付任何成本(零成本)的情况下比以前更少的代码,因为我可以更好地描述我的目的,请参阅下面的详细信息
I have a structure like below我有一个如下结构
struct Info
{
size_t d1;
size_t d2;
size_t d3;
size_t d4;
};
I use this struct in another class.我在另一个 class 中使用了这个结构。 (that call
LargeName
class) like below (调用
LargeName
类)如下
class LargeName
{
public:
// … many fields and methods
Info get_large_name_info() const
{
return info;
}
private:
Info info;
}
In this scenario if I want to use info fields from LargeName
instant in external function, I have to something like below在这种情况下,如果我想在外部 function 中使用来自
LargeName
即时的信息字段,我必须像下面这样
void foo(LargeName large_name)
{
large_name.get_large_name_info().d1;
large_name.get_large_name_info().d2;
large_name.get_large_name_info().d3;
large_name.get_large_name_info().d4;
}
As you can see in above example if my class name and getter access name of info is too long, I have to write very character to use d1 to d4.如您在上面的示例中所见,如果我的 class 名称和信息的 getter 访问名称太长,我必须编写非常字符才能使用 d1 到 d4。 But I want to avoid them.
但我想避开它们。 so for example I can write below code to prevent that
所以例如我可以写下面的代码来防止这种情况
void foo(LargeName large_name)
{
const Info& info = large_name.get_large_name_info();
info.d1;
info.d2;
info.d3;
info.d4;
}
As you can see my code is little cleaner and very shorter than before.如您所见,我的代码比以前更简洁且更短。 (I called
info
is alias variable for large_name.get_large_name_info()
) But the problem is I have not sure to compiler generate equal code for new version of my code that same as before. (我称
info
是large_name.get_large_name_info()
的别名变量)但问题是我不确定编译器会为我的代码的新版本生成与以前相同的相同代码。
I think second foo implementation my pay reference and dereferencing cost for using info variable.我认为第二个 foo 实现是我使用 info 变量的薪酬参考和取消参考成本。
I have two question:我有两个问题:
First question is how to variable aliasing in C++ without pay any cost?第一个问题是如何在不支付任何费用的情况下在 C++ 中进行变量混叠?
Note: The assembly version of two foo
function is different.注意:两个
foo
function 的汇编版本不同。 I check with GCC version 5.4.0 with -std=c++11 -O3 -S
switch.我使用
-std=c++11 -O3 -S
开关检查 GCC 版本 5.4.0。
Second question is why compiler generate different code for those foo
method?第二个问题是为什么编译器会为这些
foo
方法生成不同的代码? on which situation these two codes (first foo
and second foo
) does not behave same exactly (I want to learn why compiler generate different code?)?在哪种情况下,这两个代码(第一个
foo
和第二个foo
)的行为不完全相同(我想了解为什么编译器会生成不同的代码?)? ( I think if two code is same exactly, then assembly generated must be same ) (我认为如果两个代码完全相同,那么生成的程序集必须相同)
-------------------- APPENDIX ------------------- - - - - - - - - - - 附录 - - - - - - - - - -
A full example:一个完整的例子:
Source code:源代码:
#include <cstdlib>
#include <iostream>
#include <array>
using namespace std;
class A
{
public:
int id;
std::array<size_t, 4> data;
float prec;
};
class User
{
public:
A get_a() const
{
return a;
}
private:
A a;
};
int main()
{
User user;
// scenario 1
#ifdef SC1
cout << "A id: " << user.get_a().id << endl;
cout << "A prec: " << user.get_a().prec << endl;
cout << "A data: ";
cout << user.get_a().data[0];
cout << user.get_a().data[1];
cout << user.get_a().data[2];
cout << user.get_a().data[3];
cout << endl;
#endif
// scenario 2
#ifdef SC2
const A& a = user.get_a();
cout << "A id: " << a.id << endl;
cout << "A prec: " << a.prec << endl;
cout << "A data: ";
cout << a.data[0];
cout << a.data[1];
cout << a.data[2];
cout << a.data[3];
cout << endl;
#endif
return EXIT_SUCCESS;
}
compile with g++ -std=c++11 -O3 -D=SC1 -S main.cpp
用
g++ -std=c++11 -O3 -D=SC1 -S main.cpp
编译
scenario 1:场景1:
.file "main.cpp"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "A id: "
.LC1:
.string "A prec: "
.LC2:
.string "A data: "
.section .text.unlikely,"ax",@progbits
.LCOLDB3:
.section .text.startup,"ax",@progbits
.LHOTB3:
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB1495:
.cfi_startproc
pushq %rbx
.cfi_def_cfa_offset 16
.cfi_offset 3, -16
movl $6, %edx
movl $.LC0, %esi
movl $_ZSt4cout, %edi
subq $80, %rsp
.cfi_def_cfa_offset 96
movl 16(%rsp), %ebx
movq %fs:40, %rax
movq %rax, 72(%rsp)
xorl %eax, %eax
call _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
movl %ebx, %esi
movl $_ZSt4cout, %edi
call _ZNSolsEi
movq %rax, %rdi
call _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
movss 56(%rsp), %xmm1
movl $8, %edx
movl $.LC1, %esi
movl $_ZSt4cout, %edi
movss %xmm1, 12(%rsp)
call _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
pxor %xmm0, %xmm0
movl $_ZSt4cout, %edi
cvtss2sd 12(%rsp), %xmm0
call _ZNSo9_M_insertIdEERSoT_
movq %rax, %rdi
call _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
movl $.LC2, %esi
movl $_ZSt4cout, %edi
call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
movq 24(%rsp), %rsi
movl $_ZSt4cout, %edi
call _ZNSo9_M_insertImEERSoT_
movq 32(%rsp), %rsi
movl $_ZSt4cout, %edi
call _ZNSo9_M_insertImEERSoT_
movq 40(%rsp), %rsi
movl $_ZSt4cout, %edi
call _ZNSo9_M_insertImEERSoT_
movq 48(%rsp), %rsi
movl $_ZSt4cout, %edi
call _ZNSo9_M_insertImEERSoT_
movl $_ZSt4cout, %edi
call _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
movq 72(%rsp), %rcx
xorq %fs:40, %rcx
jne .L5
addq $80, %rsp
.cfi_remember_state
.cfi_def_cfa_offset 16
xorl %eax, %eax
popq %rbx
.cfi_def_cfa_offset 8
ret
.L5:
.cfi_restore_state
call __stack_chk_fail
.cfi_endproc
.LFE1495:
.size main, .-main
.section .text.unlikely
.LCOLDE3:
.section .text.startup
.LHOTE3:
.section .text.unlikely
.LCOLDB4:
.section .text.startup
.LHOTB4:
.p2align 4,,15
.type _GLOBAL__sub_I_main, @function
_GLOBAL__sub_I_main:
.LFB1689:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $_ZStL8__ioinit, %edi
call _ZNSt8ios_base4InitC1Ev
movl $__dso_handle, %edx
movl $_ZStL8__ioinit, %esi
movl $_ZNSt8ios_base4InitD1Ev, %edi
addq $8, %rsp
.cfi_def_cfa_offset 8
jmp __cxa_atexit
.cfi_endproc
.LFE1689:
.size _GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main
.section .text.unlikely
.LCOLDE4:
.section .text.startup
.LHOTE4:
.section .init_array,"aw"
.align 8
.quad _GLOBAL__sub_I_main
.local _ZStL8__ioinit
.comm _ZStL8__ioinit,1,1
.hidden __dso_handle
.ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609"
.section .note.GNU-stack,"",@progbits
compile with g++ -std=c++11 -O3 -D=SC2 -S main.cpp
用
g++ -std=c++11 -O3 -D=SC2 -S main.cpp
编译
Scenario 2:场景二:
.file "main.cpp"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "A id: "
.LC1:
.string "A prec: "
.LC2:
.string "A data: "
.section .text.unlikely,"ax",@progbits
.LCOLDB3:
.section .text.startup,"ax",@progbits
.LHOTB3:
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB1495:
.cfi_startproc
pushq %r14
.cfi_def_cfa_offset 16
.cfi_offset 14, -16
pushq %r13
.cfi_def_cfa_offset 24
.cfi_offset 13, -24
movl $6, %edx
pushq %r12
.cfi_def_cfa_offset 32
.cfi_offset 12, -32
pushq %rbp
.cfi_def_cfa_offset 40
.cfi_offset 6, -40
movl $.LC0, %esi
pushq %rbx
.cfi_def_cfa_offset 48
.cfi_offset 3, -48
movl $_ZSt4cout, %edi
subq $80, %rsp
.cfi_def_cfa_offset 128
movl 16(%rsp), %r14d
movss 56(%rsp), %xmm1
movss %xmm1, 12(%rsp)
movq 24(%rsp), %r13
movq 32(%rsp), %r12
movq %fs:40, %rax
movq %rax, 72(%rsp)
xorl %eax, %eax
movq 40(%rsp), %rbp
movq 48(%rsp), %rbx
call _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
movl %r14d, %esi
movl $_ZSt4cout, %edi
call _ZNSolsEi
movq %rax, %rdi
call _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
movl $8, %edx
movl $.LC1, %esi
movl $_ZSt4cout, %edi
call _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
pxor %xmm0, %xmm0
movl $_ZSt4cout, %edi
cvtss2sd 12(%rsp), %xmm0
call _ZNSo9_M_insertIdEERSoT_
movq %rax, %rdi
call _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
movl $.LC2, %esi
movl $_ZSt4cout, %edi
call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
movq %r13, %rsi
movl $_ZSt4cout, %edi
call _ZNSo9_M_insertImEERSoT_
movq %r12, %rsi
movl $_ZSt4cout, %edi
call _ZNSo9_M_insertImEERSoT_
movq %rbp, %rsi
movl $_ZSt4cout, %edi
call _ZNSo9_M_insertImEERSoT_
movq %rbx, %rsi
movl $_ZSt4cout, %edi
call _ZNSo9_M_insertImEERSoT_
movl $_ZSt4cout, %edi
call _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
movq 72(%rsp), %rcx
xorq %fs:40, %rcx
jne .L5
addq $80, %rsp
.cfi_remember_state
.cfi_def_cfa_offset 48
xorl %eax, %eax
popq %rbx
.cfi_def_cfa_offset 40
popq %rbp
.cfi_def_cfa_offset 32
popq %r12
.cfi_def_cfa_offset 24
popq %r13
.cfi_def_cfa_offset 16
popq %r14
.cfi_def_cfa_offset 8
ret
.L5:
.cfi_restore_state
call __stack_chk_fail
.cfi_endproc
.LFE1495:
.size main, .-main
.section .text.unlikely
.LCOLDE3:
.section .text.startup
.LHOTE3:
.section .text.unlikely
.LCOLDB4:
.section .text.startup
.LHOTB4:
.p2align 4,,15
.type _GLOBAL__sub_I_main, @function
_GLOBAL__sub_I_main:
.LFB1689:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $_ZStL8__ioinit, %edi
call _ZNSt8ios_base4InitC1Ev
movl $__dso_handle, %edx
movl $_ZStL8__ioinit, %esi
movl $_ZNSt8ios_base4InitD1Ev, %edi
addq $8, %rsp
.cfi_def_cfa_offset 8
jmp __cxa_atexit
.cfi_endproc
.LFE1689:
.size _GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main
.section .text.unlikely
.LCOLDE4:
.section .text.startup
.LHOTE4:
.section .init_array,"aw"
.align 8
.quad _GLOBAL__sub_I_main
.local _ZStL8__ioinit
.comm _ZStL8__ioinit,1,1
.hidden __dso_handle
.ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609"
.section .note.GNU-stack,"",@progbits
And diff is:差异是:
19c19
< pushq %rbx
---
> pushq %r14
21c21,24
< .cfi_offset 3, -16
---
> .cfi_offset 14, -16
> pushq %r13
> .cfi_def_cfa_offset 24
> .cfi_offset 13, -24
22a26,31
> pushq %r12
> .cfi_def_cfa_offset 32
> .cfi_offset 12, -32
> pushq %rbp
> .cfi_def_cfa_offset 40
> .cfi_offset 6, -40
23a33,35
> pushq %rbx
> .cfi_def_cfa_offset 48
> .cfi_offset 3, -48
26,27c38,43
< .cfi_def_cfa_offset 96
< movl 16(%rsp), %ebx
---
> .cfi_def_cfa_offset 128
> movl 16(%rsp), %r14d
> movss 56(%rsp), %xmm1
> movss %xmm1, 12(%rsp)
> movq 24(%rsp), %r13
> movq 32(%rsp), %r12
30a47,48
> movq 40(%rsp), %rbp
> movq 48(%rsp), %rbx
32c50
< movl %ebx, %esi
---
> movl %r14d, %esi
37d54
< movss 56(%rsp), %xmm1
41d57
< movss %xmm1, 12(%rsp)
52c68
< movq 24(%rsp), %rsi
---
> movq %r13, %rsi
55c71
< movq 32(%rsp), %rsi
---
> movq %r12, %rsi
58c74
< movq 40(%rsp), %rsi
---
> movq %rbp, %rsi
61c77
< movq 48(%rsp), %rsi
---
> movq %rbx, %rsi
71c87
< .cfi_def_cfa_offset 16
---
> .cfi_def_cfa_offset 48
73a90,97
> .cfi_def_cfa_offset 40
> popq %rbp
> .cfi_def_cfa_offset 32
> popq %r12
> .cfi_def_cfa_offset 24
> popq %r13
> .cfi_def_cfa_offset 16
> popq %r14
Your approach is logical, and clean, and neat.你的方法是合乎逻辑的,干净的,整洁的。
As for performance, you can only really find this out by measuring it and reading the assembly on your particular machine with your particular build settings.至于性能,您只能通过测量它并使用您的特定构建设置读取特定机器上的程序集来真正找到这一点。 But I'd be very surprised if you paid any "cost" here.
但是,如果您在这里支付任何“费用”,我会感到非常惊讶。 Compilers are clever;
编译器很聪明; they don't force you into a pointer dereference if they don't need to.
如果不需要,它们不会强迫您进行指针取消引用。 Besides, references aren't just "pointers in disguise", they symbolise a new name for an existing piece of data, and your compiler understands what you're trying to do with that.
此外,引用不仅仅是“变相的指针”,它们象征着现有数据的新名称,并且您的编译器了解您正在尝试使用它做什么。
If anything, you may gain from skipping some unnecessary Info
copy constructions and calls to get_large_name_info()
.如果有的话,您可能会从跳过一些不必要的
Info
复制结构和调用get_large_name_info()
中获益。
Even if there were some small performance hit for some reason, unless you've put this code into a tight loop, I imagine the improved legibility of the code is well worth the cost of a tiny thing like a pointer dereference.即使由于某种原因对性能造成了一些小的影响,除非您将这段代码放入一个紧密的循环中,否则我认为提高代码的易读性非常值得为指针取消引用之类的小东西付出代价。
In fact, since you're copying the Info
, the whole reference bit is a completely unnecessary distraction;实际上,由于您正在复制
Info
,因此整个引用位完全是不必要的干扰; you're just extending the lifetime of a local temporary anyway.无论如何,您只是在延长本地临时人员的寿命。 Much simpler, but just the same:
简单得多,但一样:
const Info info = large_name.get_large_name_info();
Now you don't even have a reference to worry about.现在您甚至无需担心参考。
Finally, there doesn't seem to be any reason to return that Info
by value.最后,似乎没有任何理由按值返回该
Info
。 Absent "optimisations" and the like, this is just logically unnecessary.没有“优化”等,这在逻辑上是不必要的。 If you return the original member as
const Info&
instead, then the entire discussion is moot, and the compiler will still probably not form a pointer and some dereference operations, because it's clever and knows that it doesn't need to.如果您将原始成员返回为
const Info&
,那么整个讨论将毫无意义,并且编译器可能仍然不会形成指针和一些取消引用操作,因为它很聪明并且知道它不需要。
tl;dr: probably don't worry about it tl; dr:可能不用担心
First thing's first: Your get_large_name_info()
makes a copy of the info
field.首先是第一件事:您的
get_large_name_info()
会复制info
字段。 While, in practice, the copying may be optimized away, it's probably not what you want to do;虽然在实践中,复制可能会被优化掉,但这可能不是您想要做的; rather, it's more common to return a constant lvalue reference (in your case,
const Info&
相反,更常见的是返回一个常量左值引用(在你的情况下,
const Info&
Secondly, you wrote:其次,你写道:
I called info is alias variable for
large_name.get_large_name_info()
我称 info 是
large_name.get_large_name_info()
的别名变量
that term is confusing.这个词令人困惑。 The typical use of the word "aliasing" w.r.t.
“别名”一词的典型用法 w.r.t。 C and C++ is for when mutlitple pointers (or perhaps references) point to overlapping areas in memory.
C 和 C++ 用于当多个指针(或可能是引用)指向 memory 中的重叠区域时。 if your method returned a
const&
, then you could say you've got a sort of an alias for the member info
of class LargeName
.如果您的方法返回一个
const&
,那么您可以说您为 class LargeName
的成员info
提供了一种别名。
Finally, why not use the field name as the getter name, instead?最后,为什么不使用字段名称作为 getter 名称呢?
This would give you:这会给你:
class LargeName {
public:
// … many fields and methods
const Info& info() const { return info_; }
private:
Info info_;
}
and you would write, say:你会写,说:
void foo(LargeName bar) {
bar.info().d1;
bar.info().d2;
bar.info().d3;
bar.info().d4;
}
it's true that you can shorten this further, but: if you just a variable named info
in foo()
, people might not know which info
that is.的确,您可以进一步缩短它,但是:如果您只是
foo()
中名为info
的变量,人们可能不知道那是哪个info
。
PS - Maybe your Info
class should have a size_t d[4] rather than four fields? PS - 也许你的
Info
class 应该有一个 size_t d[4] 而不是四个字段? That shortens some thing as well (at the cost of no distinct identifiers).这也缩短了一些事情(以没有不同的标识符为代价)。
I can't reproduce this.我无法重现这个。 With GCC 10.1, I get the same code for both scenarios in your appendix .
使用 GCC 10.1,我在您的附录中获得了两种场景的相同代码。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.