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]);
}
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");
}
...
}
...
};
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
I've taken over some code, and came across a weird reallocation of an array. This is a function from within an Array class (used by the JsonValue)
void reserve( uint32_t newCapacity ) {
if ( newCapacity > length + additionalCapacity ) {
newCapacity = std::min( newCapacity, length + std::numeric_limits<decltype( additionalCapacity )>::max() );
JsonValue *newPtr = new JsonValue[newCapacity];
if ( length > 0 ) {
memcpy( newPtr, values, length * sizeof( JsonValue ) );
memset( values, 0, length * sizeof( JsonValue ) );
}
delete[] values;
values = newPtr;
additionalCapacity = uint16_t( newCapacity - length );
}
}
I get the point of this; it is just allocating a new array, and doing a copy of the memory contents from the old array into the new array, then zero-ing out the old array's contents. I also know this was done in order to prevent calling destructors, and moves.
The JsonValue is a class with functions, and some data which is stored in a union (string, array, number, etc.).
My concern is whether this is actually defined behaviour or not. I know it works, and has not had a problem since we began using it a few months ago; but if its undefined then it doesn't mean it is going to keep working.
EDIT:
JsonValue looks something like this:
struct JsonValue {
// …
~JsonValue() {
switch ( details.type ) {
case Type::Array:
case Type::Object:
array.destroy();
break;
case Type::String:
delete[] string.buffer;
break;
default: break;
}
}
private:
struct Details {
Key key = Key::Unknown;
Type type = Type::Null; // (0)
};
union {
Array array;
String string;
EmbedString embedString;
Number number;
Details details;
};
};
Where Array is a wrapper around an array of JsonValues, String is a char*, EmbedString is char[14], Number is a union of int, unsigned int, and double, Details contains the type of value it holds. All values have 16-bits of unused data at the beginning, which is used for Details. Example:
struct EmbedString {
uint16_t : 16;
char buffer[14] = { 0 };
};
Whether this code has well-defined behavior basically depends on two things: 1) is JsonValue trivially-copyable and, 2) if so, are a bunch of all-zero Bytes a valid object representation for a JsonValue.
If JsonValue is trivially-copyable, then the memcpy from one array of JsonValues to another will indeed be equivalent to copying all the elements over [basic.types]/3. If all-zeroes is a valid object representation for a JsonValue, then the memset should be ok (I believe this actually falls into a bit of a grey-area with the current wording of the standard, but I believe at least the intention would be that this is fine).
I'm not sure why you'd need to "prevent calling destructors and moves", but overwriting objects with zeroes does not prevent destructors from running. delete[] values will call the destructurs of the array members. And moving the elements of an array of trivially-copyable type should compile down to just copying over the bytes anyways.
Furthermore, I would suggest to get rid of these String and EmbedString classes and simply use std::string. At least, it would seem to me that the sole purpose of EmbedString is to manually perform small string optimization. Any std::string implementation worth its salt is already going to do exactly that under the hood. Note that std::string is not guaranteed (and will often not be) trivially-copyable. Thus, you cannot simply replace String and EmbedString with std::string while keeping the rest of this current implementation.
If you can use C++17, I would suggest to simply use std::variant instead of or at least inside this custom JsonValue implementation as that seems to be exactly what it's trying to do. If you need some common information stored in front of whatever the variant value may be, just have a suitable member holding that information in front of the member that holds the variant value rather than relying on every member of the union starting with the same couple of members (which would only be well-defined if all union members are standard-layout types that keep this information in their common initial sequence [class.mem]/23).
The sole purpose of Array would seem to be to serve as a vector that zeroes memory before deallocating it for security reasons. If this is the case, I would suggest to just use an std::vector with an allocator that zeros memory before deallocating instead. For example:
template <typename T>
struct ZeroingAllocator
{
using value_type = T;
T* allocate(std::size_t N)
{
return reinterpret_cast<T*>(new unsigned char[N * sizeof(T)]);
}
void deallocate(T* buffer, std::size_t N) noexcept
{
auto ptr = reinterpret_cast<volatile unsigned char*>(buffer);
std::fill(ptr, ptr + N, 0);
delete[] reinterpret_cast<unsigned char*>(buffer);
}
};
template <typename A, typename B>
bool operator ==(const ZeroingAllocator<A>&, const ZeroingAllocator<B>&) noexcept { return true; }
template <typename A, typename B>
bool operator !=(const ZeroingAllocator<A>&, const ZeroingAllocator<B>&) noexcept { return false; }
and then
using Array = std::vector<JsonValue, ZeroingAllocator<JsonValue>>;
Note: I fill the memory via volatile unsigned char* to prevent the compiler from optimizing away the zeroing. If you need to support overaligned types, you can replace the new[] and delete[] with direct calls to ::operator new and ::operator delete (doing this will prevent the compiler from optimizing away allocations). Pre C++17, you will have to allocate a sufficiently large buffer and then manually align the pointer, e.g., using std::align…
I'm trying to allocate an array of struct and I want each struct to be aligned to 64 bytes.
I tried this (it's for Windows only for now), but it doesn't work (I tried with VS2012 and VS2013):
struct __declspec(align(64)) A
{
std::vector<int> v;
A()
{
assert(sizeof(A) == 64);
assert((size_t)this % 64 == 0);
}
void* operator new[] (size_t size)
{
void* ptr = _aligned_malloc(size, 64);
assert((size_t)ptr % 64 == 0);
return ptr;
}
void operator delete[] (void* p)
{
_aligned_free(p);
}
};
int main(int argc, char* argv[])
{
A* arr = new A[200];
return 0;
}
The assert ((size_t)this % 64 == 0) breaks (the modulo returns 16). It looks like it works if the struct only contains simple types though, but breaks when it contains an std container (or some other std classes).
Am I doing something wrong? Is there a way of doing this properly? (Preferably c++03 compatible, but any solution that works in VS2012 is fine).
Edit:
As hinted by Shokwav, this works:
A* arr = (A*)new std::aligned_storage<sizeof(A), 64>::type[200];
// this works too actually:
//A* arr = (A*)_aligned_malloc(sizeof(A) * 200, 64);
for (int i=0; i<200; ++i)
new (&arr[i]) A();
So it looks like it's related to the use of new[]... I'm very curious if anybody has an explanation.
I wonder why you need such a huge alignment requirement, moreover to store a dynamic heap allocated object in the struct. But you can do this:
struct __declspec(align(64)) A
{
unsigned char ___padding[64 - sizeof(std::vector<int>)];
std::vector<int> v;
void* operator new[] (size_t size)
{
// Make sure the buffer will fit even in the worst case
unsigned char* ptr = (unsigned char*)malloc(size + 63);
// Find out the next aligned position in the buffer
unsigned char* endptr = (unsigned char*)(((intptr_t)ptr + 63) & ~63ULL);
// Also store the misalignment in the first padding of the structure
unsigned char misalign = (unsigned char)(endptr - ptr);
*endptr = misalign;
return endptr;
}
void operator delete[] (void* p)
{
unsigned char * ptr = (unsigned char*)p;
// It's required to call back with the original pointer, so subtract the misalignment offset
ptr -= *ptr;
free(ptr);
}
};
int main()
{
A * a = new A[2];
printf("%p - %p = %d\n", &a[1], &a[0], int((char*)&a[1] - (char*)&a[0]));
return 0;
}
I did not have your align_malloc and free function, so the implementation I'm providing is doing this:
It allocates larger to make sure it will fit in 64-bytes boundaries
It computes the offset from the allocation to the closest 64-bytes boundary
It stores the "offset" in the padding of the first structure (else I would have required a larger allocation space each time)
This is used to compute back the original pointer to the free()
Outputs:
0x7fff57b1ca40 - 0x7fff57b1ca00 = 64
Warning: If there is no padding in your structure, then the scheme above will corrupt data, since I'll be storing the misalignement offset in a place that'll be overwritten by the constructor of the internal members.
Remember that when you do "new X[n]", "n" has to be stored "somewhere" so when calling delete[], "n" calls to the destructors will be done. Usually, it's stored before the returned memory buffer (new will likely allocate the required size + 4 for storing the number of elements). The scheme here avoid this.
Another warning: Because C++ calls this operator with some additional padding included in the size for storing the array's number of elements, you'll might still get a "shift" in the returned pointer address for your objects. You might need to account for it. This is what the std::align does, it takes the extra space, compute the alignment like I did and return the aligned pointer. However, you can not get both done in the new[] overload, because of the "count storage" shift that happens after returning from new(). However, you can figure out the "count storage" space once by a single allocation, and adjust the offset accordingly in the new[] implementation.
I want to copy different values of type Ts to a char buffer and be able to use them later by using reinterpret_cast. Sample code:
template<typename T>
char* Append(char* buffer, const T& value)
{
new(buffer) T(value);
return buffer + sizeof(T);
}
Is it possible to check if buffer respects the alignment requirements of T? As an example where buffer doesn't respect alignment requirements of T is any chaining of Append calls with sizeof(T0) < sizeof(T1)
EDIT:
How can check if new(buffer) T(value); is valid knowing only the address and the type?
Something along these lines perhaps:
char* fixedBuffer = buffer;
fixedBuffer = std::align(alignof(T), sizeof(T), fixedBuffer, sizeOfBuffer);
if (!fixedBuffer) {
// buffer is too small
} else if (fixedBuffer != buffer) {
// buffer is misaligned; fixedBuffer points to the correctly aligned offset
} else {
// buffer is aligned as-is.
}
I'm building my own string class in c++ 11 and I have a memory problem.
in main:
MyString str1; //Works ok, constructor creates empty char array.
const char* pointer1 = str1.c_str(); //Return the pointer to the array.
str1.Reserve(5);
// Now, when I use the Reverse method in string1, Pointer1 is
// pointing to the old memory address.
How to I change the array data in str1, but to the memory address?
With aother words, How do I fix this so that:
pointer1 == str1.c_str();
Reserve method:
void reserve(int res)
{
capacity = NewSize(size + res,0 , capacity); //Method to find the best cap.
char* oldData = data;
data = new char[capacity];
memcpy(data, oldData, capacity);
oldData = data;
//delete[] data;
data[(size)] = '\0';
}
This returns all the right data, but when I do "oldData = data", the memory address is lost.
I appreciate all help, thanks!
I think what you are asking is if there is a way to get a return value from your string class which will always point to the current string array. There are a number of ways to do this but generally this indicates bad design/implementation.
The more normal way to do this would be to advise API users that the result of c_str() is invalidated by any subsequent modifications to the object: don't keep the pointer, just call c_str() again.
Two obvious options are: a) a pointer to the pointer, very dangerous because now someone outside your class can tweak it, b) provide a wrapper class which encapsulates a pointer-to-pointer without allowing modifications.
template<typename T>
struct ReadOnlyPointer {
T* m_ptr;
... operator * ...
... operator -> ...
... operator T ...
};
ReadOnlyPointer<const char*> pointer = str1.pointer();
There also appear to be at least a couple of issues with your "reserve" function.
You push a '\0' at data[0] even though size might be zero.
MyString a;
a.reserve(0); // crash? you wrote to the first byte of a zero length array.
After copying the data from oldData to data, for some reason you assign the value of 'data' to 'oldData' and then never use 'oldData' again - this is a memory leak.
Your memcpy uses 'capacity' instead of 'size' so it may be over-copying.
Consider instead:
// ensure we have an additional 'res' bytes.
// caution: unlike stl and boost reserve, these are
// additional bytes, not total bytes.
void reserve(int res)
{
int newCapacity = NewSize(m_size + res, 0, m_capacity); //Method to find the best cap.
if(newCapacity <= m_capacity)
return;
char* newData = new char[newCapacity];
memcpy(newData, m_data, m_size);
delete[] m_data; // release the old allocation
m_data = newData;
m_capacity = newCapacity;
}
The extra data[(size)] = '\0'; could be the cause of your string becoming truncated if you are not changing the value of size elsewhere in your code.
I need help in understanding the code snipped below...allocate is a function that would be called by the overloaded new operator to allocate memory. I am having problems trying to understand the following casts in particular:
*static_cast<std::size_t*>(mem) = pAmount; //please explain?
return static_cast<char*>(mem) + sizeof(std::size_t); //?
and..
// get original block
void* mem = static_cast<char*>(pMemory) - sizeof(std::size_t); //?
the code is shown below:
const std::size_t allocation_limit = 1073741824; // 1G
std::size_t totalAllocation = 0;
void* allocate(std::size_t pAmount)
{
// make sure we're within bounds
assert(totalAllocation + pAmount < allocation_limit);
// over allocate to store size
void* mem = std::malloc(pAmount + sizeof(std::size_t));
if (!mem)
return 0;
// track amount, return remainder
totalAllocation += pAmount;
*static_cast<std::size_t*>(mem) = pAmount;
return static_cast<char*>(mem) + sizeof(std::size_t);
}
void deallocate(void* pMemory)
{
// get original block
void* mem = static_cast<char*>(pMemory) - sizeof(std::size_t);
// track amount
std::size_t amount = *static_cast<std::size_t*>(mem);
totalAllocation -= pAmount;
// free
std::free(mem);
}
The allocator keeps track of the size of allocations by keeping them along with the blocks it serves to client code. When asked for a block of pAmount bytes, it allocates an extra sizeof(size_t) bytes at the beginning and stores the size there. To get to this size, it interprets the mem pointer it gets from malloc as a size_t* and dereferences that (*static_cast<std::size_t*>(mem) = pAmount;). It then returns the rest of the block, which starts at mem + sizeof(size_t), since that is the part that the client may use.
When deallocating, it must pass the exact pointer it got from malloc to free. To get this pointer, it subtracts the sizeof(size_t) bytes it added in the allocate member function.
In both cases, the casts to char* are needed because pointer arithmetic is not allowed on void pointers.
void* allocate(std::size_t pAmount)
allocates pAmount of memory plus space to store the size
|-size-|---- pAmount of memory-----|
^
|
"allocate" will return a pointer just pasted the size field.
void deallocate(void* pMemory)
will move the pointer back to the beginning
|-size-|---- pAmount of memory-----|
^
|
and free it.
1.)
std::size_t mySize = 0;
void * men = & mySize;
// same as: mySize = 42;
*static_cast<std::size_t*>(mem) = 42;
std::cout << mySize;
// prints "42"
2.)
`return static_cast<char*>(mem) + sizeof(std::size_t);
// casts void pointer mem to a char* so that you can do pointer arithmetic.
// same as
char *myPointer = (char*)mem;
// increment myPointer by the size of size_t
return myPointer + sizeof(std::size_t);
3.)
`void* mem = static_cast<char*>(pMemory) - sizeof(std::size_t);`
// mem points size of size_t before pMemory
In order to know how much memory to clean up when you delete it (and provide some diagnostics) the allocator stores off the size in extra allocated memory.
*static_cast(mem) = pAmount; //please explain?
This takes the allocated memory and stores the number of allocated bytes into this location. The cast treats the raw memory as a size_t for storage purposes.
return static_cast(mem) +
sizeof(std::size_t); //?
This moves forward past the size bytes to the actual memory that your application will use and returns that pointer.
void* mem =
static_cast(pMemory) -
sizeof(std::size_t); //?
This is taking the block previously returned to the user and advancing back to the "real" allocated block that stored the size earlier. It's needed to do checks and reclaim the memory.
the cast is needed in order to get the proper offset since void* is not a type with a size.
when you write
return static_cast(mem) + sizeof(std::size_t);
the pointer is cast to a char* before the offset bytes is added.
ditto subtract when deallocating.