В C, зависит ли смысл A [i] [j] от того, как объявляется A?

Предположим, что у меня есть двумерный массив grid, объявленный как double grid[5][5]. Я понимаю, что справедливы следующие утверждения:

  • когда объявляется grid, смежный блок памяти выделяется для 5 * 5 удвоений, не более того,
  • когда к элементу массива обращается обозначение grid[i][j], этот фрагмент кода фактически интерпретируется как *(grid+(i*5+j)).

С другой стороны, я знаю, что я также могу сохранить ту же матрицу, что и массив указателей, написав что-то вроде:

double ** grid;
grid = (double**)malloc(sizeof(double*)*5);
for (i=0; i<5; i++)
   grid[i] = (double*)malloc(sizeof(double)*5);

и у меня есть код, который делает это. Проблема в том, что он затем переходит к элементам grid, как и раньше, с двойной надписью. Разве этот случай отличается? В этом случае grid[i][j] преобразуется в *(*(grid+i)+j), т.е. В двойное разделение? Это единственный способ увидеть, как это происходит правильно.

(Этот вопрос, вероятно, связан с моим (отсутствием) понимания взаимосвязи между типами указателей и массивов в C...)

EDIT:

Хорошо, посмотрим, получилось ли это прямо:

  • grid[i][j] всегда преобразуется в *(*(grid+i)+j);
  • это выражение действительно вычисляется по-разному в двух случаях, поскольку, как утверждает Джим в своем ответе, арифметика указателя учитывает размер указанного типа; тем не менее, правильный элемент извлекается в обоих случаях;
  • тогда и только тогда, когда "сетка" является двумерным массивом, это выражение дополнительно оптимизировано до *( (double*)grid + (i*5+j) ), что возможно, потому что компилятор знает, что любой grid[i] фактически является массивом, начинающимся с местоположения grid+i*5.

Но это оставляет мне неизбежный вывод: для 2D-массива, если я устанавливаю i=j=0, то у меня есть это **grid == *((double*)grid). Правильно ли это?

Ответ 1

когда элемент массива обращается к сетке обозначений [i] [j], этот фрагмент кода фактически интерпретируется как *(grid+(i*5+j)).

Нет. (Не знаю, почему так много ответов ответили "да" ). *(grid+(i*5+j)) - это то же самое, что и grid[i*5+j], что является ограниченным доступом для некоторых значений i и j. Кроме того, это указывает массив, а не int.

Следующие два выражения точно эквивалентны во всех случаях: A[i][j] *(*(grid+i)+j).

Вы никогда "ничего не добиваетесь", перейдя между двумя различными обозначениями разыменования. Это всего лишь две эквивалентные формы синтаксиса выражения lvalue, которое обозначает конкретный объект.

Для остальной части моего ответа я буду использовать синтаксис [ ], поскольку я нахожу его более ясным.

Возможно, вы хотели спросить что-то вроде "с int A[5][5];, тогда A[i][j] эквивалентно смещению i+5*j с начала A?"

Другие ответы немного путаются, потому что термин "смещение по N" неоднозначен. N байтов или N int или N массивов int?

Если вы представляете себе, что A были 1-мерным массивом длиной 25 (пусть называют это B), то A[i][j] обозначает тот же объект, что и B[i*5+j].

Чтобы выразить это в коде, вы должны написать: int *B = (int *)&A. Это псевдонимы 2-мерного массива int как 1-D массива.

NB. Было бы неправильно писать int *B = (int *)A, потому что A распадается на &A[0], который имеет только пять ints, поэтому B[6] по-прежнему является внешним доступом (поведение undefined). Если в вашем компиляторе вы не включили проверку границ, вероятно, вы ничего не заметите.

Ответ 2

x[i][j] всегда точно эквивалентен *(*(x+i)+j)... но вы должны иметь в виду, что арифметика указателя учитывает размер указанного типа. В

double ary[NROWS][NCOLS];

размер ary[i] (т.е. *(ary+i)) равен NCOLS*sizeof(double). В

double** ptr;

размер ptr[i] (т.е. *(ptr+i)) равен sizeof(double*).

В обоих случаях ary[i][j] или ptr[i][j] выбирается правильный элемент.

Ответ 3

Да. Вы идете правильно, но декларация

double grid[5][5]

и

double **grid; 

разные. Сначала объявляется 2D-массив из 25 элементов типа double, а второй объявляет указатель на указатель на тип double. Оба они разные. Обратите внимание, что массивы не являются указателями.

В первом случае выделение памяти находится в стеке, и оно непрерывное, и, следовательно, компилятор оптимизирует grid[i][j] до *(*(grid + i) + j), который далее оптимизируется до *(*grid + (i*5 + j)).

Во втором случае память выделяется в куче, а malloc не создает непрерывную память. В этом случае компилятор оптимизирует grid[i][j] до *(*(grid + i) + j), но не оптимизирует его до *(*grid + (i*5 + j)).

Ответ 4

Да, адрес A[i][j] относится к делает зависимым от того, как объявлено A.

Арифметика указателя, используемая для оценки array_variable[i][j], зависит от количества столбцов (во втором измерении): это потому, что все элементы для каждой строки (первое измерение) хранятся вместе, а количество столбцов влияет на то, данные для второго, третьего, ETC. строка сохраняется.

В этом URL-адресе указано, что если у вас есть двумерный массив, объявленный как:

int [NUMBER_OF_ROWS][NUMBER_OF_COLUMNS]

Тогда:

x = y[a][b]

эквивалентно:

x = *((int *)y + a * NUMBER_OF_COLUMNS + b);

URL: Как использовать выражения указателя для доступа к элементам двумерного массива в C?

В этом URL-адресе указано, что элементы (2-мерный массив) хранятся в строчном порядке: E.G. все элементы строки 1 сохраняются первыми.

http://www.cse.msu.edu/~cse251/lecture11.pdf

Этот URL-адрес имеет аналогичный расчет, в зависимости от размера второго измерения:

http://www.cs.umd.edu/class/sum2003/cmsc311/Notes/BitOp/pointer.html

addr( & arr[ row ][ col ] ) = addr( arr ) + [ sizeof( int ) * COLS * row ]
                                          + [ sizeof( int ) * col ]

Ответ 5

Дополнительно к другим ответам:

когда объявлена ​​сетка, смежный блок памяти выделяется для 5 * 5 удвоений, не более того,

Это не совсем верно (часть с меньшим будет всегда верна), но иногда вы обнаружите, что, хотя вы получаете доступ к памяти после окончания массива, программа работает (не segfault моментально). Это означает, что иногда malloc может выделять больше памяти, чем вы просите. Конечно, это не означает, что вам нужно получить доступ к этой чрезмерной памяти.

Ответ 6

Q В C, зависит ли смысл A [i] [j] от того, как объявлен A?

A Нет, это не так.

Если у вас есть,

int a[3][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9};

тогда a[i] оценивается как *(a+i).

Если у вас

int ** create2DArray(int d1, int d2)
{
   int i = 0;
   int ** a = malloc(sizeof(*a)*d1);
   for ( ; i != d1; ++i )
   {
      a[i] = malloc(sizeof(int)*d2);
   }
   return a;
}

int **a = create2DArray(3, 3);

то a[i] по-прежнему оценивается как *(a+i).

Независимо от того, как они объявлены, a[i][j] оценивается как *(a[i] + j).

Вы все равно можете вызвать функцию, например:

void func(int* p, int size)
{
   int i = 0;
   for ( ; i != size; ++i )
   {
      printf("%d ", p[i]);
   }
   printf("\n");
}

используя

func(a[i]);

независимо от того, как был определен a.

Основное различие между первым определением a и вторым определением a заключается в том, как выделяется память для него. В первом случае память выделяется в стеке, и она смежна. Во втором случае память выделяется в куче, и она, скорее всего, не соприкасается. Однако, как вы его используете и как компилятор относится к нему, он точно такой же.