C++中变量混叠的最佳方法

Optimal way to variable aliasing in C++

本文关键字:最佳 方法 变量 C++      更新时间:2023-10-16

我想学习C++中的变量别名,以便在不付出任何代价的情况下使代码比以前少很多(零成本),因为我可以更好地描述我的目的,请参阅以下详细信息

我有一个类似下面的结构

struct Info
{
size_t d1;
size_t d2;
size_t d3;
size_t d4;
};

我在另一个类中使用了这个结构。(称为LargeName类)类似于

class LargeName
{
public:
// … many fields and methods
Info get_large_name_info() const
{
return info;
}
private:
Info info;
}

在这种情况下,如果我想在外部函数中使用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;
}

正如您在上面的例子中看到的,如果我的类名和信息的getter访问名太长,我必须编写非常长的字符才能使用d1到d4。但我想避开他们。例如,我可以写下面的代码来防止

void foo(LargeName large_name)
{
const Info& info = large_name.get_large_name_info();
info.d1;
info.d2;
info.d3;
info.d4;
}

正如你所看到的,我的代码比以前更干净了,也更短了。(我称infolarge_name.get_large_name_info()别名变量)但问题是,我不确定编译器是否能为新版本的代码生成与以前相同的代码。

我认为第二个foo实现是我使用info变量的付费参考和取消引用成本。

我有两个问题:

第一个问题是如何在不付出任何代价的情况下在C++中可变混叠

注意:两个foo函数的汇编版本不同。我检查了带有-std=c++11 -O3 -S开关的GCC 5.4.0版本。

第二个问题是为什么编译器为那些foo方法生成不同的代码?在哪种情况下,这两个代码(第一个foo和第二个foo)的行为不完全相同(我想了解编译器为什么生成不同的代码?)?(我认为如果两个代码完全相同,那么生成的程序集必须相同)

--------------------附录------------------

一个完整的例子:

源代码:

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

g++ -std=c++11 -O3 -D=SC1 -S main.cpp编译

场景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

g++ -std=c++11 -O3 -D=SC2 -S main.cpp编译

场景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

差异是:

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

您的方法是合乎逻辑、干净整洁的。

至于性能,只有测量它读取具有特定构建设置的特定机器上的程序集,才能真正发现这一点。但如果你在这里支付任何"费用",我会感到非常惊讶。编译者很聪明;如果不需要的话,它们不会强迫你去引用指针。此外,引用不仅仅是"伪装中的指针",它们象征着现有数据的新名称,你的编译器知道你想用它做什么。

如果有什么不同的话,您可以跳过一些不必要的Info复制构造和对get_large_name_info()的调用。

即使由于某种原因出现了一些小的性能问题,除非你把这段代码放在一个紧密的循环中,否则我认为代码的易读性提高是值得的,就像指针取消引用一样。

事实上,由于您正在复制Info,因此整个引用位是完全不必要的干扰;无论如何,你只是在延长当地临时工的寿命。简单得多,但都一样:

const Info info = large_name.get_large_name_info();

现在你甚至连一个推荐人都不用担心了。

最后,似乎没有任何理由按值返回Info。如果没有"优化"之类的东西,这在逻辑上是没有必要的。如果您将原始成员返回为const Info&,那么整个讨论就没有意义了,编译器可能仍然不会形成指针和一些取消引用操作,因为它很聪明,并且知道不需要。

tl;博士:也许不用担心

"别名"和您的代码设计

首先:get_large_name_info()info字段进行复制。虽然在实践中,复制可能会被优化掉,但这可能不是你想要做的;相反,更常见的是返回一个常量左值引用(在您的例子中,const Info&

其次,你写道:

我调用的info是large_name.get_large_name_info()的别名变量

这个词令人困惑。C和C++中"别名"一词的典型用法是指多个指针(或引用)指向内存中的重叠区域。如果您的方法返回了一个const&,那么您可以说您已经为类LargeName的成员info获得了一种别名

最后,为什么不使用字段名作为getter名称呢?

这会给你:

class LargeName {
public:
// … many fields and methods
const Info& info() const { return info_; }
private:
Info info_;
}

你会写,说:

void foo(LargeName bar) {
bar.info().d1;
bar.info().d2;
bar.info().d3;
bar.info().d4;
}

的确,你可以进一步缩短这个时间,但是:如果你在foo()中只使用一个名为info的变量,人们可能不知道那是哪个info


PS-也许你的Info类应该有一个size_td[4]而不是四个字段?这也缩短了一些事情(代价是没有明显的标识符)。

不同方法的所谓不同代码

我无法复制这个。对于GCC 10.1,我在您的附录中得到了两种场景的相同代码。