Было несколько ошибок из-за строгого алиасинга, и я решил попытаться исправить их все. При детальном рассмотрении того, что это такое, иногда кажется, что GCC не выдает предупреждения, а также что некоторые вещи невозможно реализовать. По крайней мере, насколько я понимаю, все, что ниже, сломано. Итак, я неправильно понимаю, есть ли правильный способ сделать все эти вещи, или какой-то код просто должен технически нарушать правило и хорошо покрываться системными тестами?
Ошибки были из некоторого кода, где буферы char и unsigned char были смешаны, например. как показано ниже:
size_t Process(char *buf, char *end)
{
char *p = buf;
ProcessSome((unsigned char**)&p, (unsigned char*)end);
//GCC decided p could not be changed by ProcessSome and so always returned 0
return (size_t)(p - buf);
}
Изменение этого на приведенное ниже, казалось, решило проблему, хотя это все еще связано с приведением, поэтому я не уверен, почему это теперь работает и не содержит предупреждений:
size_t Process(char *buf, char *end)
{
unsigned char *buf2 = (unsigned char *)buf;
unsigned char *p = buf2;
unsigned char *end2 = (unsigned char*)end;
ProcessSome(&p, end2);
return (size_t)(p - buf2);
}
Также есть куча других мест, которые вроде бы работают без предупреждений
//contains a unsigned char* of data. Possibly from the network, disk, etc.
//the buffer contents itself is 8 byte aligned.
const Buffer *buffer = foo();
const uint16_t *utf16Text = (const uint16_t*)buffer->GetData();//const unsigned char*
//... read utf16Text. Does not even seem to ever be a warning
//also seems to work fine
size_t len = CalculateWorstCaseLength(...);
Buffer *buffer = new Buffer(len * 2);
uint16_t *utf16 = (uint16_t*)buffer->GetData();//unsigned char*
len = DoSomeProcessing(utf16, len, ...);
buffer->Truncate(len * 2);
send(buffer);
А некоторые с...
struct Hash128
{
unsigned char data[16];
};
...
size_t operator ()(const Hash128 &hash)
{
return *(size_t*)hash.data;//warning
}
Несимвольный случай. У этого нет предупреждения, и даже если это плохо, как мне его избежать (оба способа работают)?
int *x = fromsomewhere();//aligned to 16 bytes, array of 4
__m128i xmm = _mm_load_si128((__m128*i)x);
__m128i xmm2 = *(__m128i*)x;
Глядя на другие API, кажется, что есть различные случаи, которые, насколько я понимаю, нарушают правило (не сталкивался со специфичным для Linux/GCC, но уверен, что где-то он будет).
CoCreateInstance Имеет выходной параметр void**, требующий явного приведения указателя. В Direct3D тоже есть что-то подобное.
LARGE_INTEGER — это объединение, которое, вероятно, будет иметь чтение/запись для разных членов (например, какой-то код может использовать высокий/низкий, тогда какой-то другой может читать int64).
Я помню, как реализация CPython довольно успешно приводит PyObject* к множеству других вещей, которые в начале имеют такое же распределение памяти.
Многие реализации хеширования, которые я видел, приводят входной буфер к uint32_t*, а затем, возможно, используют uint8_t для обработки 1-3 байтов в конце.
Почти каждая реализация распределителя памяти, которую я видел, использует char* или unsigned char*, который затем должен быть приведен к желаемому типу (возможно, через возвращенный void*, но внутри для выделения, по крайней мере, это был char)