Если вы читаете еще один вопрос об aliasing (Что такое строгое правило псевдонимов?) и его главный ответ, я понял, что все еще не полностью удовлетворен, хотя я думаю Я все это понял.
(Этот вопрос теперь помечен как C и С++. Если ваш ответ относится только к одному из них, уточните, пожалуйста.)
Итак, я хочу понять, как сделать некоторые разработки в этой области, указывая указатели агрессивными способами, но с помощью простого консервативного правила, которое гарантирует, что я не буду вводить UB. У меня есть предложение для такого правила.
(Обновление: конечно, мы могли бы просто избегать всех видов пиннинга, но это не очень учебное. Если, конечно, не существует четких ноль исключений, кроме исключения union
. )
Обновление 2: теперь я понимаю, почему метод, предложенный в этом вопросе, неверен. Однако все же интересно узнать, существует ли простая, безопасная альтернатива. На данный момент существует хотя бы один ответ, который предлагает такое решение.
Это оригинальный пример:
int main()
{
// Get a 32-bit buffer from the system
uint32_t* buff = malloc(sizeof(Msg));
// Alias that buffer through message
Msg* msg = (Msg*)(buff);
// Send a bunch of messages
for (int i =0; i < 10; ++i)
{
msg->a = i;
msg->b = i+1;
SendWord(buff[0] );
SendWord(buff[1] );
}
}
Важная строка:
Msg* msg = (Msg*)(buff);
что означает, что теперь есть два указателя (разных типов), указывающие на одни и те же данные. Я понимаю, что любая попытка записи через один из них сделает другой указатель по существу недействительным. ( "Недопустимым" я подразумеваю, что мы можем безопасно его игнорировать, но чтение/запись с помощью недопустимого указателя - UB.)
Msg* msg = (Msg*)(buff);
msg->a = 5; // writing to one of the two pointers
SendWord(buff[0] ); // renders the other, buffer, invalid
Поэтому мое предлагаемое правило заключается в том, что после создания второго указателя (т.е. create msg
) вы должны немедленно и навсегда "удалить" другой указатель.
Какой лучший способ убрать указатель, чем установить его в NULL:
Msg* msg = (Msg*)(buff);
buff = NULL; // 'retire' buff. now just one pointer
msg->a = 5;
Теперь последняя строка, назначающая msg->a
, не может аннулировать любые другие указатели, потому что, конечно, их нет.
Далее, конечно, нам нужно найти способ вызова SendWord(buff[1] );
. Это невозможно сделать немедленно, потому что buff
был удален и равен NULL. Мое предложение теперь - отбросить назад.
Msg* msg = (Msg*)(buff);
buff = NULL; // 'retire' buff. now just one pointer
msg->a = 5;
buff = (uint32_t*)(msg); // cast back again
msg = NULL; // ... and now retire msg
SendWord(buff[1] );
Итак, каждый раз, когда вы накладываете указатель между двумя "несовместимыми" типами (я не уверен, как определить "несовместимый"?), вы должны немедленно "удалить" старый указатель. Установите его в NULL явно, если это поможет вам применить правило.
Насколько достаточно консервативен?
Возможно, это слишком консервативно и имеет другие проблемы, но я сначала хочу знать, достаточно ли это достаточно консервативно, чтобы не вводить UB, нарушая строгий псевдоним.
Наконец, повторите исходный код, измененный для использования этого правила:
int main()
{
// Get a 32-bit buffer from the system
uint32_t* buff = malloc(sizeof(Msg));
// Send a bunch of messages
for (int i =0; i < 10; ++i)
{ // here, buff is 'valid'
Msg* msg = (Msg*)(buff);
buff = NULL;
// here, only msg is 'valid', as buff has been retired
msg->a = i;
msg->b = i+1;
buff = (uint32_t*) msg; // switch back to buff being 'valid'
msg = NULL; // ... by retiring msg
SendWord(buff[0] );
SendWord(buff[1] );
// now, buff is valid again and we can loop around again
}
}