кто-нибудь может объяснить расположение памяти
std::vector<std::array<int, 5>> vec(2)
обеспечивает ли он непрерывный блок памяти 2D-массива с 2 рядами по 5 элементов?
Насколько я понимаю, вектор векторов
std::vector<std::vector<int>> vec(2, std::vector<int>(5))
предоставить структуру памяти двасмежные массивы длины5 элементs в разных местах в памяти.
Будет ли то же самое для вектора массивов?





Массивы не имеют косвенного доступа, а просто хранят свои данные «напрямую». То есть std::array<int, 5> буквально содержит пять int подряд, плоско. И, как и векторы, они не помещают отступы между своими элементами, поэтому они «внутренне смежны».
Однако сам объект std::array может быть больше, чем набор его элементов! Разрешено иметь завершающие "вещи", такие как отступы. Таким образом, хотя вероятно, это не обязательно верно, что ваши данные будут все непрерывными в первом случае.
An int
+----+
| |
+----+
A vector of 2 x int
+----+----+----+-----+ +----+----+
| housekeeping | ptr | | 1 | 2 |
+----+----+----+-----+ +----+----+
| ^
\-----------
An std::array<int, 5>
+----+----+----+----+----+----------->
| 1 | 2 | 3 | 4 | 5 | possible cruft/padding....
+----+----+----+----+----+----------->
A vector of 2 x std::array<int, 5>
+----+----+----+-----+ +----+----+----+----+----+----------------------------+----+----+----+----+----+----------->
| housekeeping | ptr | | 1 | 2 | 3 | 4 | 5 | possible cruft/padding.... | 1 | 2 | 3 | 4 | 5 | possible cruft/padding....
+----+----+----+-----+ +----+----+----+----+----+----------------------------+----+----+----+----+----+----------->
| ^
\-----------
И даже если бы это было так, из-за правил псевдонима, могли бы вы использовать один int* для навигации по всем 10 числам, это потенциально было бы другим вопросом!
В целом вектор из десяти int будет более четким, полностью упакованным и, возможно, более безопасным в использовании.
В случае вектора векторов вектор на самом деле просто указатель плюс некоторая уборка, отсюда и косвенность (как вы говорите).
Согласно ответам здесь данные не должны быть смежными: Гарантируется ли непрерывность данных во вложенных массивах std ::?. По этой теме ведутся дискуссии. Еще обсуждения: Имеет ли std :: array из std :: array непрерывную память? и Размер std :: array определен стандартом.
IOW, хотя выделенная память должна быть непрерывной, элементы массива не должны быть смежными.
Ох, этот ответ становится все шире и шире. Повышено до 13.
@Bathsheba Схема ужасная, ну да ладно ?
@LightnessRacesinOrbit Если вы добавите диаграмму для вектора векторов, ваш ответ будет «в камень» :)
@DanielLangr Ба, может быть, позже ??
Примечание. static_assert(sizeof(std::array<int,t>)==sizeof(int)*5) смягчает любые дополнения (и передает все версии всех основных компиляторов, поддерживающих std::array). Это не уменьшает проблемы с псевдонимом.
@ Yakk-AdamNevraumont После долгих размышлений я бы наверное счел это достаточным, учитывая, насколько широко это пройдет. Но вектор int все же лучше.
Большая разница между std::vector и std::array заключается в том, что std::vector содержит указатель на память, которую он оборачивает, а std::array содержит сам по себе фактический массив.
Это означает, что вектор векторов похож на зубчатый массив.
Для вектора массивов объекты std::array будут размещены непрерывно, но отдельно от векторного объекта. Обратите внимание, что сам объект std::array может быть больше, чем содержащийся в нем массив, и в этом случае данные не будут смежными.
Последний бит также означает, что массив (простой C-стиль или std::array) std::array также может не хранить данные непрерывно. Объекты std::array в массиве будут смежными, но не данные.
Единственный способ гарантировать непрерывность данных для «многомерного» массива - это вложенные простые массивы в стиле C.
@DanielLangr Спасибо, что напомнили мне. Перефразировал эту часть.
Стандарт C++ не гарантирует, что std::array не содержит никакой полезной нагрузки в конце массива, поэтому, увы, вы не можете предположить, что первый элемент последующего массива находится сразу после последнего элемента предыдущего массива.
Даже если бы это было так, поведение при попытке достичь любого элемента в массиве с помощью арифметики указателя на указатель на элемент в другом массиве не определено. Это связано с тем, что арифметика указателей действительна только в массивах.
Вышесказанное также относится к std::array<std::array>.
static_assert(sizeof(std::array<int,5>)==5*sizeof(int));
вышеупомянутое предотвращает наличие каких-либо дополнений на конце std::array. Ни один крупный компилятор не приведет к сбою вышеуказанного до этой даты, и я уверен, что не будет в будущем.
Если и только если вышеперечисленное не сработает, тогда у std::vector<std::array<int,5>> v(2) будет «разрыв» между std::array.
Это не так сильно, как хотелось бы; указатель сгенерирован следующим образом:
int* ptr = &v[0][0];
имеет область действия только до ptr+5, а разыменование ptr+5 является неопределенным поведением.
Это связано с правилами сглаживания; вам не разрешается «проходить» мимо конца одного объекта в другой, даже если вы знаете, что он там есть, если только вы сначала не перейдете к определенным типам (например, char*), где разрешена менее ограниченная арифметика указателей.
Это правило, в свою очередь, существует, чтобы позволить компиляторам определять, к каким данным осуществляется доступ через какой указатель, без необходимости доказывать, что арифметика произвольных указателей позволит вам достичь внешних объектов.
Так:
struct bob {
int x,y,z;
};
bob b {1,2,3};
int* py = &b.y;
независимо от того, что вы делаете с py в качестве int*, вы не может юридически модифицируете x или z с его помощью.
*py = 77;
py[-1]=3;
std::cout << b.x;
компилятор может оптимизировать строку std::cout, чтобы просто печатать 1, потому что py[-1]=3 может пытаться изменять b.x, но это означает неопределенное поведение.
Такие же ограничения не позволяют вам перейти от первого массива в вашем std::vector ко второму (т. Е. За пределы ptr+4).
Создание ptr+5 допустимо, но только как указатель на один конец. Сравнение ptr+5 == &v[1][0] также не указано в результате, хотя их двоичные значения будут абсолютно идентичны в каждом компиляторе на каждой основной аппаратной системе.
Если вы хотите пойти дальше по кроличьей норе, невозможно даже реализовать std::vector<int> в самом C++ из-за этих ограничений на псевдонимы указателей. В последний раз, когда я проверял (это было до C++ 17, но я не видел разрешения в C++ 17), стандартный комитет работал над решением этой проблемы, но я не знаю состояния каких-либо таких усилий. (Это меньшая проблема, чем вы могли подумать, потому что ничто не требует, чтобы std::vector<int> был реализован на стандартном C++; он просто должен иметь стандартно определенное поведение. Он может использовать внутренние расширения, специфичные для компилятора.)
Хороший ответ; повышен. Также обратите внимание на несколько связанную проблему, заключающуюся в том, что вы не можете написать malloc в стандартном C.
Учитывая ответы, если вы этого хотите, используйте
std::vector<int> vec(5*2)и выполните 2D-индексацию внутри плоского одномерного массива. Возможно, напишите класс-оболочку для 2D-индексации поверх плоского контейнера с длиной строки либо шаблонной, либо переменной времени выполнения. Вы также можете предоставить плоский вид, чтобы алгоритмы, которым просто нужно что-то делать с каждым элементом, не заботясь о 2D-положении, могли сделать это с помощью одного большого цикла более эффективно.