“C++ Brain Teasers”

A fun book I finished Sunday: “C++ Brain Teasers” by Anders Schau Knatten. The book site includes a few sample chapters. The acknowledgments point to https://cppquiz.org/.

I enjoyed it a lot. Having lessons in bite-sized chunks in a challenge-answer format is a nice way to learn. I also appreciated the expert (but not overly long) analysis of each situation and the bullet point summaries of the advice. I’m also reminded to remain humble.

I’m hoping to have time to follow up with the links for each chapter, including links to the author’s blog posts: https://blog.knatten.org/.

One thing I wanted to play with was uninitialized local variables picking up data from previous calls as discussed in the 3rd puzzle “Hack the Planet!” First, let’s recreate something like the example from the book:

// uninitialized1.cc 
#include <iostream>

int global_x = 1;

int make_num() {
    return global_x == 1 ? 42 : 43;
}

void foo() {
    int num = make_num();
    std::cout << "foo: " << num << '\n';
}

void bar() {
    int num;
    std::cout << "bar: " << num << '\n';
}

int main() {
    foo();
    bar();
}

Running this, I get the equivalent results to what the book shows:

% g++ uninitialized1.cc 
% ./a.out 
foo: 42
bar: 42

I think for me, the version of pair used by default by g++ looks like this:

template <class _T1, class _T2>
struct _LIBCPP_TEMPLATE_VIS pair
...
{
...

  _T1 first;
  _T2 second;

Let’s try playing with that:

// pair_fun.cc 
#include <iostream>
#include <utility>

void foo() {
    std::pair<int, int> p{12, 34};
    std::cout << p.first << ", " << p.second << '\n';
}

void bar() {
    int first;
    int second;
    std::cout << first << ", " << second << '\n';
}

int main() {
    foo();
    bar();
}

Here’s what I see for that:

% g++ pair_fun.cc
% ./a.out        
12, 34
34, 12

Interesting that the order of the elements is reversed. How about a bigger struct?

// struct_fun.cc
#include <iostream>

struct S {
  int w;
  int x;
  int y;
  int z;
};

void foo() {
  S s{-21, 12, 34, 56};
  std::cout << s.w << ", " << s.x << ", " << s.y << ", " << s.z << '\n';
}

void bar() {
  int w;
  int x;
  int y;
  int z;
  std::cout << w << ", " << x << ", " << y << ", " << z << '\n';
}

int main() {
  foo();
  bar();
}

Running that:

% g++ struct_fun.cc
% ./a.out 
-21, 12, 34, 56
56, 34, 12, -21

Yup, reversed again. Maybe this is a consequence of the standard: Order Your Members quotes from it: “Nonstatic data members of a (non-union) class with the same access control are allocated so that later members have higher addresses within a class object.” In analogy with array indexes, I would’ve guessed that higher = further down the stack, but it seems like the opposite is true? (With three members in S, bar‘s vars didn’t line up with foo‘s, maybe due to padding/alignment.)

I think for me, the version of vector used by default by g++ looks like this:

private:
  pointer __begin_ = nullptr;
  pointer __end_   = nullptr;
  _LIBCPP_COMPRESSED_PAIR(pointer, __cap_ = nullptr, allocator_type, __alloc_);

From the above, if we can access these, we’ll expect them to be in the reverse order on the stack. Trying this:

#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>

template<typename Iter>
void print(Iter begin, Iter end) {
    std::copy(begin, end, std::ostream_iterator<std::iter_value_t<Iter>>(std::cout, ", "));
    std::cout << '\n';
}

void foo() {
    std::vector<int> v{11, 23};
    std::cout << "begin: " << reinterpret_cast<long long>(v.data()) << '\n';
    std::cout << "end: " << reinterpret_cast<long long>(v.data() + 2) << '\n';
    print(v.begin(), v.end());
}

void bar() {
    std::vector<int>::allocator_type allocator;
    std::vector<int>::pointer cap;
    std::vector<int>::pointer end;
    std::vector<int>::pointer begin;
    std::cout << "begin: " << reinterpret_cast<long long>(begin) << '\n';
    std::cout << "end: " << reinterpret_cast<long long>(end) << '\n';
    print(begin, end);
}

int main() {
    foo();
    bar();
}

Running that:

% g++ --std=c++20 vector_fun.cc
% ./a.out                      
begin: 4339604016
end: 4339604024
11, 23, 
begin: 4339604016
end: 4339604024
3, 0, 

So, we are able to pick up the addresses for the beginning and end of the vector, but it looks like values within the range defined by them got overwritten before we got a chance to read them.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *