Based on the description, the most likely interpretation is that you are passing a pointer to an int ** object, and foo allocates memory for that object, so you'd have
void foo( int ***out )
{
*out = ...; // omitting actual allocation here since it's homework,
// but I can guarantee it won't be a single statement
}
int main( void )
{
int **pairs;
...
foo( &pairs );
...
}
Note than an object of T ** is not a 2-dimensional array; it can be used to implement something that can be indexed like a 2D array, but the "rows" don't have to be contiguous or even the same length. It basically looks something like this:
pairs pairs[i] pairs[i][0], pairs[i][1]
int ** int * int
+---+ +---+ +---+---+
| +-+-----> | +-+---------> | | |
+---+ +---+ +---+---+
| +-+------+
+---+ | +---+---+
| +-+---+ +--> | | |
+---+ | +---+---+
... |
| +---+---+
+-----> | | |
+---+---+
IOW, you have an object pairs that points to a sequence of int *, each of which (pairs[i]) points to a sequence of int. Because of how the [] subscript operator works, you can index into this structure using 2D array notation (pairs[i][j]), but otherwise it doesn't look or act like a 2D array.
So I'm assuming the job of foo is to allocate the memory for a structure like that, where each pairs[i] points to a sequence of 2 int. Since it needs to write a new value to pairs, we need to pass a pointer to pairs. Since pairs has type int **, that means the expression &pairs has type int ***.
I can tell you this will be a multi-step process - you will need to make multiple calls to malloc or calloc. The diagram above should give you some hints on how to do that.
fooneeds to generate (on the heap for sure) an array of pointers to int type and return it by derefrence theoutparameter/function argumentint**