Sunday, January 24, 2021

The C++20 for-each range differences, part III

With the C++20, there is a new capability that helps when the index is still needed within the loop:

1
2
3
4
5
6
7
8
int main()
{
  const char buffer[]{"buffer"};
  for(int i = 0; const auto & c : buffer)
  { 
    ++i; 
  }
}


The generated source code is then:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
int main()
{
  const char buffer[7] = {"buffer"};
  {
    int i = 0;
    char const (&__range1)[7] = buffer;

    const char * __begin1
        = static_cast<const char *>(__range1);

    const char * __end1
      = static_cast<const char *>(__range1) + 7L;

    for(; __begin1 != __end1; ++__begin1) 
    {
      const char & c = *__begin1;
      ++i;
    }
  }
}


[keywords: for loop, for-each range, c++98, c++11, c++14, c++17, c++20]

The C++98 and C++11 for-each range differences, part II

The range-based for-loop explained by Andreas Fertig is here. You may notice the slight implementation differences between C++11 and C++14 (and higher).

Sunday, September 15, 2019

C++11 std::make_shared does not use class overloaded new/delete operators

Even if std::unique_ptr, std::make_unique and std::shared_ptr use the class overloaded new/delete operators, std::make_shared does not.

Below is a pseudo-code highlighting the behavior:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <memory>
#include <iostream>

class C
{
public:
    void * operator new(size_t size)
    {
        void * p = ::operator new(size);
        std::cout << "C::new() -> " << p << "\n";
        return p;
    }

    void operator delete(void * p)
    {
        std::cout << "C::delete() -> " << p << "\n";
        ::operator delete(p);
    }
};

std::shared_ptr<C> ptr = std::make_shared<C>();

Explanations:

The std::make_shared documentation clearly explains that the creation is using the global new operator. The second aspect is that the allocation creates only a single buffer (i.e. one heap allocation) where the size is the class size plus the control block for the 'shared control' as opposed to the std::shared_ptr which creates two distinct memory buffers.

Below is a pseudo-code better highlighting the behavior:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <memory>
#include <iostream>

void * operator new(size_t size)
{
    void * p = malloc(size);
    std::cout << "Global::new() -> " << p << ", size=" << size << "\n";
    return p;
}

void operator delete(void * p)
{
    std::cout << "Global::delete() -> " << p << "\n";
    free(p);
}

class C
{
public:
    void * operator new(size_t size)
    {
        void * p = ::operator new(size);
        std::cout << "Global::new() -> " << p << ", size=" << size << "\n";
        return p;
    }

    void operator delete(void * p)
    {
        std::cout << "C::delete() -> " << p << "\n";
        ::operator delete(p);
    }
};

int main()
{
    std::shared_ptr<C> ptr = std::make_shared<C>();
}

Use C++shell to run the program.


1
2
Global::new() -> 0x3da04e0, size=24
Global::delete() -> 0x3da04e0


std::make_ptr side-effect:

When reading this post, I discovered that a std::weak_ptr could keep the control block alive and so, keeping the complete allocation alive even if std::shread_ptr count is 0.

https://stackoverflow.com/questions/20895648/difference-in-make-shared-and-normal-shared-ptr-in-c?rq=1


References:

Have a look to few question/answers on that topic:
https://stackoverflow.com/questions/57929127/why-stdmake-shared-is-always-using-the-global-memory-allocation-even-with-cl
https://stackoverflow.com/questions/49423512/c-make-shared-when-custom-new-and-delete-operators-are-defined?rq=1

Here is the explanation:
https://en.cppreference.com/w/cpp/memory/shared_ptr/make_shared

Here is the technicality


[keywords: std::make_shared, std::shared_ptr, c++11, c++14]

The C++98 and C++11 for-each range differences

While investigating a performance hit between two code paths, I discovered some interesting differences between the C++98 [1] and C++11 [2] for-each range implementations.

Here are the two different for-each ranges to compare:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
int main()
{
    constexpr unsigned numElts = 7;
    unsigned values[numElts]{2, 4, 6, 8, 12, 21, 101};

    for(unsigned idx = 0; idx < numElts; ++idx)
    {
      ++values[idx];
    }

    for(auto & v : values)
    {
      ++v;
    }
}

