Как это вычитание массива работает без присваивания?

Массив не имеет присвоенных значений, но при вычитании значений в массиве значение 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?
}

почему не 20? в массиве могут быть любые старые случайные числа, потому что вы не сказали, что вам нужны конкретные числа

user253751 16.02.2023 15:56

Но вы не вычитаете значения.

user253751 16.02.2023 15:56

Массив не инициализирован и может быть заполнен чем угодно. Так уж получилось, что a[6] - a[5] = 20, но это абсолютно не гарантия.

B Remmelzwaal 16.02.2023 15:57

@ user253751 Прошу не согласиться. У него 20 из-за того, как реализованы зубчатые массивы. Первый размер a будет содержать указатели на второй размер, который содержит указатели на третий размер, который содержит указатели на четвертый размер, содержащий фактические данные.

norok2 16.02.2023 15:59

Это вычитание указателя, а не вычитание значения.

Ian Abbott 16.02.2023 16:00

Я использовал локальный компилятор vsCode C и получил значение 20 для 'z'. Я также попробовал онлайн-компилятор C, который также вернул 20.

Creek 16.02.2023 16:00

@ norok2 многомерные массивы в C работают не так.

Yakov Galka 16.02.2023 16:00

@норок2. Пример кода не демонстрирует массивы с зубчатыми краями. Внутри не хранятся указатели.

John Bollinger 16.02.2023 16:01

Это поведение кода прекрасно определено. a[6] и a[5] сами по себе являются массивами, поэтому их вычитание является предметом арифметики указателей.

Eugene Sh. 16.02.2023 16:01

@ norok2 в этом коде нет зубчатого массива. Это настоящие нативные массивы массивов (фактически, массивов массивов). Это базовая, но не очень тонкая арифметика указателей.

WhozCraig 16.02.2023 16:02

извините, я имел в виду Iliffe vectors, я просто использую их только для зубчатых массивов, но вы правы, что это не так.

norok2 16.02.2023 16:07

@ЕвгенийШ. это хорошо определено, хотя? это гарантировано &a[6][0] == &a[5][20]?

Yakov Galka 16.02.2023 16:17

@YakovGalka Ну, я думаю, все сводится к тому, гарантировано ли это sizeof( type a[5*20] ) == sizeof (type a[5][20]). Вы заставили меня сомневаться, хотя

Eugene Sh. 16.02.2023 16:23

@YakovGalka Да, &a[6][0] == &a[5][20] всегда верно с int a[10][20][30][40];.

chux - Reinstate Monica 16.02.2023 16:45

@Creek Попробуйте изменить int a[10][20][30][40]; на int a[10][7][30][40];. Выход теперь 7?

chux - Reinstate Monica 16.02.2023 16:46

@chux-ReinstateMonica, что в стандарте подразумевается? что sizeof(T[n]) == sizeof(T)*n?

Yakov Galka 16.02.2023 16:56

@YakovGalka Это несколько запутано. Опубликуйте как вопрос SO, если хотите, но они могут быть дубликатами.

chux - Reinstate Monica 16.02.2023 17:01

@YakovGalka Обратите внимание, что intptr_t — это распространенный, но необязательный тип. Так что (intptr_t)(p+i) - (intptr_t)p == sizeof(*p)*i не гарантируется полностью. Кроме того, такая математика, как -, имеет смысл, только если предположить, что преобразование указателя в intptr_t приводит к линейной модели памяти. Это точно не гарантировано.

chux - Reinstate Monica 16.02.2023 17:05
Структурированный массив Numpy
Структурированный массив Numpy
Однако в реальных проектах я чаще всего имею дело со списками, состоящими из нескольких типов данных. Как мы можем использовать массивы numpy, чтобы...
T - 1Bits: Генерация последовательного массива
T - 1Bits: Генерация последовательного массива
По мере того, как мы пишем все больше кода, мы привыкаем к определенным способам действий. То тут, то там мы находим код, который заставляет нас...
Что такое деструктуризация массива в JavaScript?
Что такое деструктуризация массива в JavaScript?
Деструктуризация позволяет распаковывать значения из массивов и добавлять их в отдельные переменные.
1
18
119
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Потому что это арифметика указателей.

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]?

