При прочтении еще одного вопроса о псевдонимах (Что такое строгое правило псевдонима?) и Это главный ответ, я понял, что все еще не полностью удовлетворен, хотя мне кажется, что я все это понял.
(Этот вопрос теперь помечен как C и C ++. Если ваш ответ относится только к одному из этих вопросов, уточните, какие.)
Итак, я хочу понять, как развить некоторую разработку в этой области, применяя указатели агрессивным образом, но с простым консервативным правилом, гарантирующим, что я не буду вводить UB. У меня есть предложение по поводу такого правила.
(Обновление: конечно, мы могли бы просто избежать каламбуров. Но это не очень поучительно. Если, конечно, есть буквально ноль четко определенных исключений, помимо union
исключения. em>)
Обновление 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
Поэтому предлагаемое мной правило состоит в том, что, как только вы создадите второй указатель (т.е. создадите 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
}
}
uint32_t
typedef дляunsigned int
в вашей системе. Если нет, то ваш код явно UB. Если так, то мутно (Но ИМХО, не УБ). Я предлагаю обновить ваш вопрос так, чтобы дляMsg::a
использовался явно тот же тип, что и дляbuff[0]
и т. Д., Либо использовались явно разные типы. Ответ на оба случая был бы слишком длинным. - person M.M   schedule 15.07.2015