Как передать двумерный массив, созданный на стеке, в функцию, принимающую int**?

Рейтинг: 3Ответов: 2Опубликовано: 27.09.2014

Мне надо передать в функцию с прототипом:

void print(int **mt, int lines, int columns)

массив, созданный на стеке:

int tmp[3][3] = {
    {5, 1, 6},
    {3, 0, 4},
    {2, 0, 3}
};

Как это синтаксически верно записывается?

UPD:
Я отлично знаю, что массив можно создать разными способами. Но меня интересует строго:

  • print( int ** // обратить внимание, что две звездочки
  • int tmp[3][3] // обратить внимание, что объект на стеке

Ответы

▲ 8
  1. Вы не можете знать, где именно будет создан tmp, а где выделена память под его элементы. Компилятор может что-то писать в регистры, что-то в кучу, что-то в стек, гарантий нет.

    UPD: речь идет именно о том, что выражение int tmp[3][3] НЕ гарантирует, что память будет выделена на стеке.

  2. Вопреки ощущениям, N-мерные массивы, где N > 1 в C/C++ не эквивалентны указателю на указатель (N раз). Связано это с тем, что tmp[3][3] имеет тип int tmp[][3]/int (*tmp)[3] и реально в памяти лежит одним массивом, длиной в 9 int.

    Т.е. int[3][3] в памяти лежит вот так: int[3] int[3] int[3], а каждый из int[3] лежит в памяти как int int int

Как следствие, это просто разные типы данных. И приведение к int**, даже через reinterpret_cast приведет к падению, т.к. фактически там нет int** никуда.

Чтобы заработало, Вам следует изменить void print(int **mt, int lines, int columns) на void print(int mt[][3], int lines, int columns). Или заняться "колдовством" с typedef-ами, и в итоге все равно поменять интерфейс функции или вариант хранения данных из tmp.

Если же Вы уверены, что все должно быть так, как оно есть, то можно сделать костыль.

int* newtmp = reinterpret_cast<int*>(tmp);
void print(int* tmp, int rows, int cols)
{
    for (unsigned x =0; x < rows; ++x)
    {
       for (unsigned y =0; y < cols; ++y)
       {
          cout << tmp[cols*x + y];
       }
    }
}

Но это костыль, потому что закладываться на то, как в памяти лежит какой-то тип (а мы как бы знаем, что tmp[N][K] будет лежать одним массивом длиной N*K) и ковырять его прямыми обращениями к памяти - архитектурное зло, за которое выгоняют из Хогвардса :)

UPD: интерфейс функции print в любом случае не имеет никакого смысла при работе с объектами типа int tmp[X][Y]

▲ 3

@sys_dev, если Вы уверены, что функция с таким прототипом будет правильно работать с той матрицей, что Вы описали, то вызывать ее можно, например, так:

    print((int **)&tmp[0][0], 3, 3);

Код правильно работающей print() с таким (дурацким для данного случая) прототипом может быть, скажем, такой:

void 
print(int **mt, int lines, int columns) 
{
  int *a = (int *)mt, i, j;  // просто приведем тип к подходящему для доступа к последовательно расположенным в памяти элементам массива

  for (i = 0; i < lines; i++)
    for (j = 0; j < columns; j++)
      printf("%d%c", *a++, j == columns - 1 ? '\n' : ' ');
}

А что делать, если функция print все же работает с данными, на самом деле соответствующими своему прототипу?

Тогда функция ожидает матрицу, на самом деле сконструированную в виде массива указателей на строки. Так часто делают, когда создают "динамические" матрицы (т.е. массив заранее неизвестного размера в куче).

Обычно код, создающий такую матрицу, это что-то вроде

int **create_matrix (int n_lines, int n_cols) {
    int **res = (int **)malloc(sizeof(*res) * n_lines * n_cols);
    if (res) {
       int i;
       for (i = 0; i < n_lines; i++)
          if (!(res[i] = (int *)malloc(sizeof(**res) * n_cols)))
             return 0;
    }
    return res;
}

И код функции print() будет, например, такой:

void print(int **mt, int lines, int columns) {
  for (int i = 0; i < lines; i++)
    for (int j = 0; j < columns; j++)
      printf("%d%c", mt[i][j], j == columns - 1 ? '\n' : ' ');
}

Тогда в вызывающей функции надо просто сделать массив-"переходник" (адаптер), из указателей на строки tmp и передать в print его.

  int *pt[3] = {&tmp[0][0], &tmp[1][0], &tmp[2][0]};
  print(pt, 3, 3);

Естественно, этот массив можно заполнять и динамически (в цикле)

  for (i = 0; i < 3; i++)
    pt[i] = &tmp[i][0];

UPDATE

И вот так еще можно писать (по крайней мере в gcc 4.8.2).

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

void print(int columns, int a[][columns], int lines) {
  int i, j;
  for (i = 0; i < lines; i++) {
    for (j = 0; j < columns; j++)
      printf("%d ", a[i][j]);
    puts("");
  }
}

int
main (int ac, char *av[])
{
  int t[3][4] = {{1,2,3,4}, {5,6,7,8}, {21,22,23,24}};
  print(4, t, 3);

  return 0;
}

Обратите внимание, на описание параметра int a[][columns] в списке аргументов print() и динамический (по текущему значению тоже параметра int columns) его размер!

Вот при такой записи у компилятора появляется достаточно информации для вычисления адресов a[i][j].