vendredi 18 août 2017

Why does accessing my capture-by-reference variable cause a segfault in my lambda function?

I am going to give a presentation on lambda expressions for our local C/C++ Meetup, so I am preparing several examples to demonstrate how lambda expressions can solve certain tasks.

One of the examples is to create a qsort function that takes a lambda expression for the comparison function. It works fine for the simple case of sort_ascending() below, but for sort_descending(), where I include a counter integer that is captured by reference and incremented with each call to the lambda expression, I get a segfault at the first attempt to increment the counter.

I've looked at the lambda expression documentation I could find, especially cppreference.cpp. As far as I can tell, there's no reason why testcount in sort_descending should be out-of-scope: testcount is in the same stack frame as the call to qsort_l, and the lambda expression that captured testcount is no longer used when qsort_l returns.

// -*- compile-command: "g++ -std=c++11 -ggdb -Wno-pmf-conversions -Wall -Werror -Weffc++ -pedantic -o minfail minfail.cpp" -*-

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

void print_ints(const int* arr, int count)
{
   for (int i=0; i<count; ++i)
   {
      if (i)
         putchar(' ');

      printf("%3d", arr[i]);
   }

   putchar('\n');
}

class Compar_Base
{
public:
   virtual ~Compar_Base()              { }
   virtual int comp(const void* left, const void* right) const = 0;

   static int compare(const void* left, const void* right, void* obj)
   {
      return static_cast<Compar_Base*>(obj)->comp(left,right);
   }
};

template <typename Func>
class Compar_Concrete : public Compar_Base
{
   Func &m_f;
public:
   Compar_Concrete(Func f) : m_f(f) { }
   virtual ~Compar_Concrete()       { }
   Compar_Concrete(const Compar_Concrete&) = delete;
   Compar_Concrete& operator=(const Compar_Concrete&) = delete;
   virtual int comp(const void* left, const void* right) const
   {
      return m_f(left,right);
   }
};

/** Shiny, new qsort for lambda expressions. */
template <typename Func>
void qsort_l(void *base, size_t member_count, size_t member_size, Func f)
{
   Compar_Concrete<Func> comp(f);
   qsort_r(base, member_count, member_size, Compar_Base::compare, &comp);
}

void sort_ascending(int* intlist, int count)
{
   auto f = [](const void* left, const void* right) -> int
   {
      return *static_cast<const int*>(left) - *static_cast<const int*>(right);
   };

   qsort_l(intlist, count, sizeof(int), f);
}

void sort_descending(int* intlist, int count)
{
   int testcount = 0;
   auto f = [&testcount](const void* left, const void* right) -> int
   {
      // Segmentation fault at this line:
      ++testcount;

      return *static_cast<const int*>(right) - *static_cast<const int*>(left);
   };

   qsort_l(intlist, count, sizeof(int), f);

   printf("\nIt took %d tests to complete the sort.\n", testcount);
}


int main(int argc, char** argv)
{
   int ilist[] = {1,9,2,8,3,7,4,6,5};
   int count = sizeof(ilist) / sizeof(int);

   print_ints(ilist,count);
   sort_ascending(ilist, count);
   print_ints(ilist,count);
   sort_descending(ilist, count);
   print_ints(ilist,count);
}

I compiled the code above using g++ versions 4.8.4 and 5.4.0 with the same result (ie a segfault). You can see the compiler options in use by looking at the compile-command variable declared at the top of the code listing.

Aucun commentaire:

Enregistrer un commentaire