Чтобы предоставить дополнительную информацию и распространенные ошибки отличного ответа от @Antti Haapala:
TL; DR: доступ к невыровненным данным - это неопределенное поведение (UB) в C / C ++. Невыровненные данные - это данные по адресу (также известному как значение указателя), который не делится равномерно на его выравнивание (обычно это его размер). В (псевдо) коде: bool isAligned(T* ptr){ return (ptr % alignof(T)) == 0; }
Эта проблема часто возникает при анализе форматов файлов или данных, отправляемых по сети: у вас есть плотно упакованная структура с разными типами данных. Примером может служить такой протокол: struct Packet{ uint16_t len; int32_t data[]; };
(Читается как: длина 16 бит, за которой следует len, умноженное на 32-битное int в качестве значения). Теперь вы могли:
char* raw = receiveData();
int32_t sum = 0;
uint16_t len = *((uint16_t*)raw);
int32_t* data = (int32_t*)(raw2 + 2);
for(size_t i=0; i<len; ++i) sum += data[i];
Это не работает! Если вы предполагаете, что raw
выровнен (по вашему мнению, вы можете установить raw = 0
, который выровнен по любому размеру как 0 % n == 0
для всех n
), тогда data
невозможно выровнять (при условии, что выравнивание == размер типа): len
находится по адресу 0, поэтому data
находится по адресу 2 и 2 % 4 != 0
. Но приведение говорит компилятору: «Эти данные правильно выровнены» («... потому что в противном случае это UB, и мы никогда не столкнемся с UB»). Таким образом, во время оптимизации компилятор будет использовать инструкции SIMD / SSE для более быстрого вычисления суммы, и они дают сбой при получении невыровненных данных.
Примечание: есть невыровненные инструкции SSE, но они медленнее, и, поскольку компилятор предполагает выравнивание, которое вы обещали им здесь не используются.
Вы можете увидеть это в примере из @Antti Haapala, который я сократил и наложил на Godbolt, чтобы вы могли поиграть: https://godbolt.org/z/KOfi6V. Смотрите «программа вернула: 255» или «разбилась».
Эта проблема также довольно часто встречается в процедурах десериализации, которые выглядят следующим образом:
char* raw = receiveData();
int32_t foo = readInt(raw); raw+=4;
bool foo = readBool(raw); raw+=1;
int16_t foo = readShort(raw); raw+=2;
...
read*
заботится о порядке байтов и часто реализуется следующим образом:
int32_t readInt(char* ptr){
int32_t result = *((int32_t*) ptr);
#if BIG_ENDIAN
result = byteswap(result);
#endif
}
Обратите внимание, как этот код разыменовывает указатель, который указывает на меньший тип, который может иметь другое выравнивание, и вы сталкиваетесь с конкретной проблемой.
Эта проблема настолько распространена, что даже Boost страдает от нее во многих версиях. Существует Boost.Endian, который предоставляет простые типы байтов. Код на C от Godbolt можно легко записать как это:
#include <cstdint>
#include <boost/endian/arithmetic.hpp>
__attribute__ ((noinline)) size_t f(boost::endian::little_uint16_t *keyc, size_t len)
{
size_t hash = 0;
for (size_t i = 0; i < len; ++i)
hash += keyc[i];
return hash;
}
struct mystruct {
uint8_t padding;
boost::endian::little_uint16_t contents[100];
};
int main(int argc, char** argv)
{
mystruct s;
size_t len = argc*25;
for (size_t i = 0; i < len; i++)
s.contents[i] = i * argc;
return f(s.contents, len) != 300;
}
Тип little_uint16_t
- это, по сути, всего лишь несколько символов с неявным преобразованием из / в uint16_t
с byteswap
, если текущее соответствие машины BIG_ENDIAN
. Под капотом код, используемый Boost: endian, был похож на этот:
class little_uint16_t{
char buffer[2];
uint16_t value(){
#if IS_x86
uint16_t value = *reinterpret_cast<uint16_t*>(buffer);
#else
...
#endif
#if BIG_ENDIAN
swapbytes(value);
#endif
return value;
};
Он использовал знание того, что на архитектурах x86 невыровненный доступ возможен. Загрузка с невыровненного адреса была немного медленнее, но даже на уровне ассемблера такая же, как и загрузка с выровненного адреса.
Однако «возможный» не означает действительный. Если компилятор заменил "стандартную" загрузку инструкцией SSE, тогда это не сработает, как можно увидеть на godbolt. Это долгое время оставалось незамеченным, потому что эти инструкции SSE просто используются при обработке больших блоков данных с помощью одной и той же операции, например добавление массива значений, что я и сделал для этого примера. Это было исправлено в , который можно перевести в 1.storeferrer с помощью Boost 1.64 «стандартная» инструкция загрузки в ASM, которая поддерживает выровненные и невыровненные данные на x86, поэтому нет замедления по сравнению с версией с приведением. Но его нельзя преобразовать в выровненные инструкции SSE без дополнительных проверок.
Вывод: не используйте быстрые клавиши с приведением типов. С подозрением относитесь к каждому приведению типов, особенно при приведении к меньшему типу, и убедитесь, что выравнивание не может быть неправильным, или используйте безопасный memcpy.
person
Flamefire
schedule
02.08.2019
[unsigned] char*
имеет особое исключение со строгим псевдонимом: через него можно читать что угодно. Это не бесплатный обход строгого псевдонима, и создание из него невыровненногоu16*
недопустимо. - person Ry-♦   schedule 17.10.2017(const u16 *) (keyc + 1);
- person ryyker   schedule 17.10.2017const
далеко - это плохо. - person Ry-♦   schedule 17.10.2017unsigned short*
в программе, но нигде нетunsigned short
. Это похоже на нарушение псевдонима. - person Bo Persson   schedule 17.10.2017(const u16 *) (keyc + 1)
может легко привести к несогласованному доступу. Это очень плохой код. - person StoryTeller - Unslander Monica   schedule 17.10.2017x
- этоu8 x[len];
, и вы обращаетесь к его членам (char
) в функцииf
через указательconst u16*
. Это явное нарушение псевдонима. - person PSkocik   schedule 17.10.2017size_t len; scanf("%lu", &len);
зависит от платформы, потому чтоsize_t
обычно не такого же размера, какlong
, что предполагает модификатор типа форматаl
. Используйте модификатор типаz
для ссылки на аргументы типаsize_t
. - person David Foerster   schedule 17.10.2017u8
иu16
- имена типов, которые сильно вводят в заблуждение - они выглядят очень похоже на типы с фиксированной шириной, но таковыми не являются. - person Toby Speight   schedule 19.10.2017