WHAT IS DIFFERENT BETWEEN REFERENCE AND POINTER

I have not learned the concept of programming language systematically, so I can not accurately explain the difference between a reference and a pointer. And there is no interest in explaining the difference. However, in order to build a program with a better structure, we want to know how to compile it, and to divide the difference between reference and pointer sensibly based on it. I see that the results of compiling the two concepts are not wrong. Perhaps the reference in C ++ was not standardized by the desire of many who wanted to use ‘.’ Instead of ‘->’.

ENVIRONMENT

  • gcc (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609

SOURCE TO COMPARE REFERENCE TO POINTER

#include <iostream>

class Foo
{
protected:  int __v;
public:     int v() const { return __v; }
public:     Foo & operator=(const Foo & o)
            {
                if(&o != this)
                {
                    __v = o.__v;
                }
                return *this;
            }
public:     Foo & operator=(Foo && o) noexcept
            {
                if(&o != this)
                {
                    __v = o.__v;
                }
                return *this;
            }
public:     Foo(int v) : __v(v)
            {

            }
public:     Foo() : __v()
            {

            }
public:     Foo(const Foo & o) : __v(o.__v)
            {

            }
public:     Foo(Foo && o) noexcept : __v(o.__v)
            {

            }
public:     virtual ~Foo()
            {
                __v = 0;
            }
};

int main(int argc, char ** argv)
{
    Foo foo(10);
    std::cout<<foo.v()<<std::endl;

    Foo & reference = foo;
    std::cout<<reference.v()<<std::endl;

    Foo * pointer = &foo;
    std::cout<<pointer->v()<<std::endl;
    return 0;
}

DISASSEMBLE

int main(int argc, char ** argv)

ADDRESS BYTE CODE ASM DESCRIPTION
0x400a46 55 push %rbp
0x400a47 48 89 e5 mov %rsp, %rbp
0x400a4a 53 push %rbx
0x400a4b 48 83 ec 48 sub $0x48, %rsp
0x400a4f 89 7d bc mov %edi, -0x44(%rbp)
0x400a52 48 89 75 b0 mov %rsi, -0x50(%rbp)
0x400a56 64 48 8b 04 25 28 00 mov %fs:0x28, %rax
0x400a5d 00 00
0x400a5f 48 89 45 e8 mov %rax, -0x18(%rbp)
0x400a63 31 c0 xor %eax, %eax
0x400a65 48 8d 45 d0 lea -0x30(%rbp), %rax %rbp – 0x30 is variable bar’s address
0x400a69 be 0a 00 00 00 mov $0xa, %esi
0x400a6e 48 89 c7 mov %rax, %rdi
0x400a71 e8 2e 01 00 00 callq 400ba4 call Foo::Foo(int v)
0x400a76 48 8d 45 d0 lea -0x30(%rbp), %rax
0x400a7a 48 89 c7 mov %rax, %rdi
0x400a7d e8 10 01 00 00 callq 400b92 call int Foo::v() const
0x400a82 89 c6 mov %eax, %esi
0x400a84 bf e0 20 60 00 mov $0x6020e0, %edi
0x400a89 e8 02 fe ff ff callq 400890
0x400a8e be 10 09 40 00 mov $0x400910, %esi
0x400a93 48 89 c7 mov %rax, %rdi
0x400a96 e8 65 fe ff ff callq 400900
0x400a9b 48 8d 45 d0 lea -0x30(%rbp), %rax
0x400a9f 48 89 45 c0 mov %rax, -0x40(%rbp)
0x400aa3 48 8b 45 c0 mov -0x40(%rbp), %rax %rbp – 0x40 is variable reference’s address and set %rbp – 0x30
0x400aa7 48 89 c7 mov %rax, %rdi
0x400aaa e8 e3 00 00 00 callq 400b92 call int Foo::v() const
0x400aaf 89 c6 mov %eax, %esi
0x400ab1 bf e0 20 60 00 mov $0x6020e0, %edi
0x400ab6 e8 d5 fd ff ff callq 400890
0x400abb be 10 09 40 00 mov $0x400910, %esi
0x400ac0 48 89 c7 mov %rax, %rdi
0x400ac3 e8 38 fe ff ff callq 400900
0x400ac8 48 8d 45 d0 lea -0x30(%rbp), %rax
0x400acc 48 89 45 c8 mov %rax, -0x38(%rbp)
0x400ad0 48 8b 45 c8 mov -0x38(%rbp), %rax %rbp – 0x38 is variable pointer’s address and set %rbp – 0x30
0x400ad4 48 89 c7 mov %rax, %rdi
0x400ad7 e8 b6 00 00 00 callq 400b92 call int Foo::v() const
0x400adc 89 c6 mov %eax, %esi
0x400ade bf e0 20 60 00 mov $0x6020e0, %edi
0x400ae3 e8 a8 fd ff ff callq 400890
0x400ae8 be 10 09 40 00 mov $0x400910, %esi
0x400aed 48 89 c7 mov %rax, %rdi
0x400af0 e8 0b fe ff ff callq 400900
0x400af5 bb 00 00 00 00 mov $0x0, %ebx
0x400afa 48 8d 45 d0 lea -0x30(%rbp), %rax
0x400afe 48 89 c7 mov %rax, %rdi
0x400b01 e8 c2 00 00 00 callq 400bc8 call Foo::~Foo()
0x400b06 89 d8 mov %ebx, %eax
0x400b08 48 8b 55 e8 mov -0x18(%rbp), %rdx
0x400b0c 64 48 33 14 25 28 00 xor %fs:0x28, %rdx
0x400b13 00 00
0x400b15 74 21 je 400b38
0x400b17 eb 1a jmp 400b33
0x400b19 48 89 c3 mov %rax, %rbx
0x400b1c 48 8d 45 d0 lea -0x30(%rbp), %rax
0x400b20 48 89 c7 mov %rax, %rdi
0x400b23 e8 a0 00 00 00 callq 400bc8 call Foo::~Foo()
0x400b28 48 89 d8 mov %rbx, %rax
0x400b2b 48 89 c7 mov %rax, %rdi
0x400b2e e8 fd fd ff ff callq 400930
0x400b33 e8 b8 fd ff ff callq 4008f0
0x400b38 48 83 c4 48 add $0x48, %rsp
0x400b3c 5b pop %rbx
0x400b3d 5d pop %rbp
0x400b3e c3 retq

int Foo::v() const

ADDRESS BYTE CODE ASM DESCRIPTION
0x400b92 55 push %rbp
0x400b93 48 89 e5 mov %rsp, %rbp
0x400b96 48 89 7d f8 mov %rdi, -0x8(%rbp)
0x400b9a 48 8b 45 f8 mov -0x8(%rbp), %rax
0x400b9e 8b 40 08 mov 0x8(%rax), %eax
0x400ba1 5d pop %rbp
0x400ba2 c3 retq
0x400ba3 90 nop

Foo::Foo(int v)

ADDRESS BYTE CODE ASM DESCRIPTION
0x400ba4 55 push %rbp
0x400ba5 48 89 e5 mov %rsp, %rbp
0x400ba8 48 89 7d f8 mov %rdi, -0x8(%rbp)
0x400bac 89 75 f4 mov %esi, -0xc(%rbp)
0x400baf ba d8 0c 40 00 mov $0x400cd8, %edx
0x400bb4 48 8b 45 f8 mov -0x8(%rbp), %rax
0x400bb8 48 89 10 mov %rdx, (%rax)
0x400bbb 48 8b 45 f8 mov -0x8(%rbp), %rax
0x400bbf 8b 55 f4 mov -0xc(%rbp), %edx
0x400bc2 89 50 08 mov %edx, 0x8(%rax)
0x400bc5 90 nop
0x400bc6 5d pop %rbp
0x400bc7 c3 retq

Foo::~Foo()

ADDRESS BYTE CODE ASM DESCRIPTION
0x400bc8 55 push %rbp
0x400bc9 48 89 e5 mov %rsp, %rbp
0x400bcc 48 83 ec 10 sub $0x10, %rsp
0x400bd0 48 89 7d f8 mov %rdi, -0x8(%rbp)
0x400bd4 ba d8 0c 40 00 mov $0x400cd8, %edx
0x400bd9 48 8b 45 f8 mov -0x8(%rbp), %rax
0x400bdd 48 89 10 mov %rdx, (%rax)
0x400be0 48 8b 45 f8 mov -0x8(%rbp), %rax
0x400be4 c7 40 08 00 00 00 00 movl $0x0, 0x8(%rax)
0x400beb b8 00 00 00 00 mov $0x0, %eax
0x400bf0 85 c0 test %eax, %eax
0x400bf2 74 11 je 400c05
0x400bf4 48 8b 45 f8 mov -0x8(%rbp), %rax
0x400bf8 be 10 00 00 00 mov $0x10, %esi
0x400bfd 48 89 c7 mov %rax, %rdi
0x400c00 e8 9b fc ff ff callq 4008a0
0x400c05 c9 leaveq
0x400c06 c3 retq
0x400c07 90 nop

???

ADDRESS BYTE CODE ASM DESCRIPTION
0x400c08 55 push %rbp
0x400c09 48 89 e5 mov %rsp, %rbp
0x400c0c 48 83 ec 10 sub $0x10, %rsp
0x400c10 48 89 7d f8 mov %rdi, -0x8(%rbp)
0x400c14 48 8b 45 f8 mov -0x8(%rbp), %rax
0x400c18 48 89 c7 mov %rax, %rdi
0x400c1b e8 a8 ff ff ff callq 400bc8 Foo::~Foo()
0x400c20 48 8b 45 f8 mov -0x8(%rbp), %rax
0x400c24 be 10 00 00 00 mov $0x10, %esi
0x400c29 48 89 c7 mov %rax, %rdi
0x400c2c e8 6f fc ff ff callq 4008a0
0x400c31 c9 leaveq
0x400c32 c3 retq
0x400c33 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
0x400c3a 00 00 00
0x400c3d 0f 1f 00 nopl (%rax)

The reference and pointer are not much different. If so, is the function represented by the reference variable equal to the function represented by the pointer?

SOURCE TO COMPARE REFERENCE PARAMETER TO POINTER PARAMETER

/**
 *
 * @author novemberizing
 * @date 2017. 10. 10.
 */

#include <iostream>

class Foo
{
protected:  int __v;
public:     int v() const { return __v; }
public:     void set(int & v){ __v = v; }
public:     void set(int * v){ __v = *v; }
public:     Foo & operator=(const Foo & o)
            {
                if(&o != this)
                {
                    __v = o.__v;
                }
                return *this;
            }
public:     Foo & operator=(Foo && o) noexcept
            {
                if(&o != this)
                {
                    __v = o.__v;
                }
                return *this;
            }
public:     Foo(int v) : __v(v)
            {

            }
public:     Foo() : __v()
            {

            }
public:     Foo(const Foo & o) : __v(o.__v)
            {

            }
public:     Foo(Foo && o) noexcept : __v(o.__v)
            {

            }
public:     virtual ~Foo()
            {
                __v = 0;
            }
};

int main(int argc, char ** argv)
{
    Foo foo(10);
    std::cout<<foo.v()<<std::endl;

    Foo & reference = foo;
    std::cout<<reference.v()<<std::endl;

    Foo * pointer = &foo;
    std::cout<<pointer->v()<<std::endl;

    int v = 10;

    foo.set(v);
    foo.set(&v);
    return 0;
}

void Foo::set(int & v)

ADDRESS BYTE CODE ASM DESCRIPTION
0x400bd2 55 push %rbp
0x400bd3 48 89 e5 mov %rsp, %rbp
0x400bd6 48 89 7d f8 mov %rdi, -0x8(%rbp)
0x400bda 48 89 75 f0 mov %rsi, -0x10(%rbp)
0x400bde 48 8b 45 f0 mov -0x10(%rbp), %rax
0x400be2 8b 10 mov (%rax), %edx
0x400be4 48 8b 45 f8 mov -0x8(%rbp), %rax
0x400be8 89 50 08 mov %edx, 0x8(%rax)
0x400beb 90 nop
0x400bec 5d pop %rbp
0x400bed c3 retq

void Foo::set(int * v)

ADDRESS BYTE CODE ASM DESCRIPTION
0x400bee 55 push %rbp
0x400bef 48 89 e5 mov %rsp, %rbp
0x400bf2 48 89 7d f8 mov %rdi, -0x8(%rbp)
0x400bf6 48 89 75 f0 mov %rsi, -0x10(%rbp)
0x400bfa 48 8b 45 f0 mov -0x10(%rbp), %rax
0x400bfe 8b 10 mov (%rax), %edx
0x400c00 48 8b 45 f8 mov -0x8(%rbp), %rax
0x400c04 89 50 08 mov %edx, 0x8(%rax)
0x400c07 90 nop
0x400c08 5d pop %rbp
0x400c09 c3 retq

same. It is really the same.

CONCLUSION

The reason for this comparison is due to the return value of the reference variable. If you return a variable that does not exist in the container object and you want to return without exception, you can pass it as a null pointer. After all, you have to throw an exception if it is unavoidable, but the “THROW” logic does not fit much because of the current C ++ dynamic allocation. And the program weaving becomes more complicated. So what I am making now is a strange figure. It is using the lambda to treat it as an awkward figure. “END ITERATOR” can not be processed in a lot of cases, inevitably because there is an exception. Though it is strange, “CONTAINER” and “LAMBDA” seems to fit well. However, as you implement in C ++, you gradually feel that the logic is not complicated and the source is complicated, but the source is getting complicated due to insufficient language. I’m going to make a lot of comparisons in the future, but should C ++ be a good language to throw away C and also throw away STL? It still does not understand why you should call std :: move (…), which calls “MOVE CONSTRUCTOR”. C ++ seems to be becoming a monster, but is it necessary to reduce functionality and eliminate “UNDEFINED BEHAVIOR”? More importantly, throw away C! I guess it is because I am not satisfied with the current C ++ changes. It seems to be a fear of the sense of heterogeneity of MORDEN C ++ because it is a favorite language. For the time being, I will watch. Change the look of C ++!

Advertisements
WHAT IS DIFFERENT BETWEEN REFERENCE AND POINTER

BENCHMARK CLASS OPERATOR= VS UNION ASSIGNMENT

이번에 C++ 작업을 진행하면서, 계속 구현하고 지우고 구현하고 지우는 부분이 있는데, 과연 DATA STRUCTURE를 STANDARD TEMPLATE LIBRARY를 사용해야 하나, 아니면 만들어야 하나 고민 중에 있습니다. 당연히 성능 면에서 STL이 우수하겠지만, STL의 문제는 CUSTOMIZE가 어렵다는 것이겠죠. ARRAY 관련한 프로토콜을 고민하고 있는데, STL을 사용해서 구축하면, MEMORY COPY로 끝나는 부분이 FOR 연산으로 구축될 수 밖에 없는 구조적 한계에 부딪쳐서, CONTAINER를 직접 구축하기로 하였습니다. 그리고 INDEX란 구현을 하는데, 사실은 머리 속에는 성능이 차지하고 있어서, INDEX를 UNION으로 구현했는데, 구조적, 그리고 통일성이 결여된 모습인지라, 이것을 클래스로 바꾸고, OPERATOR= 재정의 할까 고민하고 있습니다. 성능이 이해할 수 있는 수준으로 나온다면, CLASS로 INDEX를 구현해 보려고 하기에, 잠깐 BENCHMARKING을 진행해 봅니다.

ENVIRONMENT

  • Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz
  • Ubuntu 5.4.0-6ubuntu1~16.04.4
  • gcc 5.4.0

SOURCE

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

#define START(format, ...)                                          \
    struct timespec ____start;                                      \
    clock_gettime(CLOCK_REALTIME, &____start);

#define END(format, ...)                                            \
    struct timespec ____end;                                        \
    clock_gettime(CLOCK_REALTIME, &____end);                        \
    struct timespec ____result;                                     \
    ____result.tv_nsec = ____end.tv_nsec - ____start.tv_nsec;       \
    ____result.tv_sec = ____end.tv_sec - ____start.tv_sec;          \
    if(____result.tv_nsec < 0)                                      \
    {                                                               \
        ____result.tv_sec--;                                        \
        ____result.tv_nsec += 1000000000;                           \
    }                                                               \
    printf("%ld.%09ld\n", ____result.tv_sec, ____result.tv_nsec);


class Foo
{
public:     typedef union __Bar
            {
                void * p;
                int no;
            } Bar;
private:    Bar __v;
public:     inline const Bar & v() const { return __v; }
public:     inline void set(void * p){ __v.p = p; }
public:     inline void set(int no){ __v.no = no; }
public:     Foo & operator=(const Foo & o)
            {
                if(&o != this)
                {
                    __v.p = o.__v.p;
                }
                return *this;
            }
public:     Foo & operator=(Foo && o) noexcept
            {
                __v.p = o.__v.p;
                return *this;
            }
public:     explicit Foo(void * p)
            {
                __v.p = p;
            }
public:     explicit Foo(int no)
            {
                __v.no = no;
            }
public:     Foo()
            {
                __v.p = nullptr;
            }
public:     Foo(const Foo & o)
            {
                __v.p = o.__v.p;
            }
public:     Foo(Foo && o) noexcept
            {
                __v.p = o.__v.p;
            }
public:     virtual ~Foo()
            {
                __v.p = nullptr;
            }
};

int main(int argc, char ** argv)
{
    int n = atoi(argv[1]);
    Foo foo(10);
    {
        START("");
        Foo bar;
        for(int i = 0; i<n;i++)
        {
            bar = foo;
        }
        END("");
    }
    {
        START("");
        Foo::Bar bar2;
        for(int i = 0; i<n;i++)
        {
            bar2 = foo.v();
        }
        END("");
    }
    {
        Foo::Bar original = foo.v();
        START("");
        Foo::Bar bar2;
        for(int i = 0; i<n;i++)
        {
            bar2 = original;
        }
        END("");
    }
    return 0;
}

BENCHMARK

1024 ASSIGNMENTS

NO OPERATOR= UNION = V() UNION = UNION
1 0.000003343 0.000002751 0.000001960
2 0.000018551 0.000019313 0.000011207
3 0.000005197 0.000004087 0.000002210
4 0.000005199 0.000004711 0.000003439
5 0.000017835 0.000016447 0.000011062
6 0.000017761 0.000020100 0.000011365
7 0.000006830 0.000005663 0.000004360
8 0.000003259 0.000003837 0.000002091
AVG 0.000009747 0.000009614 0.000005962

65536 ASSIGNMENTS

NO OPERATOR= UNION = V() UNION = UNION
1 0.001342985 0.001134340 0.000699173
2 0.001092644 0.000905978 0.000667076
3 0.000197005 0.000167230 0.000122081
4 0.001129600 0.000901970 0.000659107
5 0.000341204 0.000244269 0.000135366
6 0.001075503 0.000916490 0.000659238
7 0.001184876 0.000905472 0.000659302
8 0.000196079 0.000175969 0.000122790
AVG 0.000819987 0.000668964 0.000465516

2147483647 ASSIGNMENTS

NO OPERATOR= UNION = V() UNION = UNION
1 6.544080482 5.377740079 3.958262471
2 6.689384502 5.325625270 3.976952198
3 6.680882126 5.416293741 3.977402995
4 6.676791159 5.421278695 3.979857794
5 6.546954456 5.336170441 3.951263828
6 7.203035896 5.659068612 4.049179058
7 6.947186381 5.538815343 4.047952111
8 6.839532543 5.505688992 4.043745781
AVG 6.765980943 5.447585146 3.998077029

DISASSEMBLE

OPERATOR=

ADDRESS BYTE CODE ASM DESCRIPTION
0x400925 c7 45 84 00 00 00 00 movl $0x0, -0x7c(%rbp) for(int i = 0; …
0x40092c 8b 45 84 mov -0x7c(%rbp), %eax
0x40092f 3b 45 8c cmp -0x74(%rbp),%eax
0x400932 7d 19 jge 40094d i < n; …
0x400934 48 8d 55 90 lea -0x70(%rbp), %rdx
0x400938 48 8d 45 b0 lea -0x50(%rbp), %rax
0x40093c 48 89 d6 mov %rdx, %rsi
0x40093f 48 89 c7 mov %rax, %rdi
0x400942 e8 9f 01 00 00 callq 400ae6 bar = foo;
0x400947 83 45 84 01 addl $0x1, -0x7c(%rbp) i++)

BAR = FOO.V()

ADDRESS BYTE CODE ASM DESCRIPTION
0x4009dc c7 45 88 00 00 00 00 movl $0x0, -0x78(%rbp) for(int i = 0; …
0x4009e3 8b 45 88 mov -0x78(%rbp), %eax
0x4009e6 3b 45 8c cmp -0x74(%rbp), %eax i < n; …
0x4009e9 7d 19 jge 400a04
0x4009eb 48 8d 45 90 lea -0x70(%rbp), %rax
0x4009ef 48 89 c7 mov %rax, %rdi
0x4009f2 e8 dd 00 00 00 callq 400ad4 const Bar & v() const
0x4009f7 48 8b 00 mov (%rax), %rax bar = foo,v()
0x4009fa 48 89 45 a0 mov %rax, -0x60(%rbp)
0x4009fe 83 45 88 01 addl $0x1,- 0x78(%rbp) i++)

BAR = ORIGINAL

ADDRESS BYTE CODE ASM DESCRIPTION
0x400ab5 c7 85 78 ff ff ff 00 movl $0x0,-0x88(%rbp)
0x400abc 00 00 00
0x400abf 8b 85 78 ff ff ff mov -0x88(%rbp), %eax
0x400ac5 3b 85 7c ff ff ff cmp -0x84(%rbp), %eax i < n; …
0x400acb 7d 11 jge 400ade
0x400acd 48 8b 45 80 mov -0x80(%rbp), %rax bar = original
0x400ad1 48 89 45 a0 mov %rax, -0x60(%rbp)
0x400ad5 83 85 78 ff ff ff 01 addl $0x1, -0x88(%rbp) i++)