The first for-each range is a classical loop to access one-by-one all the elements of a list and perform a basic modification to each element. The second for-each range mimics the exact same processing, but it's written using the new C++11 for-each range writing.

Now use C++ Insights [3] to see the differences between the two implementations:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
int main()
{
  constexpr const unsigned int numElts = 7;
  unsigned int values[7] = {2, 4, 6, 8, 12, 21, 101};

  for(unsigned int idx = 0; idx < numElts; ++idx) 
  {
    ++values[idx];
  }

  {
    unsigned int (&__range1)[7] = values;

    for(unsigned int * __begin1 = __range1, *__end1 = __range1 + 7l; 
        __begin1 != __end1; ++__begin1) 
    {
      unsigned int & v = *__begin1;
      ++v;
    }
  }
}

Note that the C++98 does not change (i.e. only a minor change to clearly define the idx variable type).

Below is the assembly of the C++98 for-loop (i.e. use the online compiler godbolt [4]):


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
        mov     DWORD PTR [rbp-4], 0
.L3:
        cmp     DWORD PTR [rbp-4], 6
        ja      .L2
        mov     eax, DWORD PTR [rbp-4]
        mov     eax, DWORD PTR [rbp-80+rax*4]
        lea     edx, [rax+1]
        mov     eax, DWORD PTR [rbp-4]
        mov     DWORD PTR [rbp-80+rax*4], edx
        add     DWORD PTR [rbp-4], 1
        jmp     .L3
.L2:

Note how the gcc converts the C++11 for-each range to a classical C++98 for loop using pointer arithmetic.

Below is the assembly of the C++11 for-each range (i.e. use the online compiler godbolt [4]):


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
        lea     rax, [rbp-80]
        mov     QWORD PTR [rbp-32], rax
        mov     rax, QWORD PTR [rbp-32]
        mov     QWORD PTR [rbp-16], rax
        mov     rax, QWORD PTR [rbp-32]
        add     rax, 28
        mov     QWORD PTR [rbp-40], rax
.L5:
        mov     rax, QWORD PTR [rbp-16]
        cmp     rax, QWORD PTR [rbp-40]
        je      .L4
        mov     rax, QWORD PTR [rbp-16]
        mov     QWORD PTR [rbp-48], rax
        mov     rax, QWORD PTR [rbp-48]
        mov     eax, DWORD PTR [rax]
        lea     edx, [rax+1]
        mov     rax, QWORD PTR [rbp-48]
        mov     DWORD PTR [rax], edx
        add     QWORD PTR [rbp-16], 4
        jmp     .L5
.L4:



The C++11 implementation needs 9 assembly instructions for the C++98 implementation. But it requires 12 assembly instructions for the C++11 implementation. If you now consider all the loop (i.e. the loop content plus all declarations) then the difference is now 10 vs. 19.


References:

[1] C++98 for loop https://en.cppreference.com/w/cpp/language/for
[2] C++11 for-each range https://en.cppreference.com/w/cpp/language/range-for
[3] Andreas Fertig is the creator of C++ Insight, https://cppinsights.io/
[4] The online compiler from Matt Godbolt, https://godbolt.org/


[keywords: for loop, for-each range, c++98, c++11]

Saturday, September 7, 2019

Mandatory links to always have in your bookmarks

Let's start with useful links that you should always have in your browser bookmarks and which would never be out-dated.

Any questions around C / C++ / STL:
https://en.cppreference.com/w/

For any C++ compilation questions:
godbolt.org from Matt Godbolt https://xania.org/

Anything around modern C++ (C++11 and up) you would like to better understand:
C++ Insights from Andreas Vertig https://www.andreasfertig.info/index.html
"That's a Clang based tool which shows how the compiler sees your source code. It transforms your C++ input into an enriched version with all the things your compiler adds to the source code behind the scenes."


[keywords: c++, c++11, c++14, c++17, gcc, g++]