OK, I've recovered from being discouraged as I have thought of a workaround for the
alignof(std::max_align_t)
(or
__alignof(long double)
) problem.
I can proceed if the user supplies this value.
ie:
u_char* MyMalloc( u_16 Size, u_16 align );
@ JLBorges. Thanks again for your input.
Does my new approach here accomplish the desired pointer alignments?
Also, thanks to Framework for this thread: http://www.cplusplus.com/forum/general/89063/
wherein I learned about
std::uintptr_t
and it's usefulness in performing the necessary % (modulus) operation on an address.
I have modified the encoding for the 3 byte header. h[1], h[2] are still the same but now h[0] = 0 or 1+pad, instead of 0 or 1 only. This allows pointer validation and calculation of the used block size.
I doubt it will hurt to reveal my code for MyMalloc (which I have named Alloc) here.
First, a class declaration. A my buffer object:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
|
template<u_16 myBuffSz>
class myBuffer
{
public:
u_16 Nblocks;
myBuffer();
u_char* Alloc(u_16 Size);
u_char* Alloc(u_16 Size, u_16 align);
u_char* Alloc(const char* data, u_16 Size);
bool Free(u_char* base);
bool is_valid(u_char* base);// check if pointing to a block start position
bool is_valid(u_char* base, u_char& pad);// and return padding amount
u_16 Size(u_char* base);
u_char* begin() { return arr; }
u_char* end() { return arr + myBuffSz; }
private:
u_char arr[myBuffSz];
};
//...
template<u_16 myBuffSz>
u_char* myBuffer<myBuffSz>::Alloc(u_16 Size, u_16 align)// returns NULL if no Size block available
{
u_char* iter = arr;
while( iter < arr+myBuffSz )
{
u_16 len = iter[1] | (iter[2] << 8);// stored little endian (little byte 1st)
if( iter[0] == 0 )// block is unused
{
std::uintptr_t intAddy = (std::uintptr_t)(iter + 3);// address following 3 byte header block
u_16 pad = align - intAddy%align;// offset to beginning of "usable portion" of block
if( pad == align ) pad = 0;
u_16 SizeTotal = Size + pad;// actual needed block size
if( len >= SizeTotal + 3 )// block is large enough
{
iter[ SizeTotal+3 ] = 0;// mark remainder of block as unused
iter[ SizeTotal+4 ] = (len-(SizeTotal+3)) & 0xFF;// mark start of next block
iter[ SizeTotal+5 ] = ((len-(SizeTotal+3)) >> 8) & 0xFF;
iter[0] = 1 + pad;// new coding
iter[1] = SizeTotal & 0xFF;
iter[2] = (SizeTotal >> 8) & 0xFF;
++Nblocks;
cout << "pBlock - begin() = " << ( (iter + pad + 3)-begin() ) << endl;
cout << "pad = " << pad << endl;
return (iter + pad + 3);
}
}
iter += len + 3;// next block (or end)
}
return NULL;
}
|
I test this by allocating space for arrays of several types, then write to and read from these memory spaces using pointers to these types.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
|
// test struct
struct A
{
char ch;
int ix;
double dx;
};
ostream& operator<<( ostream& os, const A& rA )
{
os << "ch = " << rA.ch << " ix = " << rA.ix << " dx = " << rA.dx << '\n';
return os;
}
int main()
{
// std::cout << alignof(std::max_align_t) << '\n';
myBuffer<1000> buff_1;
u_16 i = 0;// for looping
// allocate space for 6 integers
int* pBlock_int = (int*)buff_1.Alloc( 6*sizeof(int), 4);
if( pBlock_int )
{
u_char* pBlock_ch = (u_char*)pBlock_int;
pBlock_int[0] = -3; pBlock_int[1] = 2758942; pBlock_int[2] = -1544; pBlock_int[3] = 7; pBlock_int[4] = 45; pBlock_int[5] = -31;
cout << "block_1 size = " << buff_1.Size( pBlock_ch ) << endl;
std::uintptr_t intAddy = (std::uintptr_t)(pBlock_int);
cout << "pBlock = " << intAddy << " pBlock%4 = " << intAddy%4 << endl;// verify alignment
for( i = 0; i < 6; ++i)
cout << pBlock_int[i] << " ";
}
else
cout << "Alloc() failed for 6 ints" << endl;
cout << endl << endl;
// allocate space for 4 floats
float* pBlock_f = (float*)buff_1.Alloc( 4*sizeof(float), 4);
if( pBlock_f )
{
u_char* pBlock_ch = (u_char*)pBlock_f;
pBlock_f[0] = -3.142f; pBlock_f[1] = 275.8942f; pBlock_f[2] = -154.3f; pBlock_f[3] = 0.0046f;
cout << "block size = " << buff_1.Size( pBlock_ch ) << endl;
std::uintptr_t intAddy = (std::uintptr_t)(pBlock_f);
cout << "pBlock = " << intAddy << " pBlock%4 = " << intAddy%4 << endl;// verify alignment
for( i = 0; i < 4; ++i)
cout << pBlock_f[i] << " ";
}
else
cout << "Alloc() failed for 4 floats" << endl;
cout << endl << endl;
// allocate space for 3 A's
cout << "sizeof(A) = " << sizeof(A) << endl;
A* pBlock_A = (A*)buff_1.Alloc( 3*sizeof(A), 8);
if( pBlock_A )
{
u_char* pBlock_ch = (u_char*)pBlock_A;
pBlock_A[0].ch = 'a'; pBlock_A[0].ix = -7; pBlock_A[0].dx = 3.1416;
pBlock_A[1].ch = 'b'; pBlock_A[1].ix = 72; pBlock_A[1].dx = 55.0;
pBlock_A[2].ch = 'c'; pBlock_A[2].ix = 49; pBlock_A[2].dx = -0.023;
cout << "block size = " << buff_1.Size( pBlock_ch ) << endl;
std::uintptr_t intAddy = (std::uintptr_t)(pBlock_A);
cout << "pBlock = " << intAddy << " pBlock%8 = " << intAddy%8 << endl;// verify alignment
for( i = 0; i < 3; ++i)
cout << pBlock_A[i];
}
else
cout << "Alloc() failed for 3 As" << endl;
cout << endl;
return 0;
}
|
Output:
pBlock - begin() = 4
pad = 1
block_1 size = 24
pBlock = 2292572 pBlock%4 = 0
-3 2758942 -1544 7 45 -31
pBlock - begin() = 32
pad = 1
block size = 16
pBlock = 2292600 pBlock%4 = 0
-3.142 275.894 -154.3 0.0046
sizeof(A) = 16
pBlock - begin() = 56
pad = 5
block size = 48
pBlock = 2292624 pBlock%8 = 0
ch = a ix = -7 dx = 3.1416
ch = b ix = 72 dx = 55
ch = c ix = 49 dx = -0.023
Process returned 0 (0x0) execution time : 0.047 s
Press any key to continue.
|
Does this testing verify that I'm doing all this right? Or, could this output be generated even if my Alloc() is returning improperly aligned pointers?
EDIT4: Answered my own question (yes). Changing
A* pBlock_A = (A*)buff_1.Alloc( 3*sizeof(A), 8);
to
A* pBlock_A = (A*)buff_1.Alloc( 3*sizeof(A), 2);
produces the same output except
pad = 1
pBlock = 2292624 pBlock%8 = 4 |
the array of 3 A's is aligned to a 4 byte boundary.
EDIT: The 1st 3 numbers output for each type: pBlock-begin(), pad and block size, all indicate that the allocations are occurring exactly as desired:
1 2 3
|
| 3 |1| 24 | 3 |1| 16 | 3 | 5 | 48...
.......^....................^........................^
.......4....................32.......................56
|
EDIT2: OK, I feel a bit dumb about this comment now:
...and somehow takes the name of a type as its "argument". |
when that is exactly what I'm doing here:
sizeof(int)
. I must have been frustrated.
EDIT3: I realize now that lines 41-43 in Alloc() may overwrite the header for the following block if the remainder of the just allocated block is < 3 bytes.
Although this doesn't affect the current testing, I will modify Alloc() soon to deal with a uselessly short remainder block.
EDIT5:
@blabla1. This is your thread. Any comments or questions so far?