Я работаю над компиляторами для нескольких встроенных платформ. Пользователь недавно пожаловался на следующее поведение одного из наших компиляторов. Данный код выглядит так:
extern volatile int MY_REGISTER;
void Test(void)
{
(void) (MY_REGISTER = 1);
}
Компилятор генерирует это (на псевдоассемблере):
Test:
move regA, 1
store regA, MY_REGISTER
load regB, MY_REGISER
То есть он не только записывает в MY_REGISTER, но и затем считывает его обратно. Дополнительная нагрузка расстроила его по соображениям производительности. Я объяснил, что это произошло потому, что в соответствии со стандартом «выражение присваивания имеет значение левого операнда после присваивания, [...]» .
Как ни странно, удаление приведенного к пустоте меняет поведение: исчезает нагрузка. Пользователь доволен, но я просто сбит с толку.
Я также проверил это в паре версий GCC (3.3 и 4.4). Там компилятор никогда не генерирует нагрузку, даже если значение используется явно, например
int TestTwo(void)
{
return (MY_REGISTER = 1);
}
Превращается в
TestTwo:
move regA, 1
store regA, MY_REGISTER
move returnValue, 1
return
Есть ли у кого-нибудь мнение о том, какая интерпретация стандарта является правильной? Должен ли вообще происходить обратный отсчет? Правильно или полезно добавлять только чтение, если значение используется или приводится к void?