If I implement a C callback like this:
register_callback([](/*some args*/){/*some stuff*/});
I get a SIGSEGV when it triggers, but if I register it like this:
auto const f([](/*some args*/){/*some stuff*/});
register_callback(f);
Then it works fine. Of particular interest (to me) is the stack trace produced by the address sanitizer:
ASAN:SIGSEGV
=================================================================
==22904==ERROR: AddressSanitizer: SEGV on unknown address 0x7f1582c54701 (pc 0x7f1582c54701 sp 0x7f1582c544a8 bp 0x7f1582c54510 T2)
#0 0x7f1582c54700 ([stack:22906]+0x7fc700)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV ??:0 ??
It looks, as if the function pointer pointed to the stack. Does pushing a lambda onto the stack push code onto the stack? Since I capture nothing the location of the function pointer is a mystery to me. What is happening? No optimization flags were used. I am not looking for workarounds.
EDIT: Apparently the '+' was the key to a working example. I don't know why it is necessary. Remove the '+' and the example with compile but SIGSEGV will be triggered with both clang-3.5
and gcc-4.9
.
#include <curl/curl.h>
#include <ostream>
#include <iostream>
int main()
{
auto const curl(curl_easy_init());
if (curl)
{
curl_easy_setopt(curl, CURLOPT_URL, "cnn.com");
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
/*
auto const f([](char* const ptr, size_t const size, size_t const nmemb,
void* const data)
{
*static_cast<::std::ostream*>(data) << ptr;
return size * nmemb;
}
);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, +f);
*/
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
+[](char* const ptr, size_t const size, size_t const nmemb,
void* const data)
{
*static_cast<::std::ostream*>(data) << ptr;
return size * nmemb;
}
);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &::std::cout);
curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
return 0;
}
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With