Home > Enterprise >  Saving a void pointer in an array of bytes
Saving a void pointer in an array of bytes

Time:01-25

I have this assignment for homework, which consists of me implementing a buffer optimization to a string class. I have to save the pointer from bytes 8 to 16, because the first byte is used to see if the string is heap-allocated or stack-allocated, and bytes 1-7 are used to save the length of the string. I need help finding out how I can save a pointer to dynamically allocated memory and then return it from bytes 8-16.

How I create the buffer:

alignas(CharT) std::array<std::byte, SZ> buffer;

How I want to return the buffer:

return reinterpret_cast<CharT *>(buffer[8]);

I tried to use memcpy() and memmove(), but when I do that I am able to return only 8 bytes from the original string.

How I create and manipulate the pointer:

auto ptr = ::operator new[](count_bytes, std::align_val_t{std::alignment_of_v<CharT>});
std::memcpy(ptr, sv.data(), count_bytes);
std::memmove(&buffer[8], ptr, sizeof(ptr));

The implementation of the buffer:

template<typename CharT = char, std::size_t SZ = 32>
struct storage {
private: 
    alignas(CharT) std::array<std::byte, SZ> buffer;

public:

    void set(const std::basic_string_view<CharT> &sv) { 
        std::size_t count_bytes = sizeof(CharT) * (sv.length()   1); ///counts the bytes of the string
        auto ptr = ::operator new[](count_bytes, std::align_val_t{std::alignment_of_v<CharT>}); /// make a pointer to the memory location
        std::memcpy(ptr, sv.data(), count_bytes);
        std::memmove(&buffer[8], &ptr, sizeof(ptr));
        operator delete[](ptr);
    }

    const CharT *str() {
        /// Returs a pointer to the first byte of the string
        return reinterpret_cast<CharT *>(buffer[8]);
    }

CodePudding user response:

I see several issues with your code:

  • There is no need to use ::operator new[] directly, using new CharT[] normally will suffice.

  • You are copying 1 too many CharTs from sv into the memory you are allocating.

  • buffer[8] is a single byte, whose value you are reinterpreting as a pointer, which is wrong. You need to instead reinterpret the address of that byte. If you are intending to return a CharT* pointer to the actual bytes following your length and flag bytes (ie, for a stack-allocated string), then reinterpreting the byte address as a CharT* is fine. But, if you are intending to return the stored CharT* pointer (ie, to a heap-allocated string), then you need to instead reinterpret the byte address as CharT** and dereference that pointer.

  • you are not storing the allocated memory length or your allocation flag into your buffer, as you have described it must contain.

  • After copying the value of ptr into your buffer, you are calling operator delete[] to free the memory you just allocated, thus leaving buffer with a dangling pointer.

  • if the buffer holds a pointer to dynamically allocate memory, and the set() is called again, the previous allocated memory is leaked.

With that said, try something more like this instead:

template<typename CharT = char, std::size_t SZ = 32>
struct storage {
private: 
    static_assert(SZ >= (8   sizeof(CharT*))), "SZ is too small");

    struct header {
        std::uint64_t flag: 8;
        std::uint64_t length: 56;
        union {
            CharT* ptr;
            std::byte data[SZ-8];
        } u;
    };

    alignas(CharT) std::array<std::byte, SZ> buffer;

public:

    void clear() {
        header *hdr = reinterpret_cast<header *>(&buffer[0]);
        if (hdr->flag == 1)
            delete[] hdr->u.ptr;
        buffer.fill(0);
    }

    void set(const std::basic_string_view<CharT> &sv) { 
        std::uint64_t len = sv.length();
        if (len > 0x00FFFFFFFFFFFFFFULL) { /// make sure the length fits in 7 bytes
            throw std::length_error("sv is too long in length");
        }

        clear();

        header hdr;
        hdr.flag = 1;
        hdr.length = len;
        hdr.u.ptr = new CharT[len 1]; /// make a pointer to the memory location
        std::copy_n(sv.data(), len, hdr.u.ptr);
        hdr.u.ptr[len] = static_cast<CharT>(0);

        std::memcpy(&buffer, &hdr, sizeof(hdr));
    }

    const CharT* str() const {
        /// Returns a pointer to the first byte of the string
        const header *hdr = reinterpret_cast<const header *>(&buffer[0]);
        if (hdr->flag == 1)
            return hdr->u.ptr;
        else
            return reinterpret_cast<const CharT*>(hdr->u.data);
    }

    uint64_t length() const {
        /// Returns the string length
        return reinterpret_cast<const header *>(&buffer[0])->length;
    }

    ...
};

That being said, I would take this a step further by making the flag field be 1 bit instead of 1 whole byte, thus giving the length field 7 more bits to work with, eg:

template<typename CharT = char, std::size_t SZ = 32>
struct storage {
private: 
    ...

    struct header {
        std::uint64_t flag: 1;
        std::uint64_t length: 63;
        ...
    };

    ...

public:

    void set(const std::basic_string_view<CharT> &sv) { 
        std::uint64_t len = sv.length();
        if (len > std::numeric_limits<int64_t>::max()) { /// make sure the length fits in 63 bits
            throw std::length_error("sv is too long in length");
        }

        ...
    }

    ...
};

CodePudding user response:

the bytes 1-7 are used to save the length

You meant bytes 0-7.

std::memmove(&buffer[8], ptr, sizeof(ptr));

The 2nd parameter to memmove is the starting address of the bytes to copy. You are telling memmove to copy sizeof(ptr) bytes from the memory address that ptr is pointing to.

You describe your intended goal as to copy the pointer itself, its sizeof(ptr) bytes, rather than sizeof(ptr) from wherever the pointer is pointing to. If so, then this should be:

std::memmove(&buffer[8], &ptr, sizeof(ptr));
  • Related