As you might have noticed, the C syntax is quite dysfunctional here, as it quickly turns hard to read. A language design decision was made back in the days to never allow arrays to be copied with = or returned from functions. There's no sensible rationale for that, it's just how the language was designed.
Had C allowed arrays to be returned by value, we would perhaps have written the code for returning an array by value like:
int create_arr_5 (void)[5]
But this is invalid C. However, consider that when we have an array int x[5], we get a pointer to that same type by wrapping the identifier x in parenthesis and adding a *. That is: int (*x)[5]. The syntax for returning a pointer to array is similar, wrap the function as well as it's parameters in parenthesis, then add a *:
int (*create_arr_5(void))[5]
However, that's still quite hard to read and it won't get easier for non-trivial functions.
One work-around is to pass the pointer to array as parameter instead... but then we get to deal with array "decay". Because of "decay" (or array adjustment as the C standard calls it formally), then
void create_arr_5 (int arr[5])
is 100% equivalent to:
void create_arr_5 (int* arr)
This is readable, but won't work if we need to malloc inside the function and return that to the caller, because the pointer is just a local copy. We can't do int** either, in case the caller expects an int (*)[5]. Gah!
So what if we let the function take a pointer to array as parameter and then have the caller call it like create_arr_5 (&array)? Sure, that works:
void create_arr_5 (int (*arr)[5])
But we still have the problem that this is a local pointer. We could pass a pointer to an array pointer...
void create_arr_5 (int (**arr)[5])
But that's also quite unreadable! We haven't really improved anything.
Reasonable compromises/pick your poison:
One reasonable compromise is to just to ignore this whole mess and have the function return a void*
void* create_arr_5 (void)
That's readable, but we lose type safety. Depending on how picky we are with type safety, this might either be fine or unacceptable.
Another compromise is to typedef the expected array type, which is somewhat bad practice but at least makes the function readable:
typedef int arr_5 [5];
arr_5* create_arr_5 (void)
{
arr_5* ptr = malloc(sizeof *ptr);
return ptr;
}
Replace functions with macros. Generally function-like macros are bad practice, but sometimes they could be justified to hide away flaws of the language. You can make them type safe nowadays even.
The advantage of a macro is that you can make one which works no matter size and still remains type safe:
#define create_arr(arr) _Generic( (arr), int(*)[]: malloc(sizeof(*arr)) )
...
int(*arr5)[5] = create_arr(arr5);
int(*arr3)[3] = create_arr(arr3);
Best solution/call for sanity: you are actually just doing a plain malloc call. Skip all of this obfuscation to begin with!
int(*arr5)[5] = malloc(sizeof *arr5);
c++if you ask about C. These are two quite distinct programming languages. I removed the tag.int (*create_arr_5(void))[5].int(* g())[5], your particular case would be something likeint (*create_arr_5(void))[5], as stated in user17732522's comment.