sorosh_sabz
sorosh_sabz

Reputation: 3003

Optimal way to variable aliasing in C++

I want to learn variable aliasing in C++ for make much code lesser than before without pay any cost (Zero Cost), for I can better describe my purpose, please see below details

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. (that call LargeName class) like below

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

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. 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.

I think second foo implementation my pay reference and dereferencing cost for using info variable.

I have two question:

First question is how to variable aliasing in C++ without pay any cost?

Note: The assembly version of two foo function is different. I check with GCC version 5.4.0 with -std=c++11 -O3 -S switch .

Second question is why compiler generate different code for those foo method? 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?) ? ( 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

scenario 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

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

Upvotes: 0

Views: 1123

Answers (2)

einpoklum
einpoklum

Reputation: 131646

"aliasing" and your code design

First thing's first: Your get_large_name_info() makes a copy of the info field. 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&

Secondly, you wrote:

I called info is alias variable for large_name.get_large_name_info()

that term is confusing. The typical use of the word "aliasing" w.r.t. C and C++ is for when mutlitple pointers (or perhaps references) point to overlapping areas in 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.

Finally, why not use the field name as the getter name, instead?

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.


PS - Maybe your Info class should have a size_t d[4] rather than four fields? 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.

Upvotes: 1

Asteroids With Wings
Asteroids With Wings

Reputation: 17454

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().

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; 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. 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.

tl;dr: probably don't worry about it

Upvotes: 1

Related Questions