lambda object + c callback sigsegv

curl_easy_setopt is defined as (in curl/easy.h):

CURL_EXTERN CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...);

That means that the third argument param must be of a type that can be passed as a C variadic. Unfortunately, while curl_easy_setopt is expecting a function pointer, passing class objects (and lambdas are class objects) is "conditionally-supported with implementation-defined semantics" ([expr.call]/7), so the compiler accepts it but then curl_easy_setopt tries to interpret the lambda object as a function pointer, with catastrophic results.

The object that you actually pass is a captureless lambda which means that it is an empty class object, of size 1 byte (all most-derived objects must be at least one byte in size). The compiler will promote that argument to a word-size integer (4 bytes on 32-bit, 8 bytes on 64-bit) and either pass 0 or leave that register/stack slot unset, meaning that garbage gets passed (since the lambda doesn't actually use its memory footprint when called).


I've just write a similar lambda with libcurl, and got crash, after checking carefully, I got the following code working like charm.

The magic is, add the leading + at the non-captured lambda expression, which will trigger conversion to plain C function pointer.

curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
  /* NOTE: Leader '+' trigger conversion from non-captured Lambda Object to plain C pointer */
  +[](void *buffer, size_t size, size_t nmemb, void *userp) -> size_t {
    // invoke the member function via userdata
    return size * nmemb;
  });

My understand is, the curl_easy_setopt() wants a void*, not an explicit function type, so the compiler just gives the address of lambda OBJECT; if we do function pointer operation on lambda object, the compiler will return the function pointer from lambda object.

Tags:

C++

C

Lambda

C++11