Variadic C function printing multiple 2-D char arrays

£可爱£侵袭症+ 提交于 2019-12-06 16:29:14

Actually, I came to the same conclusion like Eric except that I even didn't consider to solve multi-dimension array issues by mere typedefs.

Out of curiosity, I tried to write down a working version of the OP. As I finally got one I want to present my code (in addition to Eric Postpischils answer).

So, after some fiddling I got this working version testVarArgMDimArray.c:

#include <stdio.h>
#include <stdarg.h>

#define BOARDSIZE 10
#define NCOLS BOARDSIZE
#define NROWS BOARDSIZE

typedef char Board[NROWS][NCOLS];
typedef char (*PBoard)[NCOLS];

void showBoardVariadic(int nArgs, ...)
{
  va_list ap;
  va_start(ap, nArgs);
  /* Attention! VLAs are an optional feature of C11. */
  PBoard boards[nArgs];
  for (int i = 0; i < nArgs; ++i) {
    boards[i] = va_arg(ap, PBoard);
  }
  /* print the 2D arrays side-by-side */
  for (int row = 0; row < NROWS; ++row) {
    for (int i = 0; i < nArgs; ++i) {
      if (i) putchar('\t');
      for (int col = 0; col < NCOLS; ++col) {
        printf(" %c", boards[i][row][col]);
      }
    }
    putchar('\n');
  }
  va_end(ap);
}

int main()
{
  Board playerBoard;
  Board opponentBoard;
  /* initialize boards */
  for (int row = 0; row < NROWS; ++row) {
#ifdef CHECK /* for checking */
    /* insert some pattern in col 0 for checking */
    playerBoard[row][0] = 'a' + row;
    opponentBoard[row][0] = 'A' + row;
    for (int col = 1; col < NCOLS; ++col) {
      playerBoard[row][col] = opponentBoard[row][col] = '~';
    }
#else /* productive code */
    for (int col = 0; col < NCOlS; ++col) {
      playerBoard[row][col] = opponentBoard[row][col] = '~';
    }
#endif /* 1 */
  }
  showBoardVariadic(2, playerBoard, opponentBoard);
  /* done */
  return 0;
}

Before I got it working there were issues which I tried solve in VS2013. Too sad – VS2013 does not support VLAs. Hence, I had to do it in mind but I got it running.

As already recommended in one of my comments, I chose char as board element instead of char*. I could've solved it for char* as well but I feel that char* could've been an "accidental" choice in the OP.

Following the idea of Eric, I made a type for the 2D board array:

typedef char Board[NROWS][NCOLS];

The more important is probably the second:

typedef char (*PBoard)[NCOLS];

Remember that arrays in function parameters are always compiled as pointers. C never passes arrays as arguments. Hence, if we call a function with an argument of type Board we will receive an argument of type PBoard.

Please, note the parentheses around *PBoard – this grants that PBoard is a pointer to array. If you remove them you get an array of pointers instead – big difference and not what is intended.

Having mastered this, things in showBoardVariadic() become rather easy.

The array of boards is declared as:

PBoard boards[nArgs];

The assignment with va_arg is simply:

boards[i] = va_arg(ap, PBoard);

The access to the boards is simply:

printf(" %c", boards[i][row][col]);

This might be surprising but in this case the pointer to array behaves like an array of arrays. It just has a different type. (E.g. you should not use sizeof with PBoard as in this case the different types would take effect.)

As every board element contains the same contents in this state of development, I was afraid whether issues in board indexing could be unnoticed. Therefore I implemented an alternative initialization where each first element of column gets another character: for playerBoard 'a' + row, for opponentBoard 'A' + row. This test assignment is activated by defining the macro CHECK. In my test session, I compiled once with -D CHECK once without.

Btw. if you wonder why I introduced NROWS and NCOLS: While writing this answer I realized that I wouldn't notice if I accidentally flipped rows and columns somewhere as they have equal size in OP. Thus, I separated things and tested with NROWSNCOLS. Phew – it still worked properly.

Last but not least, my sample session in Cygwin (as I'm on Windows 10):

$ gcc --version
gcc (GCC) 6.4.0

$ gcc -std=c11 -D CHECK -o testVarArgMDimArray testVarArgMDimArray.c 

$ ./testVarArgMDimArray
 a ~ ~ ~ ~ ~ ~ ~ ~ ~     A ~ ~ ~ ~ ~ ~ ~ ~ ~
 b ~ ~ ~ ~ ~ ~ ~ ~ ~     B ~ ~ ~ ~ ~ ~ ~ ~ ~
 c ~ ~ ~ ~ ~ ~ ~ ~ ~     C ~ ~ ~ ~ ~ ~ ~ ~ ~
 d ~ ~ ~ ~ ~ ~ ~ ~ ~     D ~ ~ ~ ~ ~ ~ ~ ~ ~
 e ~ ~ ~ ~ ~ ~ ~ ~ ~     E ~ ~ ~ ~ ~ ~ ~ ~ ~
 f ~ ~ ~ ~ ~ ~ ~ ~ ~     F ~ ~ ~ ~ ~ ~ ~ ~ ~
 g ~ ~ ~ ~ ~ ~ ~ ~ ~     G ~ ~ ~ ~ ~ ~ ~ ~ ~
 h ~ ~ ~ ~ ~ ~ ~ ~ ~     H ~ ~ ~ ~ ~ ~ ~ ~ ~
 i ~ ~ ~ ~ ~ ~ ~ ~ ~     I ~ ~ ~ ~ ~ ~ ~ ~ ~
 j ~ ~ ~ ~ ~ ~ ~ ~ ~     J ~ ~ ~ ~ ~ ~ ~ ~ ~

$ gcc -std=c11 -o testVarArgMDimArray testVarArgMDimArray.c 

$ ./testVarArgMDimArray
 ~ ~ ~ ~ ~ ~ ~ ~ ~ ~     ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
 ~ ~ ~ ~ ~ ~ ~ ~ ~ ~     ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
 ~ ~ ~ ~ ~ ~ ~ ~ ~ ~     ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
 ~ ~ ~ ~ ~ ~ ~ ~ ~ ~     ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
 ~ ~ ~ ~ ~ ~ ~ ~ ~ ~     ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
 ~ ~ ~ ~ ~ ~ ~ ~ ~ ~     ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
 ~ ~ ~ ~ ~ ~ ~ ~ ~ ~     ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
 ~ ~ ~ ~ ~ ~ ~ ~ ~ ~     ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
 ~ ~ ~ ~ ~ ~ ~ ~ ~ ~     ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
 ~ ~ ~ ~ ~ ~ ~ ~ ~ ~     ~ ~ ~ ~ ~ ~ ~ ~ ~ ~

$

typedef – very clever Eric...

The C specification of va_arg requires “The parameter type shall be a type name specified such that the type of a pointer to an object that has the specified type can be obtained simply by postfixing a * to type.” The string char*[][BOARDSIZE] does not satisfy this. You should use a typedef to give a name to the type.

Additionally, in the parameter list of a function declaration, char*[][BOARDSIZE] is automatically adjusted to char*(*}[BOARDSIZE]. In a va_arg (or typedef), it is not. You should use the adjusted form.

So, you should define a name for a type that is a pointer to an array of BOARDSIZE pointers to char:

typedef char *(*MyType)[BOARDSIZE];

You should change boards to be an array of these rather than an array of arrays:

MyType boards[numArgs];

and you should change va_arg to use the new type:

boards[i] = va_arg(ap, MyType);

Also note that you are setting every element of the boards to the string "~". This sets them all to point to a string literal, which is likely not what you want. You are not allowed to modify characters in this string literal, so the only way to change what the boards contain is to change them to point to different strings.

If each board element is going to be a single character, you should use char instead of char *. If they are going to be a fixed or small number multiple characters, you might want an array of char instead of a pointer to char. If they are going to be a considerable number of multiple characters, you may want to use char * but allocate space for each board element.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!