Есть некоторые недоразумения.Чтобы объяснить их, потребуется время.
Массивы фиксированной ширины
Ваш пример прототипа (более или менее):
//Prototype
ReturnType function_name(ArrayType array_name[][MAX_COLS], int rows, int cols);
Где-то у вас есть постоянное определение для MAX_COLS
;в качестве аргумента это может быть:
enum { MAX_COLS = 64 };
Эта функция принимает только массивы со столбцами MAX_COLS
, хотя в массивах может быть любое количество строк.Аргумент int cols
не имеет значения для определения этого массива.В вызывающем коде массив должен быть определен с MAX_COLS
(или его эквивалентом) в качестве второго измерения.
Внутри функции компилятор будет интерпретировать ссылку array_name[i][j]
(i
и * 1021).* являющиеся целочисленными типами) как:
*(array_name + (i * MAX_COLS + j))
Он никогда не будет использовать cols
в этом расчете.Попытка передать массив с другой шириной строки в функцию приводит к - я собираюсь назвать это «неопределенным поведением», хотя может быть строго другая интерпретация, для которой применимо нежелательное и не полностью определенное поведение.Результат не будет тем, что вы ожидаете.Если вы передадите более узкий массив, вы вполне можете индексировать его за пределы фактического массива;если вы передадите более широкий массив, вы, вероятно, не выйдете за пределы, но (как и при доступе за пределами) вы не будете получать доступ к элементам, к которым ожидаете получить доступ, потому что вычисления будут основаны на MAX_COLS
,не аргумент функции cols
.
Массивы переменной длины
Используя массив переменной длины (VLA), вы можете изменить свою функцию на:
ReturnType function_name(int rows, int cols, ArrayType array_name[rows][cols]);
Обратите внимание, чтопеременные размера должны быть определены до их использования;параметры размера должны предшествовать их использованию в определении массива.
Теперь внутри функции компилятор будет интерпретировать array_name[i][j]
(как прежде) как:
*(array_name + (i * cols + j))
Вы можете передать любойформа массива, если вы получаете правильные параметры rows
и cols
.
Существует множество других обозначений, которые можно использовать в прототипе для функции, принимающей VLA, включая следующие:
ReturnType function_name(int rows, int cols, ArrayType array_name[*][*]);
ReturnType function_name(int rows, int cols, ArrayType array_name[][*]);
Тем не менее, определение функции должно иметь cols
появиться перед определением массива:
ReturnType function_name(int rows, int cols, ArrayType array_name[rows][cols]) { … }
ReturnType function_name(int rows, int cols, ArrayType array_name[][cols]) { … }
И с «нестандартным» начальным измерением вы могли бы (если бы вы былидостаточно извращенно) write:
ReturnType function_name(int cols, ArrayType array_name[][*], int rows);
ReturnType function_name(int cols, ArrayType array_name[][cols], int rows) { … }
Мое собственное мнение состоит в том, что проще поддерживать код, если объявление прототипа функции точно соответствует строке определения функции, поэтому я не буду использовать нотации с *
в прототипах.Я бы также использовал явное обозначение ArrayType array_name[rows][cols]
, указывающее, какой параметр определяет каждый размер.Это имеет значение, если вы выполняете функцию умножения матриц, например:
void MatrixMultiply(int r1, int c1, int c2,
Data m1[r1][c1], Data m2[c1][c2], Data result[r1][c2]);
Вопрос о том, будет ли компилятор сообщать о проблемах с несоответствиями размеров матриц, открыт для обсуждения, но такая возможность есть.Если вы предпочитаете, вы можете иметь избыточный параметр r2
, чтобы указать размер строки матрицы m2
, но тогда у вас возникнут проблемы, если r2 != c1
, и компилятору будет труднее помочь - высводится к утверждениям или другим механизмам сообщения об ошибках, чтобы указать, что с результатом ничего не было сделано, потому что размеры матриц не были совместимы для умножения.
Я не уверен, что я разобрал все недоразумения в вашем вопросе;Я подозреваю, что нет.
Массивы указателей против 2D-массивов
Из комментария :
Если сигнатура моей функции void modify(int rows, int cols, int *a[])
, токак мне получить доступ к a[i][j]
и &a[i][j]
в вызываемой функции?
Для этого требуется массив указателей на int
, поэтому вы должны установить массив иначе, чем прежде (ввызывающий код).Однако внутри функции вы просто пишете:
int x = a[i][j];
int *y = &a[i][j];
int *z = a[i] + j;
Выражения для y
и z
эквивалентны;Вероятно, я бы использовал первое.
Обратите внимание, что вычисление «за кадром» для x
здесь отличается от формулы, используемой для a[i][j]
при передаче массива:
int x = *(*(a + i) + j);
Имеется две ссылки на память: первая для чтения указателя a[i]
, а вторая для чтения a[i][j]
(с двумя явными добавлениями, но без явного умножения, хотя добавления нижнего индекса должны быть масштабированы до размера int *
и int
), тогда как в нотации массива была только одна ссылка на память (с двумя явными добавлениями и одним явным умножением - и только одна операция масштабирования размером int
).
В том же комментарии также сказано:
Я попробовал комбинацию a[i]+j
и (a[i]+j)
, а также *(a+(i*cols)+j)
и (a+(i*cols)+j)
, но ни одна из них не работает.
Есть большая вероятность, что мне не удалось добавить символы *
в нужных местах, особенно в части предложения "а также". Пожалуйста, предоставьте правильную запись в новом дополнительном (или заменяющем) комментарии, и я могу разбирать то, что вы на самом деле напечатали, а не то, что, как я предполагаю, вы напечатали.
Значение a[i] + j
должно дать вам указатель на целое число j th в строке i th данных. Вы должны были бы обернуть это как *(a[i] + j)
, чтобы получить значение int
. Версия в скобках также дает указатель на элемент a[i][j]
; вам нужно *
впереди, чтобы получить значение.
Варианты типа *(a+(i*cols)+j)
неправильны, потому что в этом контексте вам не нужно умножать количество столбцов. Нет необходимости, чтобы последовательные строки, на которые указывают a[i]
и a[i+1]
, были непрерывными в памяти; они могут быть расположены в нескольких непересекающихся блоках памяти (и фактически может быть несколько указателей на один блок памяти). Конечно, внутри ряда элементы должны быть смежными.
Это примерно разница между int a[][4]
:
+---+---+---+---+
| 2 | 3 | 5 | 7 |
+---+---+---+---+
| 1 | 2 | 3 | 4 |
+---+---+---+---+
| 7 | 5 | 3 | 2 |
+---+---+---+---+
| 9 | 8 | 7 | 6 |
+---+---+---+---+
и int *a[]
:
+------------+ +---+---+---+---+
| 0x010C0304 |------>| 2 | 3 | 5 | 7 |
+------------+ +---+---+---+---+
| 0x020D0408 |------>| 1 | 2 | 3 | 4 |
+------------+ +---+---+---+---+
| 0x030E050C |------>| 7 | 5 | 3 | 2 |
+------------+ +---+---+---+---+
| 0x040F0600 |------>| 9 | 8 | 7 | 6 |
+------------+ +---+---+---+---+
Обратите внимание, что для хранения данных int *a[]
требуется больше памяти, чем int a[][4]
.
Также обратите внимание, что я сделал 4 массива чисел в int *a[]
несмежными. Кроме того, строки в примере int *a[]
могут иметь различную длину, при условии, что вы знаете, как получить доступ к длине (или все длины по крайней мере 4, и вы не выходите за пределы четвертого элемента).
Адаптация MCVE из вопроса
Как я уже говорил в предыдущем разделе, когда вы используете нотацию «массив указателей», использование cols
в расчете нижнего индекса означает неправильно . Вот адаптация вашего кода - мне пришлось удалить специфичные для Windows функции, такие как _getch()
и scanf_s()
(используя вместо этого «ничто» и scanf()
). Я также показал некоторые альтернативы; печать показывает некоторые альтернативы.
#include <stdio.h>
#include <stdlib.h>
#define MAX_ROWS 20
extern void modify(int rows, int cols, int *a[]);
extern void input_2d_array(int rows, int cols, int *a[]);
extern void output_2d_array(int rows, int cols, int *a[]);
int main(void)
{
int rows = 4, cols = 3;
int *a[MAX_ROWS], i;
for (i = 0; i < rows; i++)
a[i] = (int *)malloc(cols * sizeof(int));
input_2d_array(rows, cols, a);
printf("Initially in main array =\n");
output_2d_array(rows, cols, a);
modify(rows, cols, a);
printf("Finally in main array =\n");
output_2d_array(rows, cols, a);
//_getch();
return 0;
}
void input_2d_array(int rows, int cols, int *a[])
{
int i, j;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
printf("Please enter the elements for row %d and column %d of a\n", i + 1, j + 1);
//scanf_s(" %d", (a + (i * cols) + j));
//scanf(" %d", (a + (i * cols) + j));
scanf(" %d", &a[i][j]);
//scanf(" %d", a[i] + j);
//scanf(" %d", *(a + i) + j);
}
}
}
void output_2d_array(int rows, int cols, int *a[])
{
int i, j;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
//printf(" %d", *(a + (i * cols) + j));
printf(" %d", a[i][j]);
printf(" (%d)", *(*(a + i) + j));
}
printf("\n");
}
}
void modify(int rows, int cols, int *a[])
{
int i, j;
printf("Initally in modify array =\n");
output_2d_array(rows, cols, a);
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
a[i][j] = a[i][j] + 2;
*(a[i] + j) = *(*(a + i) + j) + 2;
//*(a + (i * cols) + j) = (*(a + (i * cols) + j)) + 2;
// *(*(a + i) + j) += 2; // Gives exception
// *(a[i]+j) += 10; // Gives exception
}
}
printf("Finally in modify array =\n");
output_2d_array(rows, cols, a);
}
Пример выполнения (программа mda17
):
$ mda17
Please enter the elements for row 1 and column 1 of a
23
Please enter the elements for row 1 and column 2 of a
24
Please enter the elements for row 1 and column 3 of a
25
Please enter the elements for row 2 and column 1 of a
26
Please enter the elements for row 2 and column 2 of a
27
Please enter the elements for row 2 and column 3 of a
28
Please enter the elements for row 3 and column 1 of a
99
Please enter the elements for row 3 and column 2 of a
98
Please enter the elements for row 3 and column 3 of a
97
Please enter the elements for row 4 and column 1 of a
96
Please enter the elements for row 4 and column 2 of a
95
Please enter the elements for row 4 and column 3 of a
94
Initially in main array =
23 (23) 24 (24) 25 (25)
26 (26) 27 (27) 28 (28)
99 (99) 98 (98) 97 (97)
96 (96) 95 (95) 94 (94)
Initally in modify array =
23 (23) 24 (24) 25 (25)
26 (26) 27 (27) 28 (28)
99 (99) 98 (98) 97 (97)
96 (96) 95 (95) 94 (94)
Finally in modify array =
27 (27) 28 (28) 29 (29)
30 (30) 31 (31) 32 (32)
103 (103) 102 (102) 101 (101)
100 (100) 99 (99) 98 (98)
Finally in main array =
27 (27) 28 (28) 29 (29)
30 (30) 31 (31) 32 (32)
103 (103) 102 (102) 101 (101)
100 (100) 99 (99) 98 (98)
$