WHAT IS DIFFERENT BETWEEN REFERENCE AND POINTER

체계적으로 프로그램 언어의 개념을 배운 적이 없어서, 현재의 나는 참조와 포인터의 차이를 정확히 설명할 수 없다. 그리고 차이를 설명하는데 관심도 없다. 다만, 프로그램을 짜는데, 보다 좋은 구조를 가진 프로그램을 짜기 위해서, 과연 어떻게 컴파일 되는지를 확인하고, 이를 바탕으로 하여 감각적으로 참조와 포인터의 차이를 구분짓고자 한다. 나는 두 개념이 컴파일 되어지는 결과는 틀리지 않다고 보고 있다. 어쩌면 C++에서 참조는 ‘->’ 대신에, ‘.’을 쓰고 싶어하는 여러 사람의 갈망으로 표준화되지 않았을까?

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)

참조와 포인터는 크게 차이가 없다. 그렇다면, 참조 변수로 표현한 함수와 포인터로 표현한 함수는 같을까?

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

같다. 정말 똑같다.

CONCLUSION

이런 비교를 시도하는 이유는 참조 변수의 리턴 값 때문이다. 컨테이너 객체 안에 존재하지 않는 변수를 리턴할 때 예외처리없이 리턴하려 하면 포인터의 경우 널로 넘기면 좋은데, 참조는 딱히 좋은 방법이 없어 보인다. 결국, 불가피한 경우 예외를 던져야 하는데, 현재의 C++ 동적 할당이란 부분 때문에 “THROW” 로직이 많이 어울리지 않는다. 그리고 프로그램 짜기 더 복잡해진다. 그래서 지금 내가 만들고 있는 것들이 요상한 모습을 띄고 있다. 그 요상한 모습이란 람다를 이용해서 처리를 하고 있는 것이다. “END ITERATOR”로 처리하지 못하는 경우가 많이 존재하며, 그럴 경우 불가피하게 예외처리를 해야하기 때문이다. 요상한 모습이긴 하지만, 나름 “CONTAINER”와 “LAMBDA”는 잘 어울리는듯 보인다. 여튼, C++로구현을하다보니점점느끼는것은 로직이복잡하여소스가복잡해지는것이아니라, 언어가 아직은 미흡하여 소스가 복잡해지는 것이 아닌가 한다. 앞으로로이런비교를많이진행할것이지만, C++이 좋은 언어가 되기 위해서는 C를 버리고, 또한 STL도 버려야 하지 않을까? “MOVE CONSTRUCTOR”를 호출하는데, 왜, std::move(…)를 호출해야 하는지 여전히, 잘 이해되지 않는다. 현재의 내 실력으로 섣불리 판단하는 것이지만, C++은 괴물이 되어가고 있어 보인다.오히려, 기능을 축소하고, “UNDEFINED BEHAVIOUR”를 없애야 하지 않을까? 보다 더 중요한 것은 C를 버리고! 현재의 나의 실력으로 섣불리 판단한 것이다.아마도,현재의 C++의 변화 모습이 마음에 들지 않기 때문인 듯 하다. 너무 좋아하는 언어이다 보니 MORDEN C++의 이질감에 대한 두려움일 수도 있어 보인다. 당분간, 지켜보려 한다. 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]));”