Firstly, I'll explain why your second example succeeds but your first example fails. Then I'll suggest some options for consideration to make your code work.
In short - your first example has undefined behaviour because the notional equivalence of pointers and arrays only works in one dimension.
The second example relies on the facts that;
- The name of a one-dimensional array can be implicitly converted to a pointer to that array's first element. So, in
main()
of your second example, voidPointer(array)
is equivalent to voidPointer(&array[0])
. &array[0]
has type char *
.
- A pointer can survive a round trip via a
void *
conversion - where "round trip" means retrieving the pointer of the original type. i.e. a char *
can be converted to a void *
AND that void *
can be converted back to a char *
. So the explicit conversion char* array2 = static_cast<char*>(userdata)
done in voidPointer()
successfully retrieves the pointer - so array2
in voidPointer()
is equal to &array[0]
passed by main()
;
- Since the pointer passed by
main()
is the address of the first element of the array passed, voidPointer()
can safely treat that pointer AS IF it is an array (as long as code doesn't try to access elements out of range of the original array).
The logic above is only applicable for pointers and one-dimensional arrays, so breaks down in the first example;
- The name of a one-dimensional array can be implicitly converted to a pointer to that array's first element. So, in
main()
of your second example, voidPointer(array)
is equivalent to voidPointer(&array[0])
. However, the difference is that - the expression &array[0]
has type char (*)[3]
(a pointer to an array of three char
) and that is NOT equivalent to a char **
.
- in
voidPointer()
your code converts the received pointer to a char **
via char** array2 = static_cast<char**>(userdata)
. This means that the pointer array2
has a different type that the pointer (&array[0]
) passed by main()
;
- Since
array2
has a different type than the pointer passed by main()
the code in voidPointer()
which dereferences array2
(treats it as if it is an array of arrays) has undefined behaviour.
Generally speaking, there are two ways you can make the code work. The first is to do the right type of conversion.
void voidPointer(void* userdata)
{
char (*array2)[3] = static_cast<(char (*)[3]>(userdata);
// rest of your function can be used as is
}
As in your code, the array dimensions (which are both 3
in your example) must be known and fixed at compile time. There is no way that userPointer()
can obtain any array dimensions from userdata
, because a void *
does not carry any of that sort of information from the caller.
A second option is to wrap the array in a data structure, for example
#include <iostream>
struct Carrier {char data[3][3];};
void voidPointer(void* userdata)
{
Carrier *package2 = static_cast<Carrier *>(userdata);
for (int i=0; i<3; ++i)
{
for (int j=0; j<3; ++j)
{
std::cout << package2->data[i][j] << ' ';
}
std::cout << std::endl;
}
}
int main()
{
Carrier package;
for (int i=0; i<3; ++i)
{
for (int j=0; j<3; ++j)
{
package.data[i][j] = 'a';
}
}
voidPointer(&package);
return 0;
}
This works because a Carrier *
can survive a round trip via a void pointer (i.e. the value of package2
in voidPointer()
has the the same type AND the same value as &package
in main()
) .
A second option is to use the std::array
class. Although this is syntactically different, it is actually a modified version of the first option (since std::array
is technically a templated data structure that contains an array of fixed dimension).
#include <iostream>
#include <array>
void voidPointer(void* userdata)
{
std::array<std::array<char, 3>, 3> *package2 = static_cast<std::array<std::array<char, 3>, 3> *>(userdata);
for (int i=0; i<3; ++i)
{
for (int j=0; j<3; ++j)
{
std::cout << (*package2)[i][j] << ' ';
}
std::cout << std::endl;
}
}
int main()
{
std::array<std::array<char, 3>, 3> package;
for (int i=0; i<3; ++i)
{
for (int j=0; j<3; ++j)
{
package[i][j] = 'a';
}
}
voidPointer(&package);
return 0;
}
Since your examples both had array dimensions fixed at compile time (3
in each dimension), my examples do the same. I'll leave extending the above to have dimensions fixed at run time (e.g. as user inputs) as a learning example.