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, usingnew CharT[]
normally will suffice.You are copying 1 too many
CharT
s fromsv
into the memory you are allocating.buffer[8]
is a singlebyte
, 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 aCharT*
pointer to the actual bytes following your length and flag bytes (ie, for a stack-allocated string), then reinterpreting the byte address as aCharT*
is fine. But, if you are intending to return the storedCharT*
pointer (ie, to a heap-allocated string), then you need to instead reinterpret the byte address asCharT**
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 yourbuffer
, you are callingoperator delete[]
to free the memory you just allocated, thus leavingbuffer
with a dangling pointer.if the
buffer
holds a pointer to dynamically allocate memory, and theset()
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));