CONCLUSION

역시나 클래스와 대입연산자를 사용하여, UNION의 대입연산을 처리하는 것보다, UNION 자체를 이용한 대입 연산이 2배이상 빠르다는 것을 알 수 있습니다. 하지만, 과연 2억 번의 루프를 도는 것의 성능이 6초가 걸리는 것으로 구현하는 것이 나쁠까란 의문을 던져 봅니다. 라이브러리는 성능도 중요하지만, 사용성도 중요합니다. 6초와 3초의 결과를 보였다면, 사용성 쪽으로 UNION으로 정의하는 것보다 클래스로 정의하여 라이브러리의 구조를 더 고려하여 구현하는 것이 괜찮겠다라는 지극히 주관적인 결론을 내려 봅니다.

참고: FUNCTION CALL은 비쌉니다. ASM 코드를 비교해 보면, FUNCTION CALL 이 있느냐 없느냐 차이로 성능의 차이가 나는 것으로 예상됩니다.

BENCHMARK CLASS OPERATOR= VS UNION ASSIGNMENT

[disassemble] void * p = nullptr; is same as void * p = 0;

#include <stdio.h>

int main(void)
{
	void * p = nullptr;
	printf("%p\n", p);
	return 0;
}

int main(void)
{
offset code asm description
0x001E17E0 55 push ebp
0x001E17E1 8B EC mov ebp, esp
0x001E17E3 81 EC CC 00 00 00 sub esp, 0CCh
0x001E17E9 53 push ebx
0x001E17EA 56 push esi
0x001E17EB 57 push edi
0x001E17EC 8D BD 34 FF FF FF lea edi, [ebp-0CCh]
0x001E17F2 B9 33 00 00 00 mov ecx, 33h
0x001E17F7 B8 CC CC CC CC mov eax, 0CCCCCCCCh
0x001E17FC F3 AB rep stos dword ptr es:[edi]
void * p = nullptr;
offset code asm description
0x001E17FE C7 45 F8 00 00 00 00 mov dword ptr [p], 0
printf("%p\n", p);
offset code asm description
0x001E1805 8B 45 F8 mov eax, dword ptr [p]
0x001E1808 50 push eax
0x001E1809 68 30 7B 1E 00 push offset 01E7B30h
0x001E180E E8 21 FB FF FF call _printf
0x001E1813 83 C4 08 add esp, 8
return 0;
offset code asm description
0x001E1816 33 C0 xor eax,eax

compiled by visual studio 2017

[disassemble] void * p = nullptr; is same as void * p = 0;

[disassemble] “union [type] o = {};” is same as “union [type] o; memset(&o, 0, sizeof(union [type]));”

union example o = {};

offset code asm description
0x00291808 6A 40 push 40h memset’s third parameter n: size_t = 0x40
0x0029180A 6A 00 push 0 memset’s second parameter c: int = 0x00
0x0029180C 8D 45 B8 lea eax, [ebp-48h]
0x0029180F 50 push eax memset’s first parameter p: void * = [ebp – 0x48]
0x00291810 E8 B3 F8 FF FF call 002910C8 _memset
0x00291815 83 C4 0C add esp, 0Ch

compiled by visual studio 2017

[disassemble] “union [type] o = {};” is same as “union [type] o; memset(&o, 0, sizeof(union [type]));”