C - почему &arr не равно arr и как мы получаем размер?
int arr[] = {1, 2, 3, 4, 5, 6};
int size = *(&arr + 1) - arr;
Помогите понять, каким образом у нас arr - это массив(указатель на первый элемент массива).
А вот &arr - это указатель на весь целочисленный массив из 6 элементов.
Что есть тогда указатель &arr на весь массив при условии, что в size мы получаем размер массива.
Как указатель &arr может указывать на весь массив?
Пример нашел тут, но понять как тут работает адресная арифметика пока не могу.
Ответы (3 шт):
arr имеет тип int [6] - массив из 6 элементов int, это не указатель на первый элемент. Соответственно &arr имеет тип int ( * ) [6] - указатель на массив из 6 элементов int (тот самый "указатель на весь целочисленный массив"). В выражении *(&arr + 1) - arr сначала при сложении происходит адресная арифметика с указателем на массив из 6 элементов int, при этом указатель рассматривается как указывающий на первый элемент массива, элементы которого имеют тип int [6], далее результат (&arr + 1) разыменовывается и получается int ( & ) [6] - ссылка на массив из 6 элементов int и наконец при вычитании происходит адресная арифметика с двумя массивами из 6 элементов - для этого создаются два временных объекта - указатели на первые элементы этих массивов.
Проверить, что есть что, можно используя std::is_same:
#include <type_traits>
int main()
{
int arr []{1, 2, 3, 4, 5, 6};
static_assert(::std::is_same_v<int [6] , decltype(arr )>);
static_assert(::std::is_same_v<int ( * ) [6], decltype(&arr )>);
static_assert(::std::is_same_v<int ( * ) [6], decltype(&arr + 1 )>);
static_assert(::std::is_same_v<int ( & ) [6], decltype(*(&arr + 1) )>);
static_assert(::std::is_same_v<int * , decltype(+arr )>);
static_assert(::std::is_same_v<int * , decltype(+*(&arr + 1))>);
return 0;
}
int arr[] = {1, 2, 3, 4, 5, 6};
int size = *(&arr + 1) - arr;
- Массив
arrимеет типint [6], т.е. массив из шести элементов типаint. - Выражение
&arrимеет типint (*)[6], т.е. указатель на (массив из шести элементов типаint). - В выражении
&arr + 1к указателю применяется арифметика указателей. Значение указателя наращивается наsizeof(int[6])байт, что равноsizeof(int) * 6байт. Получившийся указатель указывает на гипотетический массив из шести элементов типаint, следующий непосредственно за массивомarr. - В выражении
*(&arr + 1)происходит разыменование указателя. Т.е. мы получаем lvalue «ссылающееся» на гипотетический массив после массиваarrUB-1). Тип этого выражения —int[6]3). - В выражении
*(&arr + 1) - arrоба операнда бинарного оператора-имеют тип массива —int [6]. И к обоим операндам применяется неявное преобразование из типа массива к типу указателя на первый элемент массива.
Т.е гипотетический массив*(&arr + 1)после массиваarrтипаint [6]преобразуется к указателю типаint*на свой первый элемент.
Аналогично массивarrпреобразуется к указателю на свой первый элемент. - Разность двух указателей типа
int*даёт знаковое целочисленное значение типа std::ptrdiff_t, равное количеству объектов типаintмежду двумя указателямиUB-2).
- Разыменование указателя на гипотетический элемент после массива вызывает неопределённое поведение, так как такой указатель не является указателем на объект.
... Every value of pointer type is one of the following:
- a pointer to an object or function (the pointer is said to point to the object or function), or
- a pointer past the end of an object ([expr.add]), or
- the null pointer value for that type, or
- an invalid pointer value.
The unary
*operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is anlvaluereferring to the object or function to which the expression points.
Код
constexpr static int arr[] = {1, 2, 3, 4, 5, 6};
constexpr std::ptrdiff_t diff = *(&arr + 1) - arr;
может вызвать следующую ошибку компиляции:
//cannot access array element of pointer past the end of object
constexpr std::ptrdiff_t diff = *(&arr + 1) - arr;
^
Даже если бы было известно, что по адресу на который указывает указатель на гипотетический элемент после последнего расположен объект, то такой указатель по прежнему нельзя разыменовывать. Код
constexpr static int arr[2][2] = {{0, 1}, {2, 3}};
constexpr int val = arr[0][2];
может вызвать следующую ошибку компиляции:
//read of dereferenced one-past-the-end pointer is not allowed in a constant expression
constexpr int val = arr[0][2];
^
- Разность двух указателей, указывающих на элементы разных массивов, вызывает неопределённое поведение.
When two pointer expressions
PandQare subtracted, the type of the result is an implementation-defined signed integral type; this type shall be the same type that is defined asstd::ptrdiff_tin the<cstddef>header.
- If
PandQboth evaluate to null pointer values, the result is0.- Otherwise, if
PandQpoint to, respectively, array elementsiandjof the same array objectx, the expressionP - Qhas the valuei − j.- Otherwise, the behavior is undefined.
[Note 1: If the valuei − jis not in the range of representable values of typestd::ptrdiff_t, the behavior is undefined. — end note]
Код
constexpr static int arr[2][2] = {{0, 1}, {2, 3}};
constexpr const int* p00 = &arr[0][0];
constexpr const int* p10 = &arr[1][0];
constexpr std::ptrdiff_t diff = p10 - p00;
Может вызвать ошибку компиляции:
//subtracted pointers are not elements of the same array
constexpr std::ptrdiff_t diff = p10 - p00;
^
- Результат разыменования указателя на тип
T— это lvalue типаT(не ссылка).
The unary
*operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is an lvalue referring to the object or function to which the expression points. If the type of the expression is “pointer toT”, the type of the result is “T”.
Выражение наподобие std::is_same_v< int ( & ) [6], decltype(*(&arr + 1)) > возвращает true в силу особенностей работы спецификатора decltype, который для lvalue типа T в качестве типа выводит T&.
For an expression
E, the type denoted bydecltype(E)is defined as follows:
...
otherwise, ifEis an lvalue,decltype(E)isT&, whereTis the type ofE;
...
Итого: приведённый в вопросе код содержит неопределённое поведение. Результат его выполнения (если вообще скомпилируется) не предсказуем.
int main(int argc, char** argv) {
int arr[2][3][5] = {
{
{1,2,3,4,5},
{6,7,8,9,10},
{11,12,13,14,15}
},
{
{21,22,23,24,25},
{26,27,28,29,30},
{31,32,33,34,35}
}
};
// Указатель на 0 двумерный массив 3-го массива
int (*ptr0_2)[3][5] = &arr[0];
// Указатель на 0 одномерный массив 0 двумерного массива 3-го массива
int (*ptr0_0_2)[5] = &arr[0][0];
// Указатель на 0 элемент 0 одномерного массива 0 двумерного массива 3-го массива
int * ptr = (int*)ptr0_0_2;
for(int i = 0; i < 2; i++) {
ptr0_0_2 = (int(*)[5])(ptr0_2 + i);
for(int j = 0; j < 3; j++) {
ptr = (int*)ptr0_0_2 + j;
for(int k = 0; k < 5; k++) {
printf("%3d",*(ptr + k));
}
printf("\n");
}
printf("\n");
}
//OR
printf("\n\n\n");
for(int i = 0; i < 2; i++) {
for(int j = 0; j < 3; j++) {
for(int k = 0; k < 5; k++) {
printf("%3d",*(*(*(arr + i) + j) +k));
}
printf("\n");
}
printf("\n");
}
return 0;
}