46

I have come across some C code that compiles, but I do not understand why. Specifically, I have a C library that has a lot of code using this format:

void get_xu_col(int i_start,
                int n,
                double x[n],
                int n_x,
                int n_u,
                int n_col,
                double xu_col[n_col][n_x + n_u]){
    ... 
}

int main(){
    ...
    double xu_col[n_col][n_x + n_u];
    get_xu_col( ..., xu_col );
    ...
}

What I don't understand is why the compiler allows for sizing in the arrays. To the best of my understanding, either the sizes must be fixed (e.g. xu_col[9][7]) or undefined (e.g. xu_col[][]). In the above code, it appears that the sizes are not compile-time constants.

Is the compiler just ignoring the arguments here? or is it really doing a compile-time check on the dimensions?

If it is the latter, then it seems error-prone to pass the dimensions separately.

The second part of the question is:

Why doesn't the same version work in C++? When I literally change the file extension from .c to .cpp and try to recompile, I get

candidate function not viable: no known conversion from 'double [n_col][n_x + n_u]' to 'double (*)[n_x + n_u]' for 7th argument
void get_xu_col(int i_start, int n, double x[n], int n_x, int n_u, int n_col, double xu_col[n_col][n_x + n_u]);

I would like to know which idiom I should use to convert this code to C++, since apparently the previous idiom was something that works in C, but not C++.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
bremen_matt
  • 6,902
  • 7
  • 42
  • 90
  • 3
    Interestingly, the compiler should actually ignore the values of the arguments used as array sizes unless you force it to consider them by adding `static` (e.g. `double x[static n]`). Despite *looking* like it does something (and not working in C++, and requiring the values to be valid/in-scope expressions), the form in the question is a bit useless. Annoying hole in the language. – Alex Celeste Feb 21 '18 at 08:54
  • I do not understand what the you are saying the word `static` would do... Would adding the keyword `static` make it a compile-time check? – bremen_matt Feb 21 '18 at 19:19
  • Relevant: [How do I use arrays in C++?](https://stackoverflow.com/questions/4810664/how-do-i-use-arrays-in-c). – sbi Feb 21 '18 at 19:32
  • 1
    In case you are unaware, it is possible to use C code in C++ projects, without "converting the code to C++" – M.M Feb 22 '18 at 05:02
  • @M.M OP said he just converted the extension of the file and failed when tried to compile. – a concerned citizen Feb 22 '18 at 06:55
  • I am aware that I can link the compiled files. – bremen_matt Feb 22 '18 at 07:36
  • @bremen_matt the standard doesn't require that the array passed to the parameter `double x[n]` actually has `n` elements, or even that it is non-null. The `n` is basically just documentation and the declaration is equivalent to `double * x`. Adding that `static` activates the requirements you *expected* to be imposed on the original. (Whether the compiler is able to check and enforce this is another matter: it should be equally able to do it either way - not in the general case but flow analysis can catch simple usages - but in the first, it isn't really allowed to error out, only warn.) – Alex Celeste Feb 22 '18 at 09:38
  • (That said, my comment only applies to the outermost array derivation: the inner array size in a multi-dimensional array must always mean something because otherwise it wouldn't be able to work out the pointer offsets.) – Alex Celeste Feb 22 '18 at 09:41
  • FWIW You can kind of do the same thing with templates in C++: http://coliru.stacked-crooked.com/a/014f48fe8421916a – NathanOliver Feb 22 '18 at 14:16
  • Yep. That is ultimately the closest thing in the C++ sense, and preferred because it will do a compile-time check on the dimensions. – bremen_matt Feb 22 '18 at 14:20

7 Answers7

62

In C, it is possible to use function parameters to define the size of a variable length array parameter as long as the size comes before the array in the parameter list. This is not supported in C++.

Stephen Docy
  • 4,738
  • 7
  • 18
  • 31
38

The reason it works in C, but not in C++ is simply because it's C code and not C++. The two languages share a history, not a grammar.

The C++ method to pass variable-sized arrays is std::vector, probably by reference if you intend to modify the vector in the function, or by const reference if you don't.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
MSalters
  • 173,980
  • 10
  • 155
  • 350
  • 1
    I thought you had to use `[*]` for variable length array function parameters in C. – Bathsheba Feb 21 '18 at 08:51
  • 3
    @Bathsheba: The first time I saw `[*]` was already in a context explaining why `[N]` worked there as well. But neither syntax is going to work in C++ - this is a C invention after C++ forked off. – MSalters Feb 21 '18 at 08:55
  • I've been naughty and made a C++ edit. If you don't like it please roll back. (And you can keep the upvote either way.) – Bathsheba Feb 21 '18 at 08:57
  • I don't think that a vector is the correct approach here because it wouldn't give you a compile-time check, whereas the C-version seems to. Perhaps an std::array? – bremen_matt Feb 21 '18 at 09:05
  • @Bathsheba the C++ edit is a good addition because it captures the fact that the "array" in C will automatically be passed by "reference", whereas a vector in C++ would be copied by default if written out in the naive way, so this is indeed needed to replicate the C behaviour more closely. – Alex Celeste Feb 21 '18 at 09:05
  • 6
    Downvoted because simply restating the fact given in the question is not an answer to why. I suspect the answer is probably because it was added in C99 or later (long after C++ was created as a near-superset of C), but I don't know enough C for that to be anything more than speculation. – Nye Feb 21 '18 at 09:57
  • 4
    @Nye: There are parts of C99 that made it into C++, so that's not the reason. But C++ already had `std::vector` since 1998. It didn't need variable-length arrays. – MSalters Feb 21 '18 at 10:06
  • 4
    @bremen_matt: How do you figure the C version could do a compile-time check on a runtime-variable-length array? – DevSolar Feb 21 '18 at 12:25
  • 1
    @Bathsheba: An older answer which shows more about the `[*]` syntax : https://stackoverflow.com/a/29576308/15416 – MSalters Feb 21 '18 at 14:15
  • I'd say a `span` bids fair to fill that role, in all cases where you don't actually need to resize the contiguous sequence. https://stackoverflow.com/questions/45723819/what-is-a-span-and-when-should-i-use-one – Deduplicator Feb 21 '18 at 14:30
  • @MSalters I don't see how this is the case, a VLA in this case really just means run time statically allocated array, which can now exist on the stack. You can't do this in C++ despite it actually being pretty useful, forcing you to use dynamic allocation where it simply isn't necessary. – Krupip Feb 21 '18 at 16:41
  • 1
    VLAs aren't in C++ because there is no way to make them interact nicely with templates, overload resolution and other C++ language features. The C++ language tries to forget about C arrays as much as possible due to their weird syntax and semantics – M.M Feb 22 '18 at 04:58
12

What I don't understand is why the compiler allows for sizing in the arrays. To the best of my understanding, either the sizes must be fixed (e.g. xu_col[9][7]) or undefined (e.g. xu_col[][]). In the above code, it appears that the sizes are not compile-time constants.

You are right, the sizes are not compile-time constants. If you have a two-dimensional array, x[line][col] the compiler needs the number of elements in a line to calculate the address of an element. Look at get_char_2() and get_char_3() example code.

If you use variable length arrays (VLAs) as function parameters you have to supply these number (see get_char_1 example). you can write:

 my_func( x[][width] )

or you can write

 my_func( x[999][width] )

Is the compiler just ignoring the arguments here? or is it really doing a >compile-time check on the dimensions?

The first number (999) will be ignored by the compiler. The second is needed. Without the line size, the compiler can not calculate addresses inside these 2D-array. The compiler does not do run-time or compile-time checks for VLAs in C.

/* file: vla.c
 *
 * variable length array example
 *
 * compile with:
 *   
 *    gcc -g -Wall -o vla vla.c 
 *
 */

#include <stdio.h>
#include <wchar.h>


/* 4 Lines - each line has 8 wide-characters */
wchar_t tab[][8] = {
{ L"12345678" },
{ L"abcdefgh" },
{ L"ijklmnop" },
{ L"qrstuvwx" }
};

/* memory layout:   
   0x00:   0x0031  0x0032 0x0033  0x0034  0x0035  0x0036  0x0037  0x0038 
   0x20:   0x0061  0x0062 0x0063  0x0064  0x0065  0x0066  0x0067  0x0068 
   ...

*/



/* get character from table w/o variable length array and w/o type */
char get_char_3(int line, int col, int width, int typesize, void *ptr )
{
char ch = * (char *) (ptr + width * typesize * line + col * typesize ); 

printf("line:%d col:%d char:%c\n", line, col, ch ); 
return ch;
}


/* get character from table w/o variable length array */
char get_char_2(int line, int col, int width, wchar_t *ptr)
{
char ch = (char) (ptr + width * line)[col]; 

printf("line:%d col:%d char:%c\n", line, col, ch ); 
return ch;
}

/* get character from table : compiler does not know line length for 
   address calculation until you supply it (width). 
*/
char get_char_1(int line, int col, int width, wchar_t aptr[][width] )
{
/* run-time calculation: 
   (width * sizeof(char) * line)  + col 
     ???    KNOWN          KOWN     KNOWN
*/
char ch = (char) aptr[line][col];

printf("line:%d col:%d char:%c\n", line, col, ch ); 
return ch;
}


int main(void)
{
char ch;

ch = tab[1][7]; /* compiler knows line length */
printf("at 1,7 we have: %c\n",  ch );

/* sizeof tab[0][0] == sizeof(wchar_t) */ 

ch = get_char_1(1,7, sizeof(tab[0])/sizeof(tab[0][0]), tab);
printf("1 returned char: %c\n", ch );

ch = get_char_2(1,7, sizeof(tab[0])/sizeof(tab[0][0]), (wchar_t*)tab);
printf("2 returned char: %c\n", ch );

ch = get_char_3(1,7, sizeof(tab[0])/sizeof(tab[0][0]),
        sizeof( wchar_t), tab);
printf("3 returned char: %c\n", ch );

printf("table size: %lu, line size: %lu,  element size: %lu\n",
       sizeof(tab),
       sizeof(tab[0]),
       sizeof(tab[0][0])
       );

printf("number of elements per lines: %lu\n",
       sizeof(tab[0])/sizeof(tab[0][0]));


printf("number of lines: %lu\n",
       sizeof(tab)/sizeof(tab[0]));

return 0;
}
Jens Harms
  • 406
  • 3
  • 5
5

All that it does (in C) is allow you to write indexing code in the called funcion without having to do the address calculation yourself, for example:

double d= xu_col[i*row_size + j]; //get element [i,j]

versus

double d= xu_col[i][j];
Paul Ogilvie
  • 25,048
  • 4
  • 23
  • 41
5

When a parameter is declared as having a single-dimensional array type, C ignores the given size and instead treats the parameter as a pointer to the element type. For nested (multi-dimensional) arrays, such treatment is only applied to the outer array. In C89, inner dimensions had to have fixed sizes, but in C99 the dimensions can be expressions. If parameters which are needed to compute an array's size are not listed until after the array, it will be necessary to use a curious mixture of old and new syntax to declare the function, e.g.

int findNonzero(short dat[*][*], int rows, int cols);
int findNonzero(dat, rows, cols)
    int rows,cols;
    short dat[static rows][cols];
{
    for (int i=0; i<rows; i++)
        for (int j=0; j<cols; j++)
            if (dat[i][j] != 0) return i;
    return -1;
}

Note that the array sizes are specified as * in the function prototype, and that the function definition does not specify types in the argument list but instead describes all the parameters' types between the argument list and the opening brace. Note also that while the compiler is likely to ignore the number of rows in the array declaration, but a smart compiler may be able to use it to facilitate optimization. Effectively, the weird "static" syntax invites the compiler to read any parts of the array, up to the given size, as it sees fit, whether or not the values are read by the code. This may be helpful on some platforms where code might benefit from processing multiple items of the array at once.

supercat
  • 77,689
  • 9
  • 166
  • 211
4

The difficulty with your code sample is that one of the function parameters is protoyped, double xu_col[n_col][n_x + n_u], where n_x and n_u are variables, not constants. If you just pass this as a double[] instead, some C++ compilers might allow a cast such as double (&table)[n_col][n_x + n_u] = (double(&)[n_col][n_x + n_u])xu_col; to work as a non-standard extension, but the portable approach would be to write accesses like xu_col[i*(n_x+n_u) + j], which you could simplify with a helper function if that’s too ugly.

An alternative approach, probably more in keeping with the spirit of the STL, might be to write a minimal container class that knows its dimensions, stores elements in a linear array for efficiency. Then you might declare redim_array<double> table = redim_array<double>(xu_col, n_col*(n_x+n_u)).redim(n_col, n_x+n_u); and access table(i,j).

Several of the other answers have described the syntax of variable-length arrays, but another aspect of your question is how it’s legal to implicitly convert a rectangular¹ two-dimensional array to a one-dimensional array.

What happens is that the rectangular array is laid out as consecutive elements in memory, so it can degenerate to a pointer to the elements, and then the function parameter can interpret that as an array with a different geometry.

Here’s a short little program that demonstrates this behavior.

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

#define ROWS 2
#define COLS 4
#define ELEMS (ROWS*COLS)

int flatten_array( const ptrdiff_t n, const int a[n] )
{
  int printed = 0;

  for ( ptrdiff_t i = 0; i < n; ++i )
    printed += printf( "%d ", a[i] );

  return printed + printf("\n");
}

int rectangular_array( const ptrdiff_t m,
                       const ptrdiff_t n,
                       const int a[m][n] )
{
  int printed = 0;

  for ( ptrdiff_t i = 0; i < m; ++i ) {
    for ( ptrdiff_t j = 0; j < n; ++j )
      printed += printf( "%d ", a[i][j] );

    printed += printf("\n");
  }

  return printed + printf("\n");
}

int main(void)
{
  static const int matrix[ROWS][COLS] = {
    {11, 12, 13, 14},
    {21, 22, 23, 24}
  };
  static const int vector[ELEMS] = {11, 12, 13, 14, 21, 22, 23, 24};

  flatten_array( ELEMS, *(const int (*const)[ELEMS])matrix );
  printf("\n");
  rectangular_array( ROWS, COLS, *(const int (*const)[ROWS][COLS])vector );

  return EXIT_SUCCESS;
}

There’s some language-lawyering in the comments below² about whether passing the array arguments without the explicit casts is technically legal by standard. I’ve chosen to relegate that to a footnote and just delete the example with no casts. In the real world, you will sometimes see code without the pointer-to-array-of-different-geometry cast, and it might generate a warning. The memory layout of the two arrays is required by the standard to be the same.

To convert to C++, you can use the pointer-conversion trick, or you now can code-golf it a bit by using references.

Here is a C++ translation of the program above. It requires all but the first dimension of the array being passed in to be constexpr, but some compilers support C99-style variable-length arrays as an extension.

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

constexpr ptrdiff_t rows = 2;
constexpr ptrdiff_t cols = 4;
constexpr ptrdiff_t elems = rows * cols;

int flatten_array( const ptrdiff_t n, const int a[] )
{
  int printed = 0;

  for ( ptrdiff_t i = 0; i < n; ++i )
    printed += printf( "%d ", a[i] );

  return printed + printf("\n");
}

int rectangular_array( const ptrdiff_t n, const int a[][cols] )
{
  int printed = 0;

  for ( ptrdiff_t i = 0; i < n; ++i ) {
    for ( ptrdiff_t j = 0; j < cols; ++j )
      printed += printf( "%d ", a[i][j] );

    printed += printf("\n");
  }

  return printed + printf("\n");
}

int main(void)
{
  static const int matrix[rows][cols] = {
    {11, 12, 13, 14},
    {21, 22, 23, 24}
  };
  static const int vector[elems] = {11, 12, 13, 14, 21, 22, 23, 24};

  flatten_array( elems, (const int(&)[elems])matrix );
  printf("\n");
  rectangular_array( rows, (const int(&)[rows][cols])vector );

  return EXIT_SUCCESS;
}

¹ C programmers sometimes call either arrays like int matrix[ROWS][COLS] or arrays like char** argv “two-dimensional arrays.” Here, I call the former rectangular and the latter ragged.

² The constraint on function arguments in the C11 standard is ‘Each argument shall have a type such that its value may be assigned to an object with the unqualified version of the type of its corresponding parameter.’ Furthermore ‘A declaration of a parameter as ''array of type'' shall be adjusted to ''qualified pointer to type''’ and, if this applies recursively, a multidimensional array of some type will be adjusted to a flat pointer of that type.

Davislor
  • 14,674
  • 2
  • 34
  • 49
  • `rectangular_array( ROWS, COLS, vector );` is a constraint violation for passing the wrong argument type. It's not legal. – M.M Feb 22 '18 at 05:01
  • @M.M Before I get all language-lawyerly with you: I give the solution to that in my second code sample, so use that if you prefer. – Davislor Feb 22 '18 at 05:23
  • @M.M I added the language lawyering as a footnote. – Davislor Feb 22 '18 at 05:41
0

regarding the second part of your question:

Why doesn't the same version work in C++? When I literally change the file extension from .c to .cpp and try to recompile, I get

The source of that problem is that C++ mangles names.

To avoid name mangling when running C++ and trying to access a C library.

near the top of the header file for the C library, after the multiple inclusion guard insert:

#ifdef __cplusplus
extern "C" {
#endif

and near the end of the header file, before the #endif of the multiple inclusion guard, insert:

#ifdef __cplusplus
}
#endif

That will eliminate the problem of functions in the assocated library file not being found

user3629249
  • 16,402
  • 1
  • 16
  • 17
  • For those that downvoted this answer, WHY did you downvote it? – user3629249 Feb 23 '18 at 18:13
  • I didnt downvote but it has more to do with VLAs rather than name mangling? – wbkang Feb 26 '18 at 23:30
  • 1
    the original text of the question (when seems to have been drastically edited) ask why a function in a C library could not be found when the calling function is from a C++ program. – user3629249 Feb 27 '18 at 06:35
  • Well that's very silly. I think this happens quite frequently actually. – wbkang Feb 27 '18 at 22:25
  • The original question refers to a compiler error not a linker error so I cannot see how name mangling is relevant to THIS question. And if all code is compiled as C++ (which is implied by the question - switching file type to .cpp) then name mangling still won't be an issue. – Steve Kidd Mar 03 '18 at 12:05