c++boostundefined-behaviorunique-ptrasio

Boost ASIO "Bad address" error when passing unique_ptr to completion handler


I'm trying to implement a simple TCP server using ASIO. The main difference here is that I'm using std::unique_ptr to hold the buffers instead of raw pointers and I'm moving them inside the completion handler in order to extend its lifetime.

Here is the code and it works just fine (I guess)

void do_read() {
    auto data = std::make_unique<uint8_t[]>(1500);
    auto undesired_var = boost::asio::buffer(data.get(), 1500);
    socket_.async_read_some(
           undesired_var,
           [self = shared_from_this(), data = std::move(data)](auto ec, auto len) mutable {
                if (!ec) {
                    std::cout << "Received " << len << " bytes :)" << std::endl;
                } else {
                    std::cout << "Read error: " << ec.message() << std::endl;
                }
           }
    );
}

Running the code above, I get the following output:

/tcp_echo/cmake-build-debug/bin/tcp_server 6666
Received 9 bytes :)

Note that I created a variable called undesired_var in order to hold the boost::asio::mutable_buffer data. When I try to remove those variables by creating them directly in the socket_.async_read_some call:

void do_read() {
    auto data = std::make_unique<uint8_t[]>(1500);
    socket_.async_read_some(
            boost::asio::buffer(data.get(), 1500),
            [self = shared_from_this(), data = std::move(data)](auto ec, auto len) mutable {
            if (!ec) {
                std::cout << "Received " << len << " bytes :)" << std::endl;
            } else {
                std::cout << "Read error: " << ec.message() << std::endl;
            }
        }
    );
}

I got the following output

tcp_echo/cmake-build-debug/bin/tcp_server 6666
Read error: Bad address

It seems that in the second case my std::unique_ptr is getting destroyed prematurely, but I can figure out why. Did I miss something?


Solution

  • Yeah it's the order in which argument expressions are evaluated.

    The std::move can happen before you do .get().

    Another Bug:

    Besides, there seems to be a large problem here:

    auto data = std::make_unique<uint8_t>(1500);
    

    That dynamically allocates an unsigned char (uint8_t) which is initialized from the integer value 1500. The allocation has size 1 (1 byte).

    auto undesired_var = boost::asio::buffer(data.get(), 1500);
    

    You're using it as if it is 1500 bytes. Oops. You probably meant:

    auto data = std::make_unique<uint8_t[]>(1500);
    

    The type of data is now std::unique_ptr<uint8_t[], std::default_delete<uint8_t[]>> which is an entirely different template specialization