3 / 3 / 0
Регистрация: 16.01.2014
Сообщений: 55
|
||||||
1 | ||||||
Внутреннее устройство многомерного массива и неявное преобразование массива в указатель20.12.2014, 12:15. Показов 3211. Ответов 22
Метки нет (Все метки)
Букв получилось многовато. Поэтому, чтобы сэкономить Ваше время, предлагаю сразу вопрос:
Буду очень благодарен, если кто-то сможет конкретизировать, как всё-таки устроен логически внутри себя многомерный массив в C++ и в какие моменты происходят в нём неявные преобразования? Далее моя попытка разобраться в этом и нащупать логику (если времени на это нет, то считайте, что пост для Вас ограничен только текстом выше).
Предполагаю: #1: 1.1) b - неявно преобразуется (далее -->) из объекта "2D-массив" - в ptr на первый его эл-т - т.е. в ptr на 1D-массив элементов типа int* 1.2) к результату 1.1 применяется разыменование `(*b)`. Т.е. ptr на 1D-массив int* --> в объект 1D-массив int* 1.3) неявно --> в ptr на свой 1й элемент - т.е. в ptr на 1D-субмассив элементов типа int //Тут становится понятно, что логика эта какая-то кривая, т.к. 1.4 должен выполнять адресацию по ptr'у на массив. Если бы так, то на выходе мы получили бы скорее адрес 1го элемента второго массива из 3-х интов (т.е. адрес эл-та "55"). Но никак не значение "22". Пробую другую логику: Считаю, что объект массив существует только для самого внешнего объекта многомерного массива. Тогда получается: #1: 1.1) b - неявно преобразуется (далее -->) из объекта "2D-массив" - в ptr на первый его эл-т - т.е. в ptr на int* (т.е. в int**) 1.2) к результату 1.1 применяется разыменование `(*b)`. Т.е. получаем int* (ptr на "11", т.е. на 1й эл-т первого субмассива) 1.3) процедура адресации `[1]` выполняет 2 функции: сдвиг и разыменование. Т.о. получаем значение "22". Вроде получается пока красиво. Но попытаюсь применить эту же логику к случаю #2 #2: 2.1) b - неявно --> из объекта "2D-массив" - в ptr на первый его эл-т - т.е. в ptr на int* (т.е. в int**) 2.2) процедура адресации `[1]` выполняет сдвиг и разыменование. ... //Тут становится ясно, что эта логика не работает, т.к. мы в результате шага 2.2 получили бы следующее: int** сдвинулся бы на 1 соответственно указываемому типу - т.е. на 4 байта указателя, а разыменование (которое подразумевается вторым действием оператора адресации) дало бы ptr на "22". И тогда последнее действие собственно разыменования явно указанного дало бы окончательный результат тоже "22". Но не "55". Т.е. должна быть, видимо, какая-то внутренняя таблица размеров указываемых объектов для указателей, получившихся в результате объявления массива на каждом уровне (разве что только кроме последнего). А, следовательно, тогда уж мы возвращаемся к тому, что должны быть объекты "массив" не только для внешнего уровня, но мы это как-будто бы опровергли выше. Попробую найти компромиссную логику: Что если действительно объект массив существует не для всех уровней многомерного массива. А, предположим, для всех, кроме последнего. И объект этот в нашем случае НЕ "Массив элементов типа int**", а скорее что-то вроде "Массив элементов типа ptr на первый эл-т объекта "Три эл-та типа int*" (далее для краткости "МассивPtrTo3Int*"). Тогда #2: 2.1) b - из объекта "МассивPtrTo3Int*" неявно --> в ptr на первый его эл-т - т.е. в ptr на 1й эл-т объекта "3 эл-та типа int*" 2.2) процедура адресации `[1]` выполняет сдвиг и разыменование: сдвиг переносит нас к ptr'у на 1й эл-т второго объекта "3 эл-та типа int*", а разыменование --> это к 1-ому из эл-тов типа int* 2.3) Последнее действие явно указанного разыменования даёт нам его значение - "55". Не стану уже Вас утомлять, но под эту логику согласованно ложится и вариант #1. Кажется, я суть почти ухватил, только как-то немного расплывчато и нечётко получилось выразить. Повторюсь: Буду очень благодарен, если кто-то сможет указать на ошибки в рассуждении и, ГЛАВНОЕ, конкретизировать, как всё-таки устроен логически внутри себя многомерный массив в C++ и в какие моменты происходят в нём неявные преобразования.
0
|
20.12.2014, 12:15 | |
Ответы с готовыми решениями:
22
Преобразование многомерного массива в двумерный Преобразование многомерного массива в одномерный Преобразование координат многомерного массива Преобразование многомерного массива к уникальному виду |
31 / 31 / 6
Регистрация: 23.10.2014
Сообщений: 107
|
||||||
20.12.2014, 13:50 | 2 | |||||
У операций есть приоритеты. Операция взятия по индексу имеет больший приоритет, нежели операция разыменования т.е *b[i] == *(b[i]), ну и:
0
|
3225 / 1752 / 436
Регистрация: 03.05.2010
Сообщений: 3,867
|
|
20.12.2014, 14:12 | 3 |
А чего тут непонятного?
Имя массива может использоваться как указатель на его первый элемент. В #1 b - это указатель на первый трехмерный массив целых. *b - это сам этот массив. Далее выводим его элемент с индексом 1. В #2 индексируем b, т.е. указатель на первый трехмерный массив целых и получаем второй трехмерный массив, который можно использовать как указатель на его первый элемент, т.е. 55. Выводим разыменование этого указателя, т.е. само число 55.
0
|
3 / 3 / 0
Регистрация: 16.01.2014
Сообщений: 55
|
|
20.12.2014, 14:30 [ТС] | 4 |
@Mr.X, в таком масштабе мне это вроде понятно. Вся же куча написанного текста - попытка разобраться в этих процессах в более мелких деталях. Большей частью, увязать с тем фактом, что `b` на самом деле - это вроде как объект "массив", который при таком обращении претерпевает неявное преобразование к указателю на свой 1й элемент (такого преобразования, например, не происходит при обращении в выражении взятия адреса `&b`). Прочёл об этом в заморской теме "array to pointer conversion" или как они это ещё называют "decay into ptr".
Вот пытаюсь понять, каждый ли уровень многомерного массива является таким же объектом, которому часто грозит "разложение в указатель" и в какой именно момент это разложение происходит в выражениях. Добавлено через 2 минуты @NotNot, это очевидные для меня вещи. Вопрос же совсем в другом.
0
|
31 / 31 / 6
Регистрация: 23.10.2014
Сообщений: 107
|
|
20.12.2014, 16:02 | 5 |
SaShka K, приглашаю вас почитать Страуструпа. The Programming Language C++ 4-е издание. В частности, седьмую главу.
Добавлено через 31 минуту http://ideone.com/VbnzJF Хе-хе. Мои представления были не верны.
0
|
3225 / 1752 / 436
Регистрация: 03.05.2010
Сообщений: 3,867
|
|
20.12.2014, 16:36 | 6 |
Ну, "объект" здесь один - это одномерный массив, записанный в память именно так, как вы указали в инициализаторе. Вся многомерность организуется через игру с указателями, что сделано, как и все с Си, через задницу. Сишный массив - это такой же "недотип", как и сишная нультерминальная строка.
У меня вот не хватает несерьезности, чтобы относиться к этим вещам серьезно. Мне кажется, что нужно быть проще, и использовать векторы и std::array, а копаться в этой нетрезвой зауми предоставить сишникам, благо они это любят.
0
|
941 / 869 / 355
Регистрация: 10.10.2012
Сообщений: 2,706
|
||||||||||||||||
20.12.2014, 17:27 | 7 | |||||||||||||||
Такой массив:
Добавлено через 29 минут
1
|
3 / 3 / 0
Регистрация: 16.01.2014
Сообщений: 55
|
|
20.12.2014, 18:04 [ТС] | 8 |
Верно! Спасибо. Вот тут я заблудился.
Действительно: int i = 1; //тип i - intint a [3] = {11,22,33}; //тип a - 1D-массив int'ов (неявно распадается в ptr на 1й эл-т - т.е. в int*)int b [2][3] = {{11,22,33}, {55,66,77}}; //тип b - 2D-массив int'ов, т.е. 1D-массив 1D-массивов int'ов (неявно распадается в ptr на 1й эл-т - т.е. в int(*)[])Тогда получается: #1: 1.1) неявно `b` --> в int(*)[3] 1.2) разыменование даёт int[3] (объект 1D-субмассив 1ой строки) 1.3) который неявно --> в int* (указатель на 1й элемент) 1.4) адресация даёт сдвиг и разыменование - получаем "22" #2: 2.1) то же самое 2.2) адресация (т.е. сдвиг + разыменование) - получаем int[3] (субмассив второй строки) 2.3) неявно --> в int* (указатель на 1й элемент) 2.4) после разыменования - "55" Вроде всё сходится и логика теперь похожа на чёткую! Но Вы натолкнули на другой вопрос: Какова же тогда роль первого измерения `b[2][3]` (в частности его размера), которая в данном случае оказывается "за кадром"? Добавлено через 3 минуты @NotNot, спасибо за Вашу активность, но всё ещё не вижу, как она может приблизить меня к ответу на поставленный вопрос: - главу просмотрел (издания 4 не имею, но в 3ем она под №5 с тем же названием) - в ней только базовые сведения - ничего нового для себя (в т.ч. по своему вопросу не встретил). - код по ссылке посмотрел - забавно, но не понял, как он отвечает на мой вопрос.
0
|
941 / 869 / 355
Регистрация: 10.10.2012
Сообщений: 2,706
|
||||||
20.12.2014, 18:28 | 9 | |||||
В общем-то, никакая. Поэтому такой массив в функцию можно так передавать:
1
|
3 / 3 / 0
Регистрация: 16.01.2014
Сообщений: 55
|
|
20.12.2014, 18:38 [ТС] | 10 |
@lss, касательно последнего Вашего кода.
Именно то, что `cout<< b;` и `cout<< *b;` дают одинаковый результат и привело меня к теме неявного преобразования от объекта массив к указателю на его 1й элемент. А попытки разобраться, когда именно это преобразование происходит (и сколько таких объектов "массив", способных претерпеть его, заключено внутри многомерного массива) привели к написанию этого поста. Что я благодаря Вам попробую заключить:
0
|
941 / 869 / 355
Регистрация: 10.10.2012
Сообщений: 2,706
|
|
20.12.2014, 18:48 | 11 |
Такой объект один - имя массива. Сколько в этом массиве содержится других массивов неважно. Компилятору нужен адрес начала многомерного массива (в который разлагается имя массива) и размерности массивов, которые он содержит, чтобы
0
|
3 / 3 / 0
Регистрация: 16.01.2014
Сообщений: 55
|
|
20.12.2014, 19:01 [ТС] | 12 |
@lss, получается, что в области видимости, в рамках которой объявлен, скажем, XD-массив, существует объект "XD-массив", у которого есть атрибут значения каждой из X размерностей. Когда же мы передаём его в функцию, то XD-массив распадается в указатель на (X-1)D-массив, теряя и 1 атрибут своей размерности верхнего уровня.
(просто пытаюсь немного систематизировать) Добавлено через 5 минут Если , то что-то неверно (а именно шаги 1.3 и 2.3) в моей последней интерпретации пошагового разбора выражений ##1 и 2: Добавлено через 3 минуты Так то оно так, но в критичных по производительности фрагментах std-контейнеры заметно эту самую производительность просаживают.
0
|
941 / 869 / 355
Регистрация: 10.10.2012
Сообщений: 2,706
|
||||||||||||||||
20.12.2014, 19:57 | 13 | |||||||||||||||
Я предлагаю проще. Когда объявляется такой массив:
Добавлено через 7 минут Вот если не так:
1
|
3225 / 1752 / 436
Регистрация: 03.05.2010
Сообщений: 3,867
|
|
20.12.2014, 20:15 | 14 |
Может оно и так, но отсюда не следует, что сишные массивы нужно называть объектами и искать в них какую-то скрытую мудрость, которой в них сроду не было.
А с std::array сравнивали по скорости?
0
|
31 / 31 / 6
Регистрация: 23.10.2014
Сообщений: 107
|
|
20.12.2014, 20:38 | 15 |
SaShka K, в главе, что я вам посоветовал, сказано, что двумерный массив, на самом деле, является одномерным массивом, а вся мерность присутствует только в исходном коде. Это утверждение и проверяет код который я предоставил. Также, в этой главе, говорится о такой вещи как: "a[b] == *(a + b) == *(b + a) == b[a]", где 'a' имеет тип int[n], а 'b' имеет тип int, что говорит о том что компилятору без разницы a[b] или b[a]. Он всегда преобразует записть вида a[b] к виду *(a + b), что, в свою очередь говорит о том что имя массива всегда преобразуется к указателю, а вот тип указателя уже зависит от размерности массива.
Теперь рассмотрим массив 'int a[n][k]'. К слову, 'a', вне каких либо операций имеет тип int[n][k]. 1. Рассмотрим операцию (*a)[i]. При разыменовании, 'a', неявно, преобразуется к int (*)[k]. В свою очередь, разыменование этого указателя даёт нам массив int[k]. Назовем его 'b'. Значит (*a)[i] -> b[i]. Во что превратится b[i] мы знаем: *(b + i), где 'b', неявно, преобразуется в int *. 2. Рассмотрим операцию *a[i], что, по сути, тоже самое что и *(a[i]). Операция a[i] дает нам массив int[k]. Назовем его 'b'. Значит *(a[i]) -> *b. При разыменовании, 'b', неявно, преобразуется в int*, который равен &b[0], и его разыменование дает нам int, который b[0], т.е первый элемент i-го подмассива массива 'a'. А теперь подумаем. Если b[i], где b: int[n], преобразуется в *(b + i), тогда может и a[i][j] преобразуется в нечто подобное? Действительно: a[i][j] -> (*(a + i))[j], где 'a' это - int (*)[k], и (*(a + i))[j] -> *(*(a + i) + j), где *(a + i) это - int[k], т.е a[i][j] -> *(*(a + i) + j).
1
|
Неэпический
|
||||||||||||||||||||||||||
20.12.2014, 20:50 | 16 | |||||||||||||||||||||||||
Чтобы было на форуме
8.3.4 Arrays 1. In a declaration T D where D has the form D1 [ constant-expressionopt ] attribute-specifier-seqopt and the type of the identifier in the declaration T D1 is “derived-declarator-type-list T”, then the type of the identifier of D is an array type; if the type of the identifier of D contains the auto type-specifier , the program is ill-formed. T is called the array element type; this type shall not be a reference type, the (possibly cv-qualified) type void, a function type or an abstract class type. If the constant-expression (5.19) is present, it shall be an integral constant expression and its value shall be greater than zero. The constant expression specifies the bound of (number of elements in) the array. If the value of the constant expression is N, the array has N elements numbered 0 to N-1, and the type of the identifier of D is “ derived-declarator-type-list array of N T”. An object of array type contains a contiguously allocated non-empty set of N subobjects of type T. Except as noted below, if the constant expression is omitted, the type of the identifier of D is “ derived-declarator-type-list array of unknown bound of T”, an incomplete object type. The type “ derived-declarator-type-list array of N T” is a different type from the type “ derived-declarator-type-list array of unknown bound of T”, see 3.9. Any type of the form “ cv-qualifier-seq array of N T” is adjusted to “array of N cv-qualifier-seq T”, and similarly for “array of unknown bound of T”. The optional attribute-specifier-seq appertains to the array. [ Example:
2. An array can be constructed from one of the fundamental types (except void), from a pointer, from a pointer to member, from a class, from an enumeration type, or from another array. 3. When several “array of” specifications are adjacent, a multidimensional array is created; only the first of the constant expressions that specify the bounds of the arrays may be omitted. In addition to declarations in which an incomplete object type is allowed, an array bound may be omitted in some cases in the declaration of a function parameter (8.3.5). An array bound may also be omitted when the declarator is followed by an initializer (8.5). In this case the bound is calculated from the number of initial elements (say, N) supplied (8.5.1), and the type of the identifier of D is “array of N T.” Furthermore, if there is a preceding declaration of the entity in the same scope in which the bound was specified, an omitted array bound is taken to be the same as in that earlier declaration, and similarly for the definition of a static data member of a class. 4. [ Example:
5. [ Note: conversions affecting expressions of array type are described in 4.2. Objects of array types cannot be modified, see 3.10. — end note ] 6. [ Note: Except where it has been declared for a class (13.5.5), the subscript operator [] is interpreted in such a way that E1[E2] is identical to *((E1)+(E2)). Because of the conversion rules that apply to +, if E1 is an array and E2 an integer, then E1[E2] refers to the E2-th member of E1. Therefore, despite its asymmetric appearance, subscripting is a commutative operation. 7. A consistent rule is followed for multidimensional arrays. If E is an n-dimensional array of rank i×j ×. . .×k, then E appearing in an expression that is subject to the array-to-pointer conversion (4.2) is converted to a pointer to an (n − 1)-dimensional array with rank j × . . . × k. If the * operator, either explicitly or implicitly as a result of subscripting, is applied to this pointer, the result is the pointed-to (n − 1)-dimensional array, which itself is immediately converted into a pointer. 8. [ Example: consider
9. [ Note: It follows from all this that arrays in C ++ are stored row-wise (last subscript varies fastest) and that the first subscript in the declaration helps determine the amount of storage consumed by an array but plays no other part in subscript calculations. — end note ]
1
|
3 / 3 / 0
Регистрация: 16.01.2014
Сообщений: 55
|
|
22.12.2014, 22:55 [ТС] | 17 |
@lss, спасибо за Ваше участие в обсуждении! Перфекционист во мне продолжил бы дискуссию, но, к счастью, рационалист ему намекает =), что суть уже схвачена, а доведение деталей до блеска будет уже гораздо больше есть времени, чем приносить пользы.
Добавлено через 7 минут @Mr.X, да я ж ничего не выдумывал сам - про "объект" и далее про его распад в указатель - это я начитался в иностранных интернетах. Просто хотелось разобраться, потому как мне как-то гораздо спокойнее живётся, когда понимаю, почему что-то именно таково, каким оно есть, а не пытаюсь запомнить магические рецепты.(но это лично моё восприятие). Касательно std::array - помню, что экспериментировал устраивать гонки сортировкам кажется с использованием std и избегая его. (но, кажется пользовался vector'ом) - помню, что код с std существенно проигрывал. Сейчас проверил что-то подобное с std::array - проигрывает в 3..5 раз. (но не стал докапываться пока, чтобы разобрать по операциям - какие именно приводят к этим задержкам) Добавлено через 15 минут @NotNot, спасибо за разъяснения - наконец понял Вас. Я пытался размышлять немного в другом ключе (но тут проблема во мне - вопрос сформулировал не слишком однозначно), но эта информация тоже интересна весьма и полезна для получения общей картинки. PS: в книжке то, о чём Вы сейчас подробнее сказали (в моём 3-ем издании) написано в приложении В.7. ("Многомерные массивы"), на которое в главе №5 ("Указатели, массивы и структуры") есть ссылка.
0
|
3225 / 1752 / 436
Регистрация: 03.05.2010
Сообщений: 3,867
|
|
22.12.2014, 23:42 | 18 |
Объект - это область памяти, хранящая данные некоторого типа. Сишный массив не является полноценным типом, поэтому и объектов в их нормальном понимании иметь не может.
0
|
285 / 176 / 21
Регистрация: 16.02.2018
Сообщений: 666
|
|
22.04.2018, 01:08 | 19 |
0
|
Неэпический
|
|
22.04.2018, 05:51 | 20 |
rat0r, поесть хочешь?
0
|
22.04.2018, 05:51 | |
22.04.2018, 05:51 | |
Помогаю со студенческими работами здесь
20
Преобразование многомерного массива в hidden (text) поля Нужно хитрое преобразование многомерного массива в одномерный Преобразование имени массива в указатель Cоздание многомерного массива - из 3х3 массива сделать 4х3 Выбор массива из многомерного массива по ключу Внутреннее устройство Искать еще темы с ответами Или воспользуйтесь поиском по форуму: |