0

I want to pass a 2D array of characters to another function using a parameter of type "void*" and then have that function have access to the elements inside that array.

This code spits out a "Segmentation Fault:11" at the point where voidPointer tries to std::cout elements inside array2.

#include <iostream>

void voidPointer(void* userdata) {
    char** array2 = static_cast<char**>(userdata);
    for (int i=0; i<3; ++i) {
        for (int j=0; j<3; ++j) {
            std::cout << array2[i][j] << ' ';
        }
        std::cout << std::endl;
    }
    return;
}

int main() {
    char array[3][3];
    for (int i=0; i<3; ++i) {
        for (int j=0; j<3; ++j) {
            array[i][j] = 'a';
        }
    }
    voidPointer(array);
    return 0;
}

I've tried lots of different things and can't figure this out at all. I was able to get the above code to work when it's dealing with a 1D array. For example, this code

#include <iostream>

void voidPointer(void* userdata) {
    char* array2 = static_cast<char*>(userdata);
    for (int i=0; i<3; ++i) {
        std::cout << array2[i] << ' ';
    }
    std::cout << std::endl;
    return;
}

int main() {
    char array[3];
    for (int i=0; i<3; ++i) {
        array[i] = 'a';
    }
    voidPointer(array);
    return 0;
}

works as expected with the output "a a a".

Backstory: I'm working on my first project in which I'm trying to use a Mouse Callback function that accepts a parameter in the form "void* userdata". I am attempting to pass a 2D character array to this Callback function so I can then pass it on to other functions that will require access to the elements inside this array. I don't really know if this is good coding practice or not so feel free to let me know some alternatives.

To anyone that responds, thank you!

  • 1
    A 2D array is not an array of 1D array. – user202729 Aug 15 '21 at 01:12
  • [casting void** to 2D array of int - C - Stack Overflow](https://stackoverflow.com/questions/18440205/casting-void-to-2d-array-of-int-c) – user202729 Aug 15 '21 at 01:13
  • 1
    Does this answer your question? [Casting void\* to two-dimentional array](https://stackoverflow.com/questions/8288450/casting-void-to-two-dimentional-array) – user202729 Aug 15 '21 at 01:14
  • Wait, that one is for C... but they're the same regarding this point. – user202729 Aug 15 '21 at 01:14
  • "_I want to pass a 2D array of characters to another function using a parameter of type "void*"_" - You say that - but it's extremely rarely a good idea. Why do you _think_ that's what you need? – Ted Lyngmo Aug 15 '21 at 01:17
  • 1
    @Ted Lyngmo - I want to use a Mouse Callback function that accepts a "void* userdata" parameter. I just assumed it was meant so I could change an object depending on Mouse Events. Maybe there's a better more common way to do that? – Jake Parker Aug 15 '21 at 01:26
  • Without seeing the proper declaration of a "Mouse Callback" I have no idea. `void*` is _rarely_ what one wants though. – Ted Lyngmo Aug 15 '21 at 01:31
  • Your problem in the 2D case is undefined behaviour, resulting from the fact that the argument passed by `main()` is a 2D array, and the function casts it to a `char **` The notional equivalence of pointers and arrays (in which the name of an array is converted implicitly to a pointer to the array's first element) only works in the first dimension (as in your second example) - and breaks down in the second dimension. – Peter Aug 15 '21 at 01:46
  • Cast it to pointer-to-array-of-3-char, not pointer-to-pointer-to-char. The array to pointer decay only applies to the outermost array. – Pete Becker Aug 15 '21 at 02:28
  • Instead of trying to solve the problem I agree with @TedLyngmo. We have this wonderful typesafe language called c++. With features like this : https://stackoverflow.com/questions/17759757/multidimensional-stdarray/17759790 So why go all this extra length to throw away information by casting to void* and then rebuilding the information up back later. And this doesn't even start to adres life cycle issues. What if the object void* pointed to was allocated on the stack and that kind of stuff. The only reason I would see for this is af an 'old' style API still needs void* – Pepijn Kramer Aug 15 '21 at 04:21

3 Answers3

1

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.

Peter
  • 35,646
  • 4
  • 32
  • 74
0

It looks like the question is how to make this scenario work instead of why the attempt failed. For those interested in why the attempt failed, see casting void** to 2D array of int.

Note: It would be better to avoid using void*, but sometimes one has to interface with someone else's C-style API where void* is the traditional way to pass data to a callback.


There is a reasonably common trick for simulating a multi-dimensional array with a one-dimensional array. Using the trick reduces your scenario to the case that works. The trick involves how you access the elements. Instead of declaring char array[DIM1][DIM2] and accessing elements via array[i][j], declare the array to be char array[DIM1 * DIM2] and access elements via array[i*DIM2 + j].

However, remembering this formula is intellectual overhead, consuming brainpower that would be better used elsewhere. Not to mention that I get the dimensions reversed half the time. You could relieve the coder of this overhead by wrapping this array in a class, and hiding the formula in a method for accessing elements. This might look like the following.

class Array2D {
    static constexpr unsigned DIM1 = 3;
    static constexpr unsigned DIM2 = 3;

    char data[DIM1 * DIM2];
  public:
    char& at(unsigned i, unsigned j)  { return data[i*DIM2 + j]; }
    // And perhaps other methods
};

You could then create an object of this class, then pass the address of that object to your C-style mouse handler. I.e. if your variable is Array2D array then call voidPointer(&array). This version can be adapted to the situation where the dimensions are not known at compile time.

Then again, if you are going to create a class anyway, why not try to preserve the syntax you are used to (using operator[] twice)? This does assume that the dimensions are compile-time constants.

class Array2D {
    static constexpr unsigned DIM1 = 3;
    static constexpr unsigned DIM2 = 3;

    char data [DIM1][DIM2];
  public:
    auto& operator[] (unsigned i)  { return data[i]; }
    // And perhaps other methods
};

Of course, this approach locks you into a single size. It would probably be a good idea to make this a template. It would be even better if someone else did all that work for me.

#include <array>
static constexpr unsigned DIM1 = 3;
static constexpr unsigned DIM2 = 3;

using Array2D = std::array< std::array<char,DIM2>, DIM1 >;
//    Note the order:                       ^^^^   ^^^^

Remember: if your variable is Array2D array then call voidPointer(&array).

JaMiT
  • 14,422
  • 4
  • 15
  • 31
-1

Why not to use a structure instead of array. You can define everything in a structure, than pass its address to your function.

Sean
  • 79
  • 5