Передача массива функции (и почему она не работает в C++)

Я натолкнулся на некоторый код на C, который компилируется, но я не понимаю, почему. В частности, у меня есть библиотека C, в которой много кода, использующего этот формат:

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 );
    ...
}

Я не понимаю, почему компилятор позволяет определять размеры в массивах. Насколько я понимаю, размеры должны быть фиксированными (например, xu_col[9][7]) или неопределенными (например, xu_col[][]). В приведенном выше коде показано, что размеры не являются константами времени компиляции.

Компилятор просто игнорирует аргументы здесь? или действительно ли он выполняет проверку времени измерения по размеру?

Если это последний, то кажется, что он подвержен ошибкам, чтобы передавать размеры отдельно.

Вторая часть вопроса:

Почему в C++ не работает такая же версия? Когда я буквально изменяю расширение файла с .c на .cpp и пытаюсь перекомпилировать, я получаю

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]);

Я хотел бы знать, какую идиому я должен использовать для преобразования этого кода в C++, поскольку, по-видимому, предыдущая идиома была чем-то, что работает на C, но не C++.

Ответ 1

В C можно использовать параметры функции для определения размера параметра массива переменной длины, если размер идет до массива в списке параметров. Это не поддерживается в C++.

Ответ 2

Причина, по которой он работает в C, но не в C++, просто потому, что это код C, а не C++. Эти два языка имеют историю, а не грамматику.

Метод C++ для передачи массивов переменного размера - std::vector, возможно, по ссылке, если вы намерены модифицировать вектор в функции или по ссылке const если вы этого не сделаете.

Ответ 3

Я не понимаю, почему компилятор позволяет определять размеры в массивах. Насколько я понимаю, размеры должны быть фиксированными (например, xu_col [9] [7]) или неопределенными (например, xu_col [] []). В приведенном выше коде показано, что размеры не являются константами времени компиляции.

Вы правы, размеры не являются константами времени компиляции. Если у вас есть двумерный массив, x [строка] [col], компилятору требуется количество элементов в строке для вычисления адреса элемента. Посмотрите пример кода get_char_2() и get_char_3().

Если вы используете переменные длины массивов (VLA) в качестве функциональных параметров, вам необходимо предоставить их (см. Пример get_char_1). ты можешь написать:

 my_func( x[][width] )

или вы можете написать

 my_func( x[999][width] )

Компилятор просто игнорирует аргументы здесь? или действительно ли выполняется проверка времени на компиляцию по размерам?

Первый номер (999) будет проигнорирован компилятором. Необходимо второе. Без размера строки компилятор не может вычислять адреса внутри этих 2D-массивов. Компилятор не выполняет проверки времени выполнения или времени компиляции для VLA в 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;
}

Ответ 4

Все, что он делает (в C), позволяет вам писать код индексирования в вызываемом funcion, не выполняя вычисления адреса самостоятельно, например:

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

против

double d= xu_col[i][j];

Ответ 5

Когда параметр объявлен как одномерный тип массива, C игнорирует заданный размер и вместо этого обрабатывает параметр как указатель на тип элемента. Для вложенных (многомерных) массивов такая обработка применяется только к внешнему массиву. В C89 внутренние размеры должны были иметь фиксированные размеры, но в C99 размеры могут быть выражениями. Если параметры, необходимые для вычисления размера массива, не перечисляются до тех пор, пока после массива, для объявления функции необходимо будет использовать любопытную смесь старого и нового синтаксиса, например

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;
}

Обратите внимание, что размеры массива указаны как * в прототипе функции и что определение функции не указывает типы в списке аргументов, а вместо этого описывает все типы параметров между списком аргументов и открывающей скобкой. Также обратите внимание, что, хотя компилятор, вероятно, игнорирует количество строк в объявлении массива, но интеллектуальный компилятор может использовать его для облегчения оптимизации. Фактически, странный "статический" синтаксис приглашает компилятор читать любые части массива, вплоть до заданного размера, по мере его соответствия, независимо от того, считываются ли эти значения кодом. Это может быть полезно на некоторых платформах, где код может извлечь выгоду из обработки нескольких элементов массива сразу.

Ответ 6

Трудность с вашим примером кода заключается в том, что один из параметров функции прототипирован, double xu_col[n_col][n_x + n_u], где n_x и n_u являются переменными, а не константами. Если вы просто передадите это как double[], некоторые компиляторы C++ могут разрешить создание, такое как double (&table)[n_col][n_x + n_u] = (double(&)[n_col][n_x + n_u])xu_col; для работы в качестве нестандартного расширения, но переносным подходом было бы писать обращения, такие как xu_col[i*(n_x+n_u) + j], которые вы могли бы упростить с помощью вспомогательной функции, если это слишком уродливо.

Альтернативный подход, возможно, более соответствующий духу STL, может заключаться в том, чтобы написать минимальный контейнерный класс, который знает его размеры, и сохраняет элементы в линейном массиве для повышения эффективности. Затем вы можете объявить redim_array<double> table = redim_array<double>(xu_col, n_col*(n_x+n_u)).redim(n_col, n_x+n_u); и table(i,j) доступа table(i,j).

Некоторые из других ответов описывали синтаксис массивов переменной длины, но еще один аспект вашего вопроса заключается в том, как его законно неявно преобразовывать двумерный массив прямоугольных чисел в одномерный массив.

Случается, что прямоугольный массив выложен как последовательные элементы в памяти, поэтому он может выродиться до указателя на элементы, а затем параметр функции может интерпретировать это как массив с другой геометрией.

Heres небольшая небольшая программа, которая демонстрирует это поведение.

#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;
}

Theres некоторые языковые адвокаты в комментариях ниже² о том, является ли передача аргументов массива без явного приведения технически законным стандартом. Ive выбрал, чтобы отнести это к сноске и просто удалить пример без приведения. В реальном мире вы иногда видите код без приведения указателя к массиву различной геометрии, и он может генерировать предупреждение. Макет памяти двух массивов требуется, чтобы стандарт был таким же.

Чтобы преобразовать в C++, вы можете использовать трюк преобразования указателя, или теперь вы можете немного поиграть в кодовое слово, используя ссылки.

Ниже приведен перевод C++ программы выше. Это требует, чтобы все параметры, кроме первого, передавались в constexpr, но некоторые компиляторы поддерживают массивы переменной длины C99 как расширение.

#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 иногда называют либо массивы, как int matrix[ROWS][COLS] либо массивы типа char** argv "двумерные массивы". Здесь я называю прежний прямоугольник, а последний оборван.

² Ограничение на аргументы функции в стандарте C11: "Каждый аргумент должен иметь такой тип, чтобы его значение могло быть присвоено объекту с неквалифицированной версией типа его соответствующего параметра. Кроме того, "объявление параметра как" массив типа "должно быть скорректировано на" квалифицированный указатель на тип ", и, если это применяется рекурсивно, многомерный массив некоторого типа будет скорректирован на плоский указатель этого типа,

Ответ 7

относительно второй части вашего вопроса:

Почему в C++ не работает такая же версия? Когда я буквально изменяю расширение файла с.c на.cpp и пытаюсь перекомпилировать, я получаю

Источником этой проблемы является то, что C++ управляет именами.

Чтобы избежать манипуляции с именем при запуске C++ и попытке доступа к библиотеке C.

рядом с верхней частью файла заголовка для библиотеки C, после вставки защиты множественного включения:

#ifdef __cplusplus
extern "C" {
#endif

и ближе к концу файла заголовка, перед #endif защиты множественного включения, вставьте:

#ifdef __cplusplus
}
#endif

Это устранит проблему с функциями, которые не найдены в файле связанной библиотеки