В C, если B изменчив, должно ли выражение (void) (B = 1) читать B - PullRequest
13 голосов
/ 28 февраля 2011

Я работаю над компиляторами для пары встроенных платформ.Пользователь недавно пожаловался на следующее поведение одного из наших компиляторов.Данный код, подобный этому:

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, но и читает его впоследствии.Дополнительная нагрузка расстроила его по соображениям производительности.Я объяснил, что это потому, что в соответствии со стандартом «Выражение присваивания имеет значение левого операнда после присваивания, [...]" .

Как ни странно, удалениеto-void меняет поведение: нагрузка исчезает.Пользователь доволен, но я просто запутался.

Так что я также проверил это в нескольких версиях GCC (3.3 и 4.4).Там компилятор никогда не генерирует нагрузку, даже если это значение явно используется, например,

int TestTwo(void)
{
    return (MY_REGISTER = 1);
}

Превращается в

TestTwo:
    move regA, 1
    store regA, MY_REGISTER
    move returnValue, 1
    return

Есть ли у кого-нибудь мнение о правильной интерпретациистандарт?Должно ли вообще происходить обратное чтение?Правильно или полезно добавить только чтение, если значение используется или приведено к пустоте?

Ответы [ 3 ]

7 голосов
/ 28 февраля 2011

Соответствующий абзац в стандарте такой:

Оператор присваивания сохраняет значение в объекте, обозначенном левым операндом.Выражение присваивания имеет значение левого операнда после присваивания, но не является lvalue.Типом выражения присваивания является тип левого операнда, если только левый операнд не имеет квалифицированного типа, и в этом случае это неквалифицированная версия типа левого операнда.Побочный эффект обновления сохраненного значения левого операнда должен происходить между предыдущей и следующей точкой последовательности.

Таким образом, это ясно делает различие между «значением левого операнда» и обновлениемсохраненного значения.Также обратите внимание, что возвращаемое значение не является lvalue (поэтому в возвращаемом выражении нет ссылки на переменную), и все квалификаторы потеряны.

Поэтому я читаю это как gcc, который делает правильные вещи, когда возвращаетзначение, которое оно сознательно должно хранить.

Редактировать:

В следующем стандарте планируется уточнить это, добавив сноску:

Реализация может считывать объект для определения значения, но не обязательна, даже если объект имеет тип с изменяемыми значениями.

Редактировать 2:

На самом деле есть еще один абзац об операторах выражений, который может пролить свет на это:

Выражение в выражении оператора оценивается как пустое выражение для его побочных эффектов. \ Footnote {Например, присваиванияи вызовы функций, которые имеют побочные эффекты}

Поскольку это подразумевает, что эффект возврата значения не требуется для такого оператора, это сильнопредполагает, что значение может быть загружено из переменной только в том случае, если оно используется.

Как итог, ваш клиент действительно справедливо расстроен, когда видит, что переменная загружена.Такое поведение может соответствовать стандарту, если вы расширите его толкование, но оно явно находится на грани приемлемости.

6 голосов
/ 28 февраля 2011

Чтение обратно кажется ближе к стандарту (особенно учитывая, что чтение изменчивой переменной может привести к значению, отличному от записанного), но я почти уверен, что это не то, что ожидается большинством кода, использующего volatile особенно в тех случаях, когда чтение или запись изменчивой переменной вызывает некоторые другие эффекты.

изменчивость в целом не очень хорошо определена - «Что представляет собой доступ к объекту, который имеет тип volatile-квалифицированный, определяется реализацией. "

Редактировать: Если бы мне пришлось создавать компилятор, я бы не считывал переменную, если она не используется, и перечитывал ее, если есть, но с предупреждением. Тогда должен ли быть использован бросок к пустоте?

(void) v;

наверняка должно быть единым целым, и, учитывая это, у меня нет никаких причин для

(void) v = exp;

не быть. Но в любом случае я бы дал предупреждение, объясняющее, как получить другой эффект.

Кстати, если вы работаете над компилятором, вы, вероятно, имеете кого-то в контакте с комитетом C, заполнение официального отчета о дефектах принесет вам обязательную интерпретацию (ну, есть риск, что DR будет классифицирован как «не дефект» "без намека на то, что они хотят ...)

1 голос
/ 28 февраля 2011

Язык в стандарте ничего не говорит о чтении энергозависимой переменной, только то, что является значением выражения присваивания, которое а) определяется семантикой C, а не содержимым переменной и б) здесь не используется, поэтому не нужно рассчитывать.

...