Allocating a contiguous block of memory for an array
Of course this in not a true 2d array, just a 1d array indexed using arr[i * col + j].

This is an OK approach.  When able, I prefer an array of pointers to an array of ints, as it is usually more flexible to the tasks I tackle.  YMMV.


Modest extreme case: sizeof *arr * row * col is multiplied in the right order.  When row or col is int/unsigned, good to use sizeof *arr first as that is type size_t and the subsequent multiplication will then happen at least using size_t math.   The concern being that with unsigned row, col, row*col  calculated first could overflow using unsigned math, but not size_t math. 

Pedantically, even sizeof *arr * row * col can overflow.  Should code need to detect this, the reasonable approach is to use the widest unsigned types like unsigned long long or uintmax_t.  (Other more pedantic methods exist.) 

assert(1LLU * sizeof *arr * row * col &lt; SIZE_MAX);

Suggest using size_t for row, col, i, j instead of any other type.  size_t is the Goldilocks type for array indexes - not too narrow, not too wide. 
Error messages are better sent to stderr.  2 advantages: will not get lost in code's typical stdout output and the stderr stream is flushed ensuring output is not mixed after later input.
Idea: Using a arr = 0; after the free(arr); has merit when the free'd variable is still in scope for a long time.  Although not needed, a decent compiler will optimized it out, but errant subsequent use is easier to detect when arr == NULL than arr = some_freed_value.
Minor: printf("%d ",arr[k]); in a loop is sometimes a problem when trying to print more than some environmental limit number (e.g. 4095) of characters on the same line.  Liberal use of fflush(stdout); or including a '\n' in the text usually solves this.
A key comment missing is: is this array row or column major?  With some analysis, that is discernible, but from a header only interface, knowing which dimension moves in the smallest steps is important in how higher level code will want to use this array for cache hit efficiency.
Coding style:  No need to introduce a variable until ready for assignment.  Consider the 2nd style.

// int *arr;
// ... many lines
// arr = malloc(sizeof *arr * row * col);

... many lines
int *arr = malloc(sizeof *arr * row * col);