Массив не имеет присвоенных значений, но при вычитании значений в массиве значение z становится равным 20.
Кто-нибудь может объяснить эту концепцию?
Любая помощь будет оценена по достоинству.
#include <stdio.h>
int main()
{
int a[10][20][30][40];
int z = a[6] - a[5];
printf("%d\n", z); // z value is 20. why?
}
Но вы не вычитаете значения.
Массив не инициализирован и может быть заполнен чем угодно. Так уж получилось, что a[6] - a[5] = 20
, но это абсолютно не гарантия.
@ user253751 Прошу не согласиться. У него 20 из-за того, как реализованы зубчатые массивы. Первый размер a
будет содержать указатели на второй размер, который содержит указатели на третий размер, который содержит указатели на четвертый размер, содержащий фактические данные.
Это вычитание указателя, а не вычитание значения.
Я использовал локальный компилятор vsCode C и получил значение 20 для 'z'. Я также попробовал онлайн-компилятор C, который также вернул 20.
@ norok2 многомерные массивы в C работают не так.
@норок2. Пример кода не демонстрирует массивы с зубчатыми краями. Внутри не хранятся указатели.
Это поведение кода прекрасно определено. a[6]
и a[5]
сами по себе являются массивами, поэтому их вычитание является предметом арифметики указателей.
@ norok2 в этом коде нет зубчатого массива. Это настоящие нативные массивы массивов (фактически, массивов массивов). Это базовая, но не очень тонкая арифметика указателей.
извините, я имел в виду Iliffe vectors, я просто использую их только для зубчатых массивов, но вы правы, что это не так.
@ЕвгенийШ. это хорошо определено, хотя? это гарантировано &a[6][0] == &a[5][20]
?
@YakovGalka Ну, я думаю, все сводится к тому, гарантировано ли это sizeof( type a[5*20] ) == sizeof (type a[5][20])
. Вы заставили меня сомневаться, хотя
@YakovGalka Да, &a[6][0] == &a[5][20]
всегда верно с int a[10][20][30][40];
.
@Creek Попробуйте изменить int a[10][20][30][40];
на int a[10][7][30][40];
. Выход теперь 7?
@chux-ReinstateMonica, что в стандарте подразумевается? что sizeof(T[n]) == sizeof(T)*n
?
@YakovGalka Это несколько запутано. Опубликуйте как вопрос SO, если хотите, но они могут быть дубликатами.
@YakovGalka Обратите внимание, что intptr_t
— это распространенный, но необязательный тип. Так что (intptr_t)(p+i) - (intptr_t)p == sizeof(*p)*i не гарантируется полностью. Кроме того, такая математика, как -
, имеет смысл, только если предположить, что преобразование указателя в intptr_t
приводит к линейной модели памяти. Это точно не гарантировано.
Потому что это арифметика указателей.
a[6]
— это 20
элементы типа int[30][40]
из a[5]
.
Это происходит потому, что массивы распадаются на указатели
Должен признаться, что я немного не уверен в том, разрешено ли это вычитание. Несмотря на то, что очевидно, что &a[6][0]
и &a[5][20]
являются одним и тем же значением, делает ли это &a[6][0] - &a[5][0]
таким же допустимым, как &a[5][20] - &a[5][0]
?
@IanAbbott Это вычитание законно. &a[5][20]
является незаконным, и любое выражение, использующее его, вызывает UB
Но делает ли это также законным a[6] - a[4]
(то есть &a[6][0] - &a[4][0]
)? Я думаю, что мы здесь на шаткой почве.
@IanAbbott Я с вами здесь, что это совсем не очевидно; но я думаю, что это следует из sizeof(T[n]) == sizeof(T)*n
и (intptr_t)(p+i) - (intptr_t)p == sizeof(*p)*i
, которые я считаю гарантированными.
@IanAbbott нет, все в порядке
&a[5][20]
не является незаконным. Это тот же указатель, что и a[5] + 20
(или &a[5][0] + 20
).
Нет, второй индекс находится вне диапазона. Это UB, даже если он находится внутри того же массива.
Вы говорите, что &a[5][20]
незаконно, но a[5] + 20
и &a[5][0] + 20
в порядке?
@0___________, (@Ian Abbot) Зачем утверждать, что &a[5][20]
— это плохо? Индекс 20 находится за концом массива, что является допустимым исключением для вычислений массива. Формирование адреса массива в порядке. printf("%p\n", (void *) &a[5][20]);
в порядке.
@YakovGalka У меня нет таких гарантий для intptr_t
, но, возможно, замена intptr_t
на char *
допустима (то есть (char *)(p+i) - (char *)p == sizeof(*p)*i
), потому что любой объект можно рассматривать как массив char
, а два указателя char
указывают на байты внутри объекта a
в этом случае.
Массив не имеет назначенных значений, но при вычитании значений в массиве значение z становится равным 20.
Вы не вычитаете какие-либо значения в массиве. Во всяком случае, не int
значения.
Эта декларация...
int a[10][20][30][40];
... говорит, что a
представляет собой массив из 10 (массивы из 20 (массивы из 30 (массивы из 40 int
)))).
Следовательно, a[5]
и a[6]
представляют собой массив из 20 (массивы из 30 (массивы из 40 int
)).
Когда выражения с массивом появляются в качестве операндов в большинстве типов выражений, они автоматически преобразуются в указатели на их первые элементы, поэтому a[6] - a[5]
эквивалентно &a[6][0] - &a[5][0]
.
Различия указателей вычисляются в единицах размера указанного типа, поэтому для этой цели на самом деле не имеет значения, какой тип a[5][0]
и a[6][0]
, или какие значения int
хранятся в a[i][j][k][l]
. Значение разницы полностью определяется вторым измерением a
, которое равно 20.
z значение равно 20. почему?
Учитывая int a[10][20][30][40];
, a[6]
указывает на массив, массив из 20 элементов.
При использовании в вычитании, например a[6] - a[5]
, массивы преобразуются в адрес типа их первых элементов. Так это как &a[6][0] - &a[5][0]
. &a[6][0]
— это указатель. Тип указателя, который они разделяют, здесь не так важен.
Вычитание указателя возвращает разницу в элементах в виде целого числа типа ptrdiff_t
. Между &a[6][0]
и &a[5][0]
20 элементов.
Когда два указателя вычитаются, оба должны указывать на элементы одного и того же объекта массива или один после последнего элемента объекта массива; результатом является разница индексов двух элементов массива. Размер результата определяется реализацией, а его тип (целочисленный тип со знаком) равен
ptrdiff_t
... C17dr § 6.5.6 9
Затем код присваивает 20 (типа ptrdiff_t
) z
, int
.
Я считаю, что это технически неопределенное поведение. По крайней мере, на C++, возможно, и на C.
На практике он почти наверняка напечатает 20, как объясняют другие ответы, но нарушает C11 6.5.6 Additive operators / 9
(и в C++: [expr.add]/4.2):
Когда два указателя вычитаются, оба должны указывать на элементы одного и того же объекта массива, или один за последним элементом объекта массива...
Ваши указатели указывают на два разных массива: на первые элементы a[6]
и a[5]
соответственно.
Можно возразить, что первый элемент a[6]
находится после последнего элемента a[5]
, но, по крайней мере, в C++ это не так, даже если они имеют одинаковое значение: общеизвестно, как был получен указатель (вот почему вещи например std::launder существуют).
Не-UB способ вычисления того же самого:
int z = ((uintptr_t)a[6] - (uintptr_t)a[5]) / sizeof(a[0][0]);
Технически это определяется реализацией (из-за преобразования указателя в uintptr_t
), но должно иметь еще меньшую вероятность взлома.
Ваше замечание о том, что «указатели указывают на два разных массива:», интересно, хотя это разные массивы в более крупном общем массиве, хммм.
@chux-ReinstateMonica Ммм, но для того, чтобы общий массив имел значение, -
должна быть специальная формулировка для многомерных массивов, а ее нет.
Хорошее исправление, еще не согласовано с еще меньшей вероятностью взлома. «потребуется специальная формулировка для многомерных массивов» -> существуют и другие возможности. Я подозреваю, что глубоко в недрах спецификации C тот факт, что массивы a[6]
и a[5]
сами по себе являются частью большего общего массива, делает a[6] - a[5]
хорошо определенным. Просто слишком глубоко для меня сегодня.
Будет ли int z = ((char *)a[6] - (char *)a[5]) / sizeof(a[0][0]);
действительным? a[6]
и a[5]
— это разные объекты массива, но оба являются частью одного и того же более крупного объекта, так что это может быть нормально. Если он действителен, он избегает определяемой реализацией природы преобразования указателя в uintptr_t
.
@IanAbbott Это спорно. Я думаю, что это лучше, чем то, что делает OP, и, вероятно, я бы так и сделал, но это все еще может быть неопределенным. std::launder есть интересная формулировка для «достижимости через указатель», но я не помню, уточняется ли это где-либо еще в стандарте. Не должно создавать никаких проблем в реальном мире.
@HolyBlackCat Я удалил замечание sizeof(int) == 4
, поскольку оно не имеет отношения к результату вычитания указателя.
почему не 20? в массиве могут быть любые старые случайные числа, потому что вы не сказали, что вам нужны конкретные числа