5

So my code is this :

int a, b;
printf("Give dimensions\n");
scanf("%d %d", &a, &b);
double array1[a][b];
printf("Give values\n");
int i, j;
for(i=0; i<a; i++)
{
    for(j=0; j<b; j++)
    {
        scanf("%lf", &array1[i][j]);
    }
}

My problem is, i am told this is a wrong way to allocate memory and that i should use malloc. I was supposed to create a dynamic array using user's dimensions.

Edit: rest of the program:

double sum=0;
for(i=0; i<a; i++)
{
    for(j=0; j<b; j++)
    {
        sum = sum + array1[i][j];
    }
    printf("Line %d: sum = %lf\n", i+1, sum);
    sum=0;
}
vailanter
  • 51
  • 3
  • Note that you should validate that the read (`scanf()`) succeeded and that the values read are plausible (strictly positive and not too big) before going ahead and allocating the VLA. You should also check the other input operation too — taking appropriate action if the input fails. – Jonathan Leffler Jan 19 '17 at 15:42

4 Answers4

6

Depends on how you define "correct". This is legal C since C99.

The problem however, is that if a and b are too large, the array will overflow the call stack. If this is a likely scenario, you should prefer malloc. The call stack is usually allocated to be fairly small compared to the heap. So that's probably the source of the advice.

Note that you can still enjoy the array subscript notation with dynamically allocated arrays:

double (*array)[b] = malloc(sizeof(double[a][b]));

The array is in one continuous block, and the VLA pointer will result in a[i][j] referencing the correct element.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
5

No, this is perfectly correct and valid way, as long as you are using a compiler version / environment which supports Variable-length array.

This was a mandatory feature as on C99 but again made optional on C11.

The major differences between using VLA and a "pointer-and-memory-allocator functions-combination" are

  • VLAs are block scoped. a VLA does not live outside the scope, so it cannot be returned from a function and be used in the caller, unlike the pointer-and-malloc approach.
  • Usually VLAs are allocated in stack for all major implemenataions, so that is size limited.
Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261
  • "Perfectly correct and valid way" as long as you care a T-shirt about error handling, avoiding stack overflows, bounds checking and so on. – Pablo Ariel Aug 28 '23 at 06:49
5

The C compiler may handle it as valid code, although C11 standard made VLA support optional.

The main problem is that actually you have no way to check if the allocation was successful or not, especially when the size is unknown. This is also the main advantage of malloc/calloc:

double (*array1)[b] = malloc(a * sizeof(*array1));
if (!array1) {
     // handle allocation failure
}
Grzegorz Szpetkowski
  • 36,988
  • 6
  • 90
  • 137
2
scanf("%d %d", &a, &b);
double array1[a][b];

This is valid as of the C99 standard, although you'll want to do some sanity checking on your inputs first (i.e., make sure both inputs were actually read, make sure neither is negative, make sure they're in a reasonable range, etc.).

array1 is a variable-length array (VLA), which were first introduced in C99. They've been made optional as of C2011, but I think almost all hosted implementations still support them. To make sure, check for the __STDC_NO_VLA__ macro - if it's defined, then the implementation does not support VLAs.

size_t a, b;
if ( scanf( "%zu %zu", &a, &b ) < 2 )
  // bail out with an error message

#if defined( __STDC_VERSION__ ) && __STDC_VERSION__ >= 199901L && !defined( __STDC_NO_VLA__ ) 

  /**
   * This implementation supports VLAs.  We need to do an additional
   * sanity check on a and b to make sure we don't allocate an
   * object too large for the stack (even though VLAs
   * don't have to be allocated on the stack, that's how many 
   * implementations choose to do it).
   */
  if ( a > SOME_MAX_LIMIT || b > SOME_MAX_LIMIT )
    // bail out with an error
  double array1[a][b];

#else  

  /**
   * This implementation does not support VLAs, so we'll have to use
   * dynamic memory allocation.  Note that memory allocated this way
   * is *not* contiguous - rows are not adjacent, so the object immediately
   * following array1[0][b-1] is not array1[1][0].  If that matters, 
   * then this won't work.  
   */
  double **array1 = malloc( sizeof *array1 * a );
  if ( array1 )
  {
    size_t i;
    for ( i = 0; i < a; i++ )
    {
      array1[i] = malloc( sizeof *array1[i] * b );
      if ( !array1[i] )
        break;
    }
    if ( i < a ) // memory allocation failure, clean up any previously allocated memory
    {
      while ( i-- )
        free( array1[i] );
      free( array1 );
      // bail out with an error here
    }
  }
  else
    // memory allocation failed, bail with an error

#endif

At this point, your code can reference array1[i][j], no matter which way we allocated it. However, you will need to add this at the end of your function:

#if !defined( __STDC_VERSION__ ) || __STDC_VERSION__ < 199901L || defined( __STDC_NO_VLA__ )

  for ( size_t i = 0; i < a; i++ )
    free( array1[i] );

  free( array1 );

#endif

so that we properly clean up after ourselves if we had to use malloc.

VLAs behave pretty much like every other auto (local) array - the memory for them will be released when you exit the function, so you can't return a pointer to them and have the pointer be valid after the function exits.

Since their size isn't determined until runtime, you can't use them at file scope (outside of any function), you can't declare them as static, and they cannot be members of a struct or union type.

You can combine VLAs with dynamic memory allocation:

size_t a, b;
if ( scanf( "%zu %zu", &a, &b ) < 2 )
  // bail out with an error

double (*array1)[b] = malloc( sizeof *array1 * a );

You get a dynamically-allocated 2D array with user-specified dimensions that is contiguous, and you're only limited by the heap size, not the stack size, so you can allocate large objects this way.

John Bode
  • 119,563
  • 19
  • 122
  • 198