Ian Abbott 16.02.2023 16:14

@IanAbbott Это вычитание законно. &a[5][20] является незаконным, и любое выражение, использующее его, вызывает UB

0___________ 16.02.2023 16:16

Но делает ли это также законным a[6] - a[4] (то есть &a[6][0] - &a[4][0])? Я думаю, что мы здесь на шаткой почве.

Ian Abbott 16.02.2023 16:20

@IanAbbott Я с вами здесь, что это совсем не очевидно; но я думаю, что это следует из sizeof(T[n]) == sizeof(T)*n и (intptr_t)(p+i) - (intptr_t)p == sizeof(*p)*i, которые я считаю гарантированными.

Yakov Galka 16.02.2023 16:22

@IanAbbott нет, все в порядке

0___________ 16.02.2023 16:22
&a[5][20] не является незаконным. Это тот же указатель, что и a[5] + 20 (или &a[5][0] + 20).
Ian Abbott 16.02.2023 16:23

Нет, второй индекс находится вне диапазона. Это UB, даже если он находится внутри того же массива.

0___________ 16.02.2023 16:24

Вы говорите, что &a[5][20] незаконно, но a[5] + 20 и &a[5][0] + 20 в порядке?

Ian Abbott 16.02.2023 16:27

@0___________, (@Ian Abbot) Зачем утверждать, что &a[5][20] — это плохо? Индекс 20 находится за концом массива, что является допустимым исключением для вычислений массива. Формирование адреса массива в порядке. printf("%p\n", (void *) &a[5][20]); в порядке.

chux - Reinstate Monica 16.02.2023 16:40

@YakovGalka У меня нет таких гарантий для intptr_t, но, возможно, замена intptr_t на char * допустима (то есть (char *)(p+i) - (char *)p == sizeof(*p)*i), потому что любой объект можно рассматривать как массив char, а два указателя char указывают на байты внутри объекта a в этом случае.

Ian Abbott 16.02.2023 17:00

Массив не имеет назначенных значений, но при вычитании значений в массиве значение 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 - Reinstate Monica 16.02.2023 17:27

@chux-ReinstateMonica Ммм, но для того, чтобы общий массив имел значение, - должна быть специальная формулировка для многомерных массивов, а ее нет.

HolyBlackCat 16.02.2023 17:34

Хорошее исправление, еще не согласовано с еще меньшей вероятностью взлома. «потребуется специальная формулировка для многомерных массивов» -> существуют и другие возможности. Я подозреваю, что глубоко в недрах спецификации C тот факт, что массивы a[6] и a[5] сами по себе являются частью большего общего массива, делает a[6] - a[5] хорошо определенным. Просто слишком глубоко для меня сегодня.

chux - Reinstate Monica 16.02.2023 17:38

Будет ли int z = ((char *)a[6] - (char *)a[5]) / sizeof(a[0][0]); действительным? a[6] и a[5] — это разные объекты массива, но оба являются частью одного и того же более крупного объекта, так что это может быть нормально. Если он действителен, он избегает определяемой реализацией природы преобразования указателя в uintptr_t.

Ian Abbott 16.02.2023 19:27

@IanAbbott Это спорно. Я думаю, что это лучше, чем то, что делает OP, и, вероятно, я бы так и сделал, но это все еще может быть неопределенным. std::launder есть интересная формулировка для «достижимости через указатель», но я не помню, уточняется ли это где-либо еще в стандарте. Не должно создавать никаких проблем в реальном мире.

HolyBlackCat 16.02.2023 19:31

@HolyBlackCat Я удалил замечание sizeof(int) == 4, поскольку оно не имеет отношения к результату вычитания указателя.

Yakov Galka 17.02.2023 19:02

Другие вопросы по теме