简体   繁体   English

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

[英]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. (我称infolarge_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.cppg++ -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.cppg++ -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:可能不用担心

"aliasing" and your code design “别名”和您的代码设计

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).这也缩短了一些事情(以没有不同的标识符为代价)。

The supposedly different code for the different methods不同方法的所谓不同代码

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM