Allocating a contiguous block of memory for an array I try to work with a contiguous block of memory, to create an array (2D) whose SIZE is not known at compile time (before c99) so no variable-length arrays are involved here.

I came up with the following:

#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;

int main(void){
    unsigned int row, col,i, j, k;
    int l = 0;
    int *arr;

    printf("Give the ROW: ");
    if ( scanf("%u",&amp;row) != 1){
        printf("Error, scanf ROW\n");
        exit(1);
    }

    printf("Give the COL: ");
    if ( scanf("%u",&amp;col) != 1){
        printf("Error, scanf COL\n");
        exit(2);
    }

    arr = malloc(sizeof *arr * row * col);
    if(arr == NULL){
        printf("Error, malloc\n");
        exit(3);
    }

    for ( i = 0; i &lt; row ; i++){
        for ( j = 0 ; j &lt; col ; j++){
            arr[i * col + j] = l;
            l++;
        }
    }

    for (k = 0 ; k &lt; (row * col) ; k++){
        printf("%d ",arr[k]);
    }

    free(arr);
}


Which gives me the following:

Give the ROW: 6
Give the COL: 3
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17


Is this the right approach?
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);