John Gowers
John Gowers

Reputation: 2736

Can you pass multi-dimensional arrays into a C function as pointers and then cast them back into arrays inside the function?

Problem: suppose you are trying to write a function in C that will fill a 2D array with values from a file. The file contains the values arranged in rows (records), where each row contains a number of fields. The function should take in a pointer to the 2D array and the address of the file and fill in the array. Importantly, the function should work independently of how many fields there are per record. For example, in one program you might call the function to read values from a file where there are four fields per record:

int array_of_values[MAX_NUMBER_OF_RECORDS][4];
fill_in_array(array_of_values, "spacetime.csv");

In another program, you might want to fill in values when there are eleven fields per record:

int array_of_values[MAX_NUMBER_OF_RECORDS][11];
fill_in_array(array_of_values, "M-theory.csv");

Unfortunately, if you try to do this you fall foul of the way C handles multidimensional arrays. Multidimensional arrays are not implemented in C as arrays of pointers to arrays, but instead as one long one-dimensional array. This means that the function needs to know the width of the array in order to read data from it.

So the following function definition will give you an error:

void fill_in_array(int array_of_values[MAX_NUMBER_OF_RECORDS][], char *path)

[Note that the following would be fine:

void fill_in_array(int array_of_values[][MAX_NUMBER_OF_RECORDS], char *path)

since the compiler does not need to know the index for the first dimension, but suppose that this is not allowed (e.g., if the function needs to mess around with individual records like array_of_values[1]).]

This is the point that I have reached in my program. There are two solutions that present themselves:

  1. Force the function to work with a fixed number of fields. I'd obviously rather not do this, but I could, for example, declare a constant MAX_NUMBER_OF_FIELDS and leave unused fields empty.
  2. Make the fill_in_array function take in a pointer rather than an array and dynamically allocate an Iliffe vector containing the fields. This is an attractive idea (since it would stop us having to declare a maximum number of records/fields, but it would also mean we would have to create (and remember to use!) a function to free the array of fields.

I've got one other idea. That is to modify the declaration of the function to the following:

void fill_in_array(int **array_of_values, int number_of_fields, char *path)

(Here, number_of_fields refers to the number of fields per record, so we might call it as fill_in_array(array_of_values, 4, "spacetime.csv");.

Notice that the parameter array_of_values is no longer an explicit array, but is a pointer. Normally, if you assign a double-pointer to point to a 2D array, the result is meaningless. My idea is that it might be possible to use the number_of_fields parameter so that the function knows how to deal with expressions like array_of_values[i][j].

In principle this should be fairly easy: indeed, if a is a 2D array, then a[i][j] is defined to be

*(a + (i * n) + j)

where n is the length of the array, so we could replace every occurrence of array_of_values[i][j] with *(array_of_values + (i * number_of_fields) + j), and every occurrence of array_of_values[i] with array_of_values + (i * number_of_fields). This code would be very hard to read, however. Is there some way of telling the compiler that the width of the array is number_of_fields so that I can use index notation to access elements of the array?

Upvotes: 1

Views: 1129

Answers (4)

han
han

Reputation: 755

Unless you are limited to C89 (i.e. the MSVC compiler), you can pass multi-dimensional arrays around like this:

#include <stdio.h>

void fill_in_array(size_t m, size_t n, int array_of_values[m][n])
{
  for (size_t i = 0; i < m; ++i) {
    for (size_t j = 0; j < n; ++j) {
      array_of_values[i][j] = ((i == j) ? 1 : 0);
    }
  }
}

void print_array(size_t m, size_t n, int array_of_values[m][n])
{
  for (size_t i = 0; i < m; ++i) {
    for (size_t j = 0; j < n; ++j) {
      printf(" %d", array_of_values[i][j]);
    }
    printf("\n");
  }
}

int main()
{
  {
    int array_of_values[2][4];
    fill_in_array(2, 4, array_of_values);
    print_array(2, 4, array_of_values);
  }
  {
    size_t h = 6, w = 5;
    int array_of_values[h][w];
    fill_in_array(h, w, array_of_values);
    print_array(h, w, array_of_values);
  }
}

Upvotes: 0

Valeri Atamaniouk
Valeri Atamaniouk

Reputation: 5163

What you are looking exists in C++, but not in C, I believe. In C++ you can define template functions for working with arrays of size, known at a compile time, and compiler takes care of the rest. In C, there are two approaches:

  • Define the size explicitly
    This is the case of functions like memcpy, where you specify number of elements

    void process_array(int *data[], size_t max_x, size_t max_y)
    ....
    
  • Define the size using invalid number
    This is the case of functions like strlen where data is terminated by certain value ('\0' here)
    So if you want to have a function with matrix, but variable number of elements, you must define a way how to indicate that in the data.

    #define ARRAY_TERM -1
    
    void process_array(int *data[])
    {
        size_t i, j;
        for (i = 0; data[i]; i++)
        {
            for (j = 0; data[i][j] != ARRAY_TERM; j++)
            {
               ...
            }
        }
    }
    ...
    

Hope you've got the idea. Not very convenient to use.

There is another approach: define your own type. Yes, it is a viable option in many cases:

typedef struct array *array_t;
struct array
{
    size_t max_x, max_y;
    int *data;
};

Basic set of functions to work with it:

int array_init(array_t *a; size_t max_x, size_t max_y)
{
    array_t res;
    res = malloc(sizeof(*res));
    res->max_x = max_x;
    res->max_y = max_y;
    res->data = calloc(max_x * max_y, sizeof(int));
    *a = res;
    return 0;
}

void array_destroy(array_t *a)
{
    free((*a)->data);
    free(*a);
}

And then you can define your additional functions for operation.

Upvotes: 0

robbie_c
robbie_c

Reputation: 2458

There are a few solutions.

Use a struct:

typedef struct {
  // whatever appears in a record
} record_t

void fill_in_array(record_t records[MAX_NUMBER_OF_RECORDS], const char* path);

Note that this only makes sense if the size of a record is known at compile time, which given your example, it may not be.

Use a stride:

void fill_in_array(int *array_of_values, int stride, const char *path)
{
  #define IDX(x, y) (x + (y * stride))

  // get the val at i,j
  int val = array_of_values[IDX(i,j)];

  #undef IDX
}

You've suggested this approach in your function with number_of_fields which is a stride, however stride is a term which other developers looking at your code are more likely to recognize.

One minor unrelated point, if you don't change the contents of path, you should make it const :)

Upvotes: 1

unwind
unwind

Reputation: 399959

No, there's no such way.

Once you need to have generic address computation, you need to implement it yourself.

Congratualations for arriving at the solution of adding an explicit parameter that describes the number of fields per record, that's certainly how it should be done.

You can use a macro inside the function to make the address calculation more easily managable, perhaps.

Upvotes: 3

Related Questions