Выровнены ли массивы в .NET по какой-либо границе?
Если да, то к какому? И одинаково ли это для всех типов массивов?
Выровнены ли массивы в .NET по какой-либо границе?
Если да, то к какому? И одинаково ли это для всех типов массивов?
Общеязыковая инфраструктура (ECMA-335) накладывает следующие ограничения на выравнивание:
12.6.2 Выравнивание
Встроенные типы данных должны быть правильно выровнены, что определяется следующим образом:
- 1-байтовые, 2-байтовые и 4-байтовые данные правильно выравниваются, когда они хранятся на границе 1, 2 или 4 байта соответственно.
- 8-байтовые данные правильно выравниваются, когда они хранятся на той же границе, которая требуется базовому оборудованию для атомарного доступа к собственному int.
Таким образом, int16 и unsigned int16 начинаются с четного адреса; int32, unsigned int32 и float32 начинаются с адреса, кратного 4; и int64, unsigned int64 и float64 начинаются с адреса, кратного 4 или 8, в зависимости от целевой архитектуры. Собственные типы размеров (собственный int, собственный unsigned int и &) всегда имеют естественное выравнивание (4 байта или 8 байтов, в зависимости от архитектуры). При внешней генерации они также должны быть выровнены по своему естественному размеру, хотя переносимый код может использовать выравнивание по 8 байтам, чтобы гарантировать независимость от архитектуры. Настоятельно рекомендуется, чтобы float64 был выровнен по 8-байтовой границе, даже если размер собственного int составляет 32 бита.
CLI также указывает, что вы можете использовать префикс unaligned
для произвольного выравнивания. Кроме того, JIT должен создавать правильный код для чтения и записи независимо от фактического выравнивания.
Кроме того, интерфейс командной строки допускает явное расположение полей класса:
explicitlayout
: класс, отмеченныйexplicitlayout
, заставляет загрузчик игнорировать последовательность полей и использовать предоставленные явные правила макета в виде смещений полей и/или общего размера или выравнивания класса. Существуют ограничения на допустимые макеты, указанные в Разделе II....
При желании разработчик может указать размер упаковки для класса. Это информация о макете, которая используется нечасто, но позволяет разработчику контролировать выравнивание полей. Это не спецификация мировоззрения как таковая, а скорее служит модификатором, который устанавливает потолок для всех мировоззрений. Типичными значениями являются 1, 2, 4, 8 или 16. Универсальные типы не должны быть помечены
explicitlayout
.
explicitlayout
классы будут потреблять больше памяти, чем у них есть на самом деле, что упрощает сборку мусора. Теоретически это может вызвать проблемы с производительностью.
- person user7116; 16.03.2012
Я не делал этого сам, но если вам нужно контролировать выравнивание массива для взаимодействия с неуправляемым режимом, вы можете рассмотреть возможность использования (небезопасного) фиксированного массива внутри структуры с примененным StructLayoutAttribute
и посмотреть, если это работает.
В .NET объекты (типом которых являются массивы) всегда выравниваются на основе размера указателя (например, выравнивание по 4 или 8 байтам). Таким образом, указатели объектов и массивы объектов всегда выравниваются в .NET.
Код в ответе Майкла Грачика проверяет выравнивание по индексу, потому что, хотя сам массив выровнен, поскольку это массив Int32, отдельные нечетные индексы не будут выровнены в 64-битных системах. В 32-битных системах все индексы массива Int32 будут выровнены.
Так что технически этот метод мог бы быть быстрее, если бы он проверял разрядность процесса. В 32-битных процессах не нужно будет выполнять проверку выравнивания для массивов Int32. Поскольку все индексы будут выровнены по словам, а указатели в этом случае также имеют длину слова.
Я также должен отметить, что разыменование указателя в .NET не требует выравнивания. Однако это будет медленнее. например если у вас есть действительный указатель byte* и он указывает на данные длиной не менее восьми байтов, вы можете привести его к long* и получить значение:
unsafe
{
var data = new byte[ 16 ];
fixed ( byte* dataP = data )
{
var misalignedlongP = ( long* ) ( dataP + 3 );
long value = *misalignedlongP;
}
}
Читая исходный код .NET, вы можете видеть, что Microsoft иногда учитывает выравнивание, а часто нет. Примером может служить внутренний метод System.Buffer.Memmove
(см. https://referencesource.microsoft.com/#mscorlib/system/buffer.cs,c2ca91c0d34a8f86). Этот метод имеет пути кода, которые приводят byte* к типу long без каких-либо проверок выравнивания в нескольких местах, и вызывающие методы также не проверяют выравнивание.
Я ничего не знаю об управляемых массивах, но в нескольких местах код Microsoft BCL предполагает, что fixed
массивы выровнены по словам. Вот пример из BitConverter.cs
в .NET 4.0:
public static unsafe int ToInt32 (byte[]value, int startIndex) {
//... Parameter validation
fixed( byte * pbyte = &value[startIndex]) {
if( startIndex % 4 == 0) { // data is aligned
return *((int *) pbyte);
}
else {
// .. do it the slow way
}
}
}
Как видите, код проверяет выравнивание с помощью startIndex, а не *pbyte. Есть только две причины, почему это так:
Я не думаю, что это ошибка. Я использую ToInt32 все время, и это никогда не вызывает у меня проблем. Я также склонен доверять BCL презумпции сомнения, потому что авторы иногда хорошо знают внутреннее устройство CLR.
Я думаю, можно с уверенностью предположить, что массивы fixed
всегда выравниваются по словам.