Whenever you feel the need to do a C-style cast (like you do with (int**)x) you should take that as a sign you're doing something wrong.
An array of arrays is not the same as a pointer to a pointer.
What happens is that you pass an invalid pointer, and the program will experience undefined behavior and crash.
What should happen is that the array x decay to a pointer to its first element, i.e. plain x is the same as &x[0]. That will be a pointer to an array, with the type int (*)[2] (in the case of x in your question). That is the type you need to use for the function argument:
void sum(int (*x)[2]) { ... }
And call like
sum(x);
You can also generalize to accept any kind of "2D" array, by using template arguments for the dimensions and passing the array by reference:
// For std::size_t
#include <cstdlib>
template<std::size_t A, std::size_t B>
void sum(int (&x)[A][B])
{
// Use A and B as the sizes for the arrays...
...
}
While the above template version is better, the "proper" solution in C++ should be using std::array, as in:
std::array<std::array<int, 2>, 3> = { ... };
and then pass it as a constant reference to the function.
If the sizes aren't known at compile-time, or if the arrays should be dynamic, then use std::vector instead.
A good book and and good teacher would teach these classes before going into pointers or C-style arrays.
(int**)xcan be very dangerous. The compiler won't tell you thatxis certainly not anint**. Your code never produces any pointer to pointer to int.