2

(This is a follow up to this question.)

I'm trying to understand what "best practice" (or really any practice) is for passing a multidimensional array to a function in C. Certainly this depends on the application, so lets consider writing a function to print a 2d array of variable size. In particular, I'm interested in how one would write the function printArry(_____) in the following code.

The answer in the question referenced above uses variable length array (VLA) features of C. These are not present in the C98 standard and are optional in the C11 standard (they also don't seem to compile in C++11 either).

How would one answer this question without using VLA functionality? Passing a two dimensional array seems like a fairly basic task, so I can't imagine there isn't a reasonable approach to this.

void printArry(_____){
    /* what goes here? */
}

int main(void){

    int a1=5;
    int b1=6;
    int a2=7;
    int b2=8;

    int arry1[a1][b1];
    int arry2[a2][b2];

    /* set values in arrays */

    printArry(arry1, a1, b1);
    printArry(arry2, a2, b2);
    return 0;
}
Community
  • 1
  • 1
ABD
  • 209
  • 1
  • 7
  • 6
    Excuse me, but aren't `int arry1[a1][b1];` etc, VLAs? With these, there is ***even more*** of a need to pass the array dimension to a function, since you can't define a type. – Weather Vane Jan 01 '16 at 21:05
  • Similar question: [Passing multidimensional array in c90 (pre VLA-style)](http://stackoverflow.com/questions/30621250/passing-multidimensional-array-in-c90-pre-vla-style?). – kaylum Jan 01 '16 at 21:06
  • @Weather Vane, I'm not an expert on the standards but (for example) c++11 accepts the initialization of int arry1[a1][b1] but won't accept VLA in a function definition. I assume the same is true in c98 but I can't test it at the moment. – ABD Jan 01 '16 at 21:09
  • @ABD the only information about an array passed as a function argument is its base address as a pointer. Since the function cannot know its dimensions, you have to pass that too. – Weather Vane Jan 01 '16 at 21:12
  • can't write such as `int arry1[a1][b1];` in c**89**. – BLUEPIXY Jan 01 '16 at 21:15
  • @Weather Vane, I'm fine passing the dimensions too. I'm interested in an answer that involves passing the dimensions as parameters to the function. – ABD Jan 01 '16 at 21:15
  • Did you digest the answers, and links, in the previous question? – Weather Vane Jan 01 '16 at 21:22
  • @Weather Vane, the first answer was entirely based on VLAs. Your answer explained that one needs to pass the dimensions, which was exactly my point when I asked you if one needed to do that. These were the only two answers. Also the only link in the answers and comments is one describing the prototyping function [*]. So I think I can say, yes, I did digest the answers and links in the previous question. – ABD Jan 01 '16 at 21:25

3 Answers3

3

The question is currently problematic:

The answer in the question referenced above uses variable length array (VLA) features of C. These are not present in the C98 standard and are optional in the C11 standard.

The C standard prior to C99 (ISO/IEC 9899:1990) was C89 (ANSI) or C90 (ISO) — functionally the same standard. There was C++98 (ISO/IEC 14882:1998), but that's a wholly different language.

int main(void)
{
    int a1=5;
    int b1=6;
    int a2=7;
    int b2=8;

    int arry1[a1][b1];
    int arry2[a2][b2];

This sample code is using VLAs; it will not compile under strict C90 rules.


If you are constrained to the pre-1999 C standard, then you are constrained to using code similar to this. Clearly, you can initialize the arrays by reading data from a file or anything else that takes your fancy; using direct initializers is possible because the arrays are no longer VLAs.

#include <stdio.h>

void printArry(int d1, int d2, int *data);

int main(void)
{
    enum { a1 = 5, b1 = 6, a2 = 7, b2 = 8 };
    int arry1[a1][b1] =
    {
        {  9,  8,  7,  6,  5,  4 },
        {  2,  3,  4,  3,  2,  1 },
        { -9, -8, -7, -6, -5, -4 },
        { 39, 38, 37, 36, 35, 34 },
        { 99, 98, 97, 96, 95, 94 },
    };
    int arry2[a2][b2] =
    {
        { 198, 158, 165, 136, 198, 127, 119, 103, },
        { 146, 123, 123, 108, 168, 142, 119, 115, },
        { 160, 141, 168, 193, 152, 152, 147, 137, },
        { 144, 132, 187, 156, 188, 191, 196, 144, },
        { 197, 164, 108, 119, 196, 171, 185, 133, },
        { 107, 133, 184, 191, 166, 105, 145, 175, },
        { 199, 115, 197, 160, 114, 173, 176, 184, },
    };

    printArry(a1, b1, &arry1[0][0]);
    printArry(a2, b2, &arry2[0][0]);

    return 0;
}

void printArry(int d1, int d2, int *data)
{
    int i, j;
    for (i = 0; i < d1; i++)
    {
        for (j = 0; j < d2; j++)
            printf(" %4d", data[i * d2 + j]);
        putchar('\n');
    }
}

The printArry() function is, essentially, doing the index (subscript) calculation that you would like the compiler to do for you — but it can't because it is not able to handle C99 VLAs.

The first array of data is hand-crafted to allow problems to be spotted (such as using d1 instead of d2 in the subscript calculation). The second array of data is simply 56 random values between 100 and 199.

Sample output:

    9    8    7    6    5    4
    2    3    4    3    2    1
   -9   -8   -7   -6   -5   -4
   39   38   37   36   35   34
   99   98   97   96   95   94
  198  158  165  136  198  127  119  103
  146  123  123  108  168  142  119  115
  160  141  168  193  152  152  147  137
  144  132  187  156  188  191  196  144
  197  164  108  119  196  171  185  133
  107  133  184  191  166  105  145  175
  199  115  197  160  114  173  176  184
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
2

You would not need a VLA to pass an VLA as parameter. In fact you must know that arrays and functions can be neither passed to nor returned from a function (unless you use a structure hack, but in our case this wouldn't work).

However you could pass a pointer to your array using VM types. Something like this:

void printArry(size_t a1, size_t b1, int (*parry)[a1][b1]){
/* accessing array 'array' by '*parry' */
}

You must declare the pointer to the VLA array parameter after declaring the parameters defining the array dimensions for obvious reasons ('a1' and 'b1' must be defined at the time of use).

The code using this function will look like this:

int main(int argc, char** argv){

size_t a1=5;
size_t b1=6;
size_t a2=7;
size_t b2=8;

int arry1[a1][b1];
int arry2[a2][b2];

/* set values in arrays */

printArry(a1, b1, &arry1);
printArry(a2, b2, &arry2);

}

We can't use the structure hack firstly because structures can't have VLA members and secondly because even if they could we wouldn't be able to specify their length when an argument is passed (the member type must be known at the time of declaration and we can't declare structures inside a function declaration).

Confirmed - even ordinary (non VM) arrays must work with this version of 'printArry'(supposing that their dimensions sizes are properly passed to the function). At least this is what the C11 standard says about it in it's examples.

All pointers to arrays with equal dimensions sizes can be assigned one another (no-matter if they are VM or not) without invoking UB.

Here are the examples on the latest C11 standard from which this comes from:

EXAMPLE 3 The following declarations demonstrate the compatibility rules for variably modified types.

extern int n;

extern int m;

void fcompat(void)
{
    int a[n][6][m];

    int (*p)[4][n+1];

    int c[n][n][6][m];

    int (*r)[n][n][n+1];

    p = a;   // invalid: not compatible because 4 != 6

    r = c;   // compatible, but defined behavior only if
             // n == 6 and m == n+1

}

At $6.7.6.2.

Which means you can call this function for example like this:

int main(int argc, char **argv)
{
    int arry[10][23];

    printArry(sizeof(arry) / sizeof(arry[0]), 
            sizeof(arry[0]) / sizeof(arry[0][0]), &arry);
}

On the other hand you could also access your array the bad old way using a simple pointer to it's first element:

void printArry(size_t a1, size_t b1, int *parryFirstElement){
    for(size_t i = 0; i < a1; ++i)
        for(size_t y = 0; y < b1; ++y)
            parryFirstElement[i * b1 + y]; //your array [i][y] element
}

Calling this function could be done the same way as before (as IIRC pointer in 'C' doesn't need to be casted).

I replaced 'int' in the times where is better to use 'size_t'. Although if you're targeting some old standard you could reverse them back to 'int'.

I may be wrong though. I'm sleepy right now. If I'm wrong - well then that's bad.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
AnArrayOfFunctions
  • 3,452
  • 2
  • 29
  • 66
  • I don't follow how the function you proposed is suppose to work. what is the role of a2 and b2? – ABD Jan 01 '16 at 21:34
  • They define the dimensions of the VLA you are passing pointer to. But pleas wait a bit as I'm editing my question right now. – AnArrayOfFunctions Jan 01 '16 at 21:36
  • OK. You can comment right now. I'm going to switch to my other PC and check the standard by this time (for more information, the code here surely works). – AnArrayOfFunctions Jan 01 '16 at 21:40
  • I don't understand: your calls to printArry have 3 parameters, but the function is defined with 5 parameters? – ABD Jan 01 '16 at 21:43
  • Sorry for that. But I fixed it recently. – AnArrayOfFunctions Jan 01 '16 at 21:44
  • 2
    Thanks. I think I understand this. But is this really not using VLAs? If I had a static definition of arry1 and arry2 would this compile (in particular the function printArry) in c98? – ABD Jan 01 '16 at 21:47
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/99485/discussion-between-fisocpp-and-abd). – AnArrayOfFunctions Jan 01 '16 at 21:50
0

There seems to be a misunderstanding about the standards and VLAs: C99 and all following C standards contain VLAs, and compilers, such as the gcc support it (with the -std=c99, -std=C11, etc. flags).

What does not work, is C++ of any standard. VLAs are simply not a feature of the language, C is much more powerful in this regard than C++.

So, if you want to use VLAs, just make sure that you are compiling with a real C compiler (gcc), not with a C++ compiler (g++). If you do so, they should work like a charm.


Afaik, there is a proposal for C++17 that says it wants to add VLAs to the language. However, the VLA functionality in that proposal is seriously restricted. Up to the point of complete unusability in my eyes. C99 still remains much more powerful than C++17 in that regard.

cmaster - reinstate monica
  • 38,891
  • 9
  • 62
  • 106
  • VLAs are not exactly a "power" feature. Supporting VLAs like C does would break a number of other capabilities of C++ - which probably explains why that proposal you mention to restricts them. C++ doesn't really need VLA, as it has standard containers, which can contain each other recursively. This is more consistent with the philosophy of C++ to use or add features to the library before changing the language (the reverse of the C philosophy). Whether C VLAs is better depends on preference and, in C++, what other features one is willing to give up to achieve them. – Peter Jan 01 '16 at 22:53
  • Technically, neither gcc nor g++ are compilers. – edmz Jan 01 '16 at 23:05
  • @Peter What capabilities do they break? They're currently supported as language extensions (gcc, clang). Supporting them allows for stack allocation, which can be a non-trivial optimization. The [dynarray](http://en.cppreference.com/w/cpp/container/dynarray) proposal explicitly states this. – Jason Jan 01 '16 at 23:25
  • Note that G++ (the GCC C++ compiler) allows VLAs in C++ by default; you can tell it not to accept them. – Jonathan Leffler Jan 02 '16 at 01:37
  • @black: OK; I'll bite — in what way are `gcc` and `g++` not compilers? – Jonathan Leffler Jan 02 '16 at 01:38
  • Jason: There are several forms of type deduction within template functions that are incompatible with VLAs. Those capabilities are used quite extensively in real-world libraries and code – Peter Jan 02 '16 at 01:48
  • Jonathan: black is referring to the fact that gcc and g++ are implemented as driver programs that execute a set of other programs (the preprocessor, compiler phases, linker, etc). They don't actually do the compilation themselves - they orchestrate the process. However, practically, users of gcc and g++ use them to do compilation. – Peter Jan 02 '16 at 01:51
  • @Peter Jonathan won't hear you if you don't include the string "@JonathanLeffler" in your comment. – cmaster - reinstate monica Jan 02 '16 at 09:27
  • @JonathanLeffler Yes, Peter correctly expressed what I wanted to say. Indeed, GCC stands for GNU Compiler Collection and can compile many languages including, but not limited to, C and C++ (Go and Haskell too for example). IIRC, it used to stand for GNU C compiler. Now the compilation is left to back-ends such as `cc1` and `cc1plus`. PS: "bite" -- why this verb? – edmz Jan 02 '16 at 12:27
  • @black: OK — I recognize what you're saying, and its validity. It's a question of perspective, or maybe level of detail. C compilers have always been handled thus. – Jonathan Leffler Jan 02 '16 at 12:37
  • @Peter I'm guessing you're referring to specialization based on length? – Jason Jan 04 '16 at 19:59