|
int j = 0x21;
size_t n5 = CreateArrayAtMemory<int>(a5, 2,3,4,5,6);
vector<char> a5(n5);
int***** f5 = (int*****)a5.data();
CreateArrayAtMemory(f5,2,3,4,5,6);
a5 is not initialized, there must be nullptr.
|
|
|
|
|
Not rated. But you will get my big 5 after you fix the implementation.
A C++ array is a contiguous place in the memory.
For instance an unidimensional array of 12 elements is placed like this:
T arr[12] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}
An element with index [4] is accessed like arr+4
A bidimensional array of 2x6 in memory is placed like this
T arr[2][6] = {{0, 1, 2, 3, 4, 5}, {6, 7, 8, 9, 10, 11}}, and in memory it is identical to the one above {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}
An element with indexes [1][2] is accessed like this: arr + (6)*1 + 2;
A tridimensional array of 2x4x2 in memory is placed like this.
T arr[2][4][2] = {{{0, 1}, {2, 3}, {4, 5}, {6, 7}}, {{8, 9}, {10, 11}, {12, 13}, {14, 15}}},
and in memory it is identical to {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
An element with indexes [1][2][1] is accessed like this: arr + (2*4)*1+(2)*2+1; where d*4 is size of (2x4) bidimensional array and (2) is size of unidimensional array,
Try to do the same with your arrays of pointers, pointers to pointers and so on
Keep in mind that in a C++ multidimensional array, all the pointers arr, &arr, arr[0], &arr[0], arr[0][0], &arr[0][0], arr[0][0][0] and so on, will point to exactly the same address.
------------------------------------------------------
Your implementation is very different and look like this
Unidimensional array:
T* ptr = dynamic addr that points to a contiguous {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}
Bidimensional array is even worse
T** ptr = dynamic addr that points to an array of pointers {T* ptr1, T* ptr2}
And each of ptr1, ptr2 is a pointer pointing to some very different places in the memory. A multidimensional array is always pointing to the same place in the memory.
And each of ptr1, ptr2 is a pointer pointing to some very different places in the memory, looking like this
ptr1 {0, 1, 2, 3, 4, 5} random memory ptr2 {6, 7, 8, 9, 10, 11}
A tridimensional array looks like this
T*** ptr = dynamic addr that points to an array of pointers {T** ptr1, T** ptr2}, ok, you probably got the idea. You keep arrays of pointers to pointers to pointers, array to pointers to pointers, array of pointers, and then arrays.
Keep in mind that arr, &arr, arr[0], &arr[0], arr[0][0], &arr[0][0], arr[0][0][0] and so on, all that pointers will point to different addresses. Each of your index is a new pointer that points to different part of memory.
crc of abc
modified 6-May-20 14:27pm.
|
|
|
|
|
The test examples in the current version of the article are incorrect: the first calls to CreateArrayAtMemory are correct, but they only calculate the amount of memory needed. The second calls are incorrect: they are missing the template argument, which then - incorrectly - defaults to char .
Suggestion: drop the default argument! It's not that helpful, and if someone accidentally forgets to attach the correct template argument, you're risking errors that are hard to detect and fix.
Better yet: turn the type of the first argument into a template parameter and derive the element type from that. See remove_pointer - C++ Reference[^]
GOTOs are a bit like wire coat hangers: they tend to breed in the darkness, such that where there once were few, eventually there are many, and the program's architecture collapses beneath them. (Fran Poretto)
|
|
|
|
|
The code works for integers. But not for other types.
|
|
|
|
|
|
I replaced short by double in your sample code and get:
0 0 0 36 37 38
56 bytes used
|
|
|
|
|
Did you replace it everywhere?
size_t n2 = CreateArrayAtMemory<double>(a2,N2,N3);
double** f2 = (double**)a2;
for (int i1 = 0; i1 < N2; i1++)
{
for (int i2 = 0; i2 < N3; i2++)
{
f2[i1][i2] = j++;
}
}
for (int i1 = 0; i1 < N2; i1++)
{
for (int i2 = 0; i2 < N3; i2++)
{
std::cout << (double)f2[i1][i2] << " ";
}
}
std::cout << std::endl << n2 << " bytes used " << std::endl;
std::cout << std::endl;
33 34 35 36 37 38
56 bytes used
|
|
|
|
|
Somehow it won't work even for integers:
int main(void)
{
int j = 0x21;
size_t n2 = CreateArrayAtMemory<short>(nullptr, 2, 3);
vector<char> a2(n2);
short** f2 = (short**)a2.data();
CreateArrayAtMemory(f2, 2, 3);
for (int i1 = 0; i1 < 2; i1++)
{
for (int i2 = 0; i2 < 3; i2++)
{
f2[i1][i2] = j++;
}
}
for (int i1 = 0; i1 < 2; i1++)
{
for (int i2 = 0; i2 < 3; i2++)
{
std::cout << (int)f2[i1][i2] << " ";
}
}
std::cout << std::endl << n2 << " bytes used " << std::endl;
std::cout << std::endl;
}
33 9250 9472 36 37 38
28 bytes used
Complied and linked in VS2015.
|
|
|
|
|
I'm seeing the same results using https://www.onlinegdb.com/online_c++_compiler[^]
I haven't checked the entire code, but maybe this is an alignment issue: online gdb uses 64 bit pointers, and judging by your output (size = 28), so did you.
GOTOs are a bit like wire coat hangers: they tend to breed in the darkness, such that where there once were few, eventually there are many, and the program's architecture collapses beneath them. (Fran Poretto)
|
|
|
|
|
All calls to CreateArrayAtMemory MUST have the correct element type attached as template parameter. (unless it's char) Otherwise the offsets for the internal pointers cannot be calculated correctly.
Try
CreateArrayAtMemory<short>(f2, 2, 3);
GOTOs are a bit like wire coat hangers: they tend to breed in the darkness, such that where there once were few, eventually there are many, and the program's architecture collapses beneath them. (Fran Poretto)
|
|
|
|
|
Great! Now it works for both short and double.
|
|
|
|
|
There is an error in the example
../main.cpp:47:44: error: ‘a2’ was not declared in this scope<br />
size_t n2 = CreateArrayAtMemory<short>(a2,2,3)
|
|
|
|
|
|
example 2x3 code is dependent on platform.
I did compile example2_3 as "g++ example2_3.cpp -o exam -std=c++11" in ubuntu 14.04 and ran it
---
output
---
multi_dim$ ./exam
33 9250 9472 36 37 38
28 bytes used
---
full source code
---
#include <iostream>
#include <vector>
using namespace std;
template <typename T = char> size_t CreateArrayAtMemory(void*, size_t bs)
{
return bs*sizeof(T);
}
template <typename T = char,typename ... Args>
size_t CreateArrayAtMemory(void* p, size_t bs, Args ... args)
{
size_t R = 0;
size_t PS = sizeof(void*);
char* P = (char*)p;
char* P0 = (char*)p;
size_t BytesForAllPointers = bs*PS;
R = BytesForAllPointers;
char* pos = P0 + BytesForAllPointers;
for (size_t i = 0; i < bs; i++)
{
char** pp = (char**)P;
if (p)
*pp = pos;
size_t RLD = CreateArrayAtMemory<T>(p ? pos : nullptr, args ...);
P += PS;
R += RLD;
pos += RLD;
}
return R;
}
int main(void)
{
int j = 0x21;
size_t n2 = CreateArrayAtMemory<short>(nullptr,2,3);
vector<char> a2(n2);
short** f2 = (short**)a2.data();
CreateArrayAtMemory(f2,2,3);
for (int i1 = 0; i1 < 2; i1++)
{
for (int i2 = 0; i2 < 3; i2++)
{
f2[i1][i2] = j++;
}
}
for (int i1 = 0; i1 < 2; i1++)
{
for (int i2 = 0; i2 < 3; i2++)
{
std::cout << (int)f2[i1][i2] << " ";
}
}
std::cout << std::endl << n2 << " bytes used " << std::endl;
std::cout << std::endl;
}
|
|
|
|
|
I can confirm this error. I have added this line into the first nested for loop:
std::cout << "address of f["<<i1<<"]["<<i2<<"] is "<<&f2[i1][i2]<<std::endl;
Then I got this output:
Quote: address of f[0][0] is 0x2494c30
address of f[0][1] is 0x2494c32
address of f[0][2] is 0x2494c34
address of f[1][0] is 0x2494c33
address of f[1][1] is 0x2494c35
address of f[1][2] is 0x2494c37
33 9250 9472 36 37 38
28 bytes used
With these addresses it's clear what happened: the values of f[0][1] and f[0][2] were overwritten by the later writes. It's not clear how the values were written to an odd address - was the address rounded up or down before writing? But anyway, the two rows of data overlap due to the incorrect address calculations!
GOTOs are a bit like wire coat hangers: they tend to breed in the darkness, such that where there once were few, eventually there are many, and the program's architecture collapses beneath them. (Fran Poretto)
|
|
|
|
|
I've found the error: the line
CreateArrayAtMemory(f2,2,3); should be
CreateArrayAtMemory<short>(f2,2,3);
Without the type info, the offsets are calculated incorrectly, and writing to elements in row 1 (or below) will overwrite data in the upper rows.
GOTOs are a bit like wire coat hangers: they tend to breed in the darkness, such that where there once were few, eventually there are many, and the program's architecture collapses beneath them. (Fran Poretto)
|
|
|
|
|
Hi, Michael! Your template is very interesting, but, on my mind, it demonstrates not obvious manner of memory management. Using of outer pointers to a vector's data is strange enough. And recursion loses looping in competition for efficiency. Is your way really better then a simple one?
template <typename T> T** CreateArray(size_t n, size_t m)
{
T** mem = new T*[n];
mem n[0] = new T[n * m];
for (size_t i = 1; i < n; ++i) mem[i] = mem[i + 1] + m * sizeof(T);
return mem;
}
|
|
|
|
|
Your function uses two allocations and will only create 2D arrays.
|
|
|
|
|
While I could think of cleaner (and maybe clearer) ways of doing this, the code here is not as strange is it might seem at a first glance.
Basically the idea is to (re)construct a data structure based on the way a multi-level pointer is dereferenced: on the lowest level you have an array of elements; one level up and you have an array of pointers to arrays of elements; another level up you have an array of pointers to arrays of pointers to arrays of elements. Thats what CreateArrayAtMemory constructs, and thanks to recursion it's actually not that hard to see in the code.
As for efficiency, templates are resolved at compile time: the entire recursion is unrolled in the actual code! Therefore there is no loss of efficiency compared to using a loop or any other kind of iterative approach.
That said, this template requires a lot of additional memory for pointers, and that is only needed for the sake of abusing a multilevel pointer indirection to allow the use of a nested index operator. It would be cleaner to simply write a class that stores the elements in a flat array and provides a variadic function at(int index1, Args ... args) to retrieve and access the desired element. But then you could just as well use boost::tensor
GOTOs are a bit like wire coat hangers: they tend to breed in the darkness, such that where there once were few, eventually there are many, and the program's architecture collapses beneath them. (Fran Poretto)
|
|
|
|
|