2

I want to initialize a 2D character array "table" as one of two predefined 2D character arrays called "A" and "B" by using a straightforward void function "assignTable". However, while the array get the right values inside "assignTable" the assigned values seemingly do not carry over into the main function. I suspect that there is some problem with the pointers.

Could you please tell me what I am doing wrong?

#include <stdio.h>
#include <stdlib.h>


char A[10][10] = {
        {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'},
        {'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T'},
        {'U', 'V', 'W', 'X', 'Y', 'Z', '.', ',', '!', '?'},
        {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'},
        {'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T'},
        {'U', 'V', 'W', 'X', 'Y', 'Z', '.', ',', '!', '?'},
        {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'},
        {'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T'},
        {'U', 'V', 'W', 'X', 'Y', 'Z', '.', ',', '!', '?'},
        {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'}
};

char B[10][10] = {
        {' ', 't', 'a', 'b', 'c', 'f', 'g', 'z', 'j', 'm'},
        {' ', 't', 'a', 'b', 'c', 'f', 'g', 'z', 'j', 'm'},
        {' ', 't', 'a', 'b', 'c', 'f', 'g', 'z', 'j', 'm'},
        {' ', 't', 'a', 'b', 'c', 'f', 'g', 'z', 'j', 'm'},
        {' ', 't', 'a', 'b', 'c', 'f', 'g', 'z', 'j', 'm'},
        {' ', 't', 'a', 'b', 'c', 'f', 'g', 'z', 'j', 'm'},
        {' ', 't', 'a', 'b', 'c', 'f', 'g', 'z', 'j', 'm'},
        {' ', 't', 'a', 'b', 'c', 'f', 'g', 'z', 'j', 'm'},
        {' ', 't', 'a', 'b', 'c', 'f', 'g', 'z', 'j', 'm'},
        {' ', 't', 'a', 'b', 'c', 'f', 'g', 'z', 'j', 'm'}
};

void printTable(int rows, int columns, char table[rows][columns])
{
  for (int i = 0; i < rows; i = i + 1)
    {
      for (int j = 0; j < columns; j = j + 1)
          printf("%c", table[i][j]);
      printf("\n");
    }
  printf("\n");
}

void asssignTable(char* table, char* table_identity)
{
  if (table_identity[0] == 'A')
    table = A;
  else if (table_identity[0] == 'B')
    table = B;
  printf("In the function ""assignTable"":\n");      // does work 
  printTable(10, 10, table);
}

int main()
{
  char (*table)[10];
  asssignTable(&table, "A");
  printf("In main :\n");          // does not work
  printTable(10, 10, table);

  return 0;
}
3nondatur
  • 353
  • 2
  • 9
  • 3
    `table = A;` will change the local variable `table`. Read this: https://stackoverflow.com/questions/766893/how-do-i-modify-a-pointer-that-has-been-passed-into-a-function-in-c. Anyway, you cannot assign arrays in C. – Jabberwocky Apr 27 '23 at 12:17
  • 1
    You're trying to pass a `char (**)[10]` as an argument to a function that expects a `char *`. Do you understand how that can't possibly work? Your compiler should have warned about incompatible pointer types. Never attempt to run code until all such warnings have been corrected. – Tom Karzes Apr 27 '23 at 13:02

2 Answers2

5

Prerequisite knowledge:

  • char* has nothing in common with char(*)[columns] nor with char [rows][columns] and therefore cannot be used.
  • char(*)[columns] is a pointer to a 1D array. Though it may be used when iterating through a 2D array.
  • Whenever you pass an array to a function or declare it as a function parameter, it "decays" into a pointer to its first element. The first element of char table[rows][columns] is a char [columns] type. A pointer to such a type is char (*)[columns].
  • Whenever we assign something to a (pointer) parameter inside a function, that change does not affect the pointer on the caller side. We have to assign something to what a pointer parameter points at.

The fix is to rewrite the function like this:

void asssignTable(int rows, 
                  int columns, 
                  char (**table)[rows][columns], 
                  const char* table_identity)
{
  if (table_identity[0] == 'A')
    *table = &A;
  else if (table_identity[0] == 'B')
    *table = &B;
  printf("In the function ""assignTable"":\n");
  printTable(rows, columns, **table);
}
  • rows and columns because we want to use a consistent API with your printTable function.
  • We cannot use a char table [rows][columns] because it will decay to a char (*)[columns] and assigning that pointer to something won't affect the caller. We have to use a pointer to pointer - in this case a pointer to pointer to a 2D array. (This syntax is fairly consistent with ordinary pointers.)
  • *table de-references the pointer-to-pointer and gives us access to the pointer that was used in main(). We set this pointer to point at the address of the 2D array &A etc.
  • printTable(rows, columns, **table); By de-referencing twice we get an array char [rows][columns] after which it will decay into a pointer to the first element like usual, since we can never actually pass an array to a function by value.

The caller code becomes:

char (*table)[10][10];
asssignTable(10, 10, &table, "A");
printf("In main :\n");
printTable(10, 10, *table);
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Also note that if the pointer to 2D array syntax is cumbersome, you can do `char (*ptr)[columns] = table[0]` in main and then access `ptr[i][j]` without de-referencing anything. – Lundin Apr 27 '23 at 13:06
  • You're using one more level of indirection than necessary. You can just declare `char (*table)[10]`, then pass `&table`. The called function would then declare the parameter as `char (**table)[10]`, and instead of assigning `&A` or `&B`, it can just assign `A` or `B` to `*table`. This is the most natural way to do it in C, and it eliminates an entire level of indirection. It's also corresponds more closely to the way one would do it if the arrays were one-dimensional, where you would again assign `A` or `B` rather than `&A` or `&B`. – Tom Karzes Apr 27 '23 at 15:27
  • As a rule of thumb, if you find yourself taking the address of an array, in most cases you have one more level of indirection than you need (as in this case). – Tom Karzes Apr 27 '23 at 15:35
  • @TomKarzes A pointer to a 2D array _is_ `char (*)[r][c]`. In many situations it is more _convenient_ to use a pointer to a 1D array when iterating through a 2D array, because you can then use the readable `array[i][j]` syntax instead of `(*array)[i][j]`. But that doesn't make `char (*)[r][c]` a less correct form, just a sometimes more cumbersome one. The biggest advantage is that it scales will with even more dimensions: ` `char (*)[x][y][z]`. Whereas using a `char (*)[y][z]` pointer to access a 3D array starts to look mighty strange. – Lundin Apr 27 '23 at 15:50
  • Also `char (*table)[10]` is _not_ one more level of indirection from `char(*table)[10][10]`. One more level of indirection yields `char [10][10]`. – Lundin Apr 27 '23 at 15:51
  • It's would be one less, not one more. But you're talking about physical indirection, while I'm talking about syntactic indirection, i.e. the number of `*` operators needed to access it (either explicit or implied). `char (*table)[10]` has two syntactic levels of indirection, while `char (*table)[10][10]` has three syntactic levels. But as I said, what you have is something C programmers generally avoid, in favor of the more natural way that I described. There is no need to take the address of an array here. – Tom Karzes Apr 27 '23 at 16:04
  • I do agree using a pointer to the entire array, rather than a pointer to a slice of the array, scales up a little more obviously. In one dimension, people almost never use pointers to arrays, but instead use pointers to array elements, e.g. `char *` to access a `char [10]`. You *could* use a `char (*)[10]` to access a `char [10]`, but you'd need to take its address, and then you'd have an extra level of syntactic indirection. – Tom Karzes Apr 27 '23 at 16:08
1

The answer is really quite simple: C is a strictly 'pass-by-value' language. So, consider the following program:

void assign(int a) {
  a = 5;
}
int main() {
  int x = 0;
  assign(x);
  printf("%d\n",x);
}

Hopefully, it is obvious that this program will print '0', and not '5'. This is because the value of x is being passed to assign. Now, consider a very similar program:

int globalArray[] = {1,2,3};
void assign(int *p) {
  p = globalArray;
}

int main() {
  int *array;
  // dynamically allocate array and initialize to {5,6,7}
  // ...
  assign(array);
  print_array(array); // this is a pretend function
}

Notably, array is simply a value (a memory address), just like int x was a value in the last program. Therefore, the assign function doesn't actually change anything.

Instead, you could do something like

int globalArray[] = {1,2,3};
void assign(int** arrayPointer) {
  *arrayPointer = globalArray;
}

Another thing to note is that stack-allocated 2D arrays are arranged differently from dynamically allocated 2D arrays.

squidwardsface
  • 389
  • 1
  • 7