Преобразование массива / указателя
Недостаток, который вы понимаете, связан с использованием массивов и указателей. В C массив представляет собой отдельный тип объекта. Одна из причин, которая вызывает путаницу, заключается в том, что массив преобразуется в указатель на его первый элемент при доступе. (преобразование массива / указателя) Это регулируется C11 Standard - 6.3.2.1 Другие операнды - L-значения, массивы и указатели функций (p3) (обратите внимание на 4 исключения, когда преобразование массива / указателя не происходит)
Ключ здесь type . Когда вы объявляете двумерный массив, например,
int arrSTACK[2][5] = {{100, 200, 300, 400, 500},{600,700,800,900,1000}};
При доступе он будет преобразован в указатель - но какого типа? 2D массив в C - это массив одномерных массивов. Преобразование массива / указателя применяется только к первому уровню косвенности. Так при доступе arrSTACK
преобразуется в указатель на массив int[5]
. Так что его тип int (*)[5]
. Так как тип управляет арифметика указателя c arrSTACK + 1
увеличивает пятизначные значения так, чтобы он указывал на начало второго 1D массива, который составляет arrSTACK
(вторая строка)
Указатели
int **arrHEAP
объявляет один указатель. A указатель на указатель на int
. Это не имеет ничего общего с массивом. Однако указатель на указатель может быть проиндексирован, как если бы вы индексировали двумерный массив для адресации отдельных целых чисел, хранящихся в памяти. Это единственное сходство между двумерным массивом и объектом, созданным путем выделения памяти для указателей, а затем выделения памяти для целых чисел и назначения начального адреса для каждого блока, содержащего целые числа, одному из выделенных вами указателей. Здесь нет гарантии, что все элементы arrHEAP
являются смежными в памяти, как и в двумерном массиве.
Итак, давайте посмотрим на разницу в том, как арифметика указателей c работает с arrHEAP
. Когда вы разыменовываете arrHEAP
, указатель на указатель (например, arrHEAP[0]
) Какой тип возникает в результате разыменования? Если у вас есть pointer-to-pointer-to int
и вы разыменовываете его, у вас остается pointer-to int
. Так, с массивом, разыменование привело к типу pointer-to int[5]
, но с arrHEAP[0]
результатом будет просто pointer-to int
(no * 1056) * - это просто указатель на int
). Так чем же отличается арифметика указателя c? arrSTACK + 1
увеличивает указатель на 5 * sizeof(int)
байт (20
-байт). С arrHEAP + 1
продвигается только к следующему указателю в выделенном вами блоке указателей (1-указатель 8
-байт).
Именно поэтому вы не можете передать одну функцию другой , Функция, ожидающая, что массив воспринимает arrSTACK[0]
и arrSTACK[1]
на расстоянии 20
-байт, в то время как с указателем arrHEAP[0]
и arrHEAP[1]
разнесены только 8
-байт. Это суть предупреждений о несовместимости указателей и ошибок, которые вы генерируете.
Тогда нет гарантии, что все значения arrSTACK
являются последовательными в памяти. Вы знаете, что arrSTACK[1]
всегда 20 байтов от начала массива. С arrHEAP
первый выделенный указатель не имеет гарантированных отношений с другим с точки зрения смежности. Позже они могут быть заменены или перераспределены.
Это означает, что если вы попытаетесь указать от arrSTACK
до function_C(int **arr)
, компилятор сгенерирует предупреждение для несовместимых типов указателей - потому что они есть. И наоборот, если вы попытаетесь указать от arrHEAP
до function_B(int size, int (*arr)[size])
, оно также выдаст предупреждение из-за несовместимых типов указателей - потому что они есть.
Даже если использование объекта и массива в другой функции может показаться так, как будто оно будет работать, потому что вы по существу индексируете оба одинаково, компилятор не может пропустить один несовместимый тип - это не работа компиляторов.
Компилятор может основывать свою работу только на обещании, которое вы дали ему при написании кода. Для function_B(int size, int (*arr)[size])
вы обещали, что отправляете двумерный массив одномерных массивов, содержащий 5 int
. С function_C(int **arr)
вы пообещали компилятору предоставить указатель на указатель на int
. Когда компилятор увидит, что вы пытаетесь передать неправильный объект в качестве параметра, он выдаст предупреждение, и вам следует учесть это предупреждение, поскольку начало 2-го блока целых чисел в arrHEAP
не гарантированно равно 6 int
от начала arrHEAP
- и там его не будет.