1

Let's say I have a pointer

char *p;

in my main function I have a multidimensional array a:

char a[10][15];

I want p to point to a and to be able to use p[x][y] to refer to values in the array. I thought p = a; would normally take care of this but I got a cast error.

p = &a[0][0];

makes the new pointer but sees it as a one-dimensional array of chars. Any way to make the pointer see the array as two-dimensional?

Note that when I define p I do not know the dimensions of a yet.

Benjamin753
  • 115
  • 1
  • 1
  • 5
  • `char (*p)[10][15]` or `char (*p)[15]` would probably both work, off the top of my head – Mooing Duck Dec 17 '18 at 00:45
  • Gives me the same error, in the defention of the pointer I don't know the dimensions of the array yet; the numbers I gave here are for the sake of the example. – Benjamin753 Dec 17 '18 at 00:52
  • 2
    Possible duplicate of [Accessing multi-dimensional arrays in C using pointer notation](https://stackoverflow.com/questions/43907616/accessing-multi-dimensional-arrays-in-c-using-pointer-notation) - a two-dimensional array, as the answerer there says, decays into a pointer to an array, or a pointer to a pointer. – hpm Dec 17 '18 at 00:57
  • "when I define p I do not know the dimensions of a yet." is curious. Why do you need to define `p` **before** `a`? Post the code that demonstrates your need. – chux - Reinstate Monica Dec 17 '18 at 02:36
  • If you do not know the dimensions, you cannot prepare a method to navigate the array. What you can do is allocate space for it, if you know the total number of elements. You can prepare and operate on a pointer to that space. But you cannot access array elements if you cannot figure out where they are, which requires knowing the dimensions (except the first, which is only needed for knowing how many elements there are in that dimension). – Eric Postpischil Dec 17 '18 at 03:19

3 Answers3

0

The declaration

char * p;

makes p a pointer to an object of type char, or to the first element of an array of objects of type char.

You want to declare a pointer to an array of 15 chars, or to the first element of an array of arrays of 15 chars:

char (* p) [15];

Now you can say

p = a;

And access the elements of a through p.

AlexP
  • 4,370
  • 15
  • 15
0

If you don't know the dimensions of a. You need a jagged array. That is another array that stores the address of every sub-array.

char a1[10];
char a2[20];
char a3[30];
char* jagged[3] = {a1, a2, a3};
char** p = jagged;

But the overhead of another array is probably not what you want.

Without the jagged array you'll have to know at least the second dimension of the array to locate the element you want. For your array a[10][15], to locate the element a[x][y], the code get the offset of that element by calculating x * 15 + y. Note the 15 in that expression, it's the second dimension of the 2d array.

W.H
  • 1,838
  • 10
  • 24
  • The first part with the code is right. THe following paragraph is confusing. This probably _is_ what he wants, because otherwise he'd have to calculate offsets. With the code as you have it, he doesn't have to calculate. – Mooing Duck Dec 17 '18 at 01:23
  • @MooingDuck If you're using `C` already. You probably don't want the overhead of another array just to use `2d` array. Especially when your array are actually not jagged. – W.H Dec 17 '18 at 01:32
  • Preparing a so-called “jagged” array (which is bad for performance and other reasons) requires preparing numerous pointers. Instead of going to all that trouble, it is easier to just pass the dimensions. – Eric Postpischil Dec 17 '18 at 03:18
0

C offers no mechanism to pass the dimensions of variable-sized multidimensional arrays around. You have to implement it yourself, i.e. you have to come up with your own type that means int array[10][20]. For example, in the (untested) code below, you can pass around an int_array *, and use int_at to get the value. You can also inspect the dimensions within int_array, etc. The code using the int_array * doesn't even need to know how many dimensions it has got.

vint_ptr_at is doing, pretty much, what the compiler-generated code would do to compute the address, although that code can be optimized better than the generic version. Normally, if you were to iterate, you could use the int* you got from int_ptr_at, and keep incrementing it.

#include <assert.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>

typedef struct {
  int *data;
  int num_dims;
  bool owns_data;
  struct {
    size_t count, product;
  } dims[1];
} int_array;

void free_int_array(int_array *arr) {
  if (arr && arr->owns_data) free(arr->data);
#ifdef DEBUG
  arr->data = NULL;
#endif
  free(arr);
}

static bool int_array_setup_dims(int_array *arr, va_list args) {
  size_t n = 1;
  int const num_dims = arr->num_dims;
  for (int i = 0; i < num_dims; i++) {
    int dim = va_arg(args, int);
    arr->dims[i].count = dim;
    arr->dims[num_dims - 1 - i].product = n;
    if (n > SIZE_MAX / dim) return false;
    n *= dim;
  }
  return true;
}

int_array *vnew_int_array_on(void *data, int num_dims, va_list args) {
  int_array *arr = malloc(sizeof(int_array) + (num_dims-1)*sizeof(int));
  if (!arr) goto fail;
  arr->num_dims = num_dims;
  if (!int_array_setup_dims(arr, args)) goto fail;

  arr->data = data ? data : malloc(n * sizeof(int));
  arr->num_dims = num_dims;
  arr->owns_data = !data;
  if (!arr->data) goto fail;
  return arr;
fail:
  free(arr);
  return NULL;
}

int_array *new_int_array(int num_dims, ...) {
  va_list args;
  va_start(args, num_dims);
  int_array *arr = vnew_int_array_on(NULL, num_dims, args);
  va_end(args);
  return arr;
}

int_array *new_int_array_on(void *data, int num_dims, ...) {
  va_list args;
  va_start(args, num_dims);
  int_array *arr = vnew_int_array_in(data, num_dims, args);
  va_end(args);
  return arr;
}

int *vint_ptr_at(const int_array *arr, va_list args) {
  size_t index = 0, n = 1;
  int const num_dims = arr->num_dims;
  for (int i = 0; i < num_dims; i++) {
    int j = va_arg(args, int);
    index += j * arr->dims[i].product;
  }
  return arr->data + index;
}

int *int_ptr_at(const int_array *arr, ...) {
  va_list args;
  va_start(args, arr);
  int *ptr = vint_ptr_at(arr, args);
  va_end(args);
  return ptr;
}  

int int_at(const int_array *arr, ...) {
  va_list args;
  va_start(args, arr);
  int *ptr = vint_ptr_at((int_array*)arr, args);
  va_end(args);
  return *ptr;
}

size_t *indices_for(const int_array *arr) {
  if (!arr) return NULL;
  size_t const size = sizeof(size_t) * arr->num_dims;
  size_t *indices = malloc(size);
  if (indices) memset(indices, 0, size);
  return indices;
}

bool next_index(const int_array *arr, size_t *indices) {
  for (int i = arr->num_dims - 1; i >= 0; i--) {
    if (indices[i] < arr->dims[i].count) {
      indices[i] ++;
      return true;
    }
    indices[i] = 0;
  }
  return false;
}

int main() {
  int data[10][15];
  int_array *arr = new_int_array_on(data, 2, 10, 15);
  assert(arr->dims[0].dim == 10);
  assert(arr->dims[1].dim == 15);
  data[2][3] = 40;
  data[4][5] = 50;
  data[5][6] = 100;
  assert(int_at(arr, 2, 3) == 40);
  assert(int_at(arr, 4, 5) == 50);
  assert(int_at(arr, 5, 6) == 100);
  free_int_array(arr);
}
#include <stdio.h>

void iteration_example(const int_array *arr) {
  size_t *indices = indices_for(arr);
  if (indices) {
    int *data = arr->data;
    do {
      printf("arr");
      for (int i = 0; i < arr->num_dims; ++i)
        printf("[%zd]", indices[i]);
      printf(" = %d\n", *data++);
    } while (next_index(arr, indices));
  }
  free(indices);
}
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313