Volatile не работает должным образом
рассмотрим этот код:
struct A{
volatile int x;
A() : x(12){
}
};
A foo(){
A ret;
//Do stuff
return ret;
}
int main()
{
A a;
a.x = 13;
a = foo();
}
используя g++ -std=c++14 -pedantic -O3
я получаю эту сборку:
foo():
movl , %eax
ret
main:
xorl %eax, %eax
ret
по моей оценке переменная x
должно быть написано, по крайней мере, три раза (возможно, четыре), но это даже не написано после (функция foo даже не вызывается!)
еще хуже, когда вы добавить inline
ключевое слово foo
вот результат:
main:
xorl %eax, %eax
ret
Я думал, что volatile означает, что каждый прочитанный или написать должны произойдет, даже если компилятор не может видеть точку чтения/записи.
что здесь происходит?
обновление:
сдачи декларации A a;
снаружи main вот так:
A a;
int main()
{
a.x = 13;
a = foo();
}
генерирует этот код:
foo():
movl , %eax
ret
main:
movl , a(%rip)
xorl %eax, %eax
movl , a(%rip)
ret
movl , a(%rip)
ret
a:
.zero 4
что ближе к тому, что вы ожидали бы....Я еще больше запутался, чем когда-либо
2 ответов
Visual C++ 2015 не оптимизирует назначения:
A a;
mov dword ptr [rsp+8],0Ch <-- write 1
a.x = 13;
mov dword ptr [a],0Dh <-- write2
a = foo();
mov dword ptr [a],0Ch <-- write3
mov eax,dword ptr [rsp+8]
mov dword ptr [rsp+8],eax
mov eax,dword ptr [rsp+8]
mov dword ptr [rsp+8],eax
}
xor eax,eax
ret
то же самое происходит и с /O2 (Максимальная скорость) и /Ox (полная оптимизация).
летучие записи сохраняются также gcc 3.4.4, используя как -O2, так и-O3
_main:
pushl %ebp
movl , %eax
movl %esp, %ebp
subl , %esp
andl $-16, %esp
call __alloca
call ___main
movl , -4(%ebp) <-- write1
xorl %eax, %eax
movl , -4(%ebp) <-- write2
movl , -8(%ebp) <-- write3
leave
ret
используя оба этих компилятора, если я удаляю ключевое слово volatile, main () становится по существу пустым.
Я бы сказал, что у вас есть случай, когда компилятор чрезмерно агрессивно (и неправильно IMHO) решает, что поскольку "a" не используется, операции над ним не являются необходимыми и не учитывают изменчивый элемент. Создание самого " а " изменчивым может получить то, что вы хотите, но поскольку у меня нет компилятора, который воспроизводит это, я не могу сказать наверняка.
в прошлом (хотя это по общему признанию конкретного Microsoft), https://msdn.microsoft.com/en-us/library/12a04hfd.aspx говорит:
Если элемент структуры помечен как volatile, то volatile распространяется на все структура.
что также указывает на поведение, которое вы видите проблемы компилятора.
наконец, если вы сделаете " a " глобальной переменной, то понятно, что компилятор менее стремится считать ее неиспользуемой и отбросить ее. Глобальные переменные extern по умолчанию, поэтому невозможно сказать, что глобальный " a " не используется, просто посмотрев на основную функцию. Некоторые другие единицы компиляции (.cpp-файл) может использовать его.
страница GCC на летучие доступ дает некоторое представление о том, как это работает:
стандарт рекомендует компиляторам воздерживаться от оптимизации доступа к изменчивым объектам, но оставляет его реализацию определенной относительно того, что представляет собой изменчивый доступ. Минимальное требование заключается в том, что в точке последовательности все предыдущие обращения к летучим объектам стабилизировались и никаких последующих обращений не произошло. Таким образом, реализация свободна переупорядочить и объединить изменчивые обращения, которые происходят между точками последовательности, но не могут сделать это для обращений через точку последовательности. Использование летучих не позволяет нарушать ограничение на обновление объектов несколько раз между двумя точками последовательности.
in C standardese:
§5.1.2.3
2 доступ к изменчивому объекту, изменение объекта, изменение файла, или вызов функции, которая выполняет любую из этих операций все сторону эффекты, 11) которые являются изменениями в состоянии среда выполнения. Оценка выражения может производить сторону эффекты. В определенных точках последовательности выполнения точки последовательности, все побочные эффекты предыдущих оценок будут завершены и никакие побочные эффекты последующих оценок не будут иметь проходить. (Резюме пунктов последовательности приводится в приложении С.)
3 в абстрактная машина, все выражения вычисляются, как указано по семантике. Фактическая реализация не должна оценивать часть выражение, если оно может вывести, что его значение не используется и что нет необходимые побочные эффекты произведены (включая любое причиненное путем вызывать а функция или доступ к Летучему объекту).
[...]
5 минимум на соответствующая реализация являются:
- в последовательность точки, изменчивые объекты стабильны в том смысле, что предыдущие обращения завершены, а последующие обращения еще не завершены произошло. [...]
я выбрал стандарт C, потому что язык проще, но правила по существу одинаковы в C++. См. правило "как будто".
теперь на моей машине,-O1
не оптимизирует вызов foo()
, так что давайте использовать -fdump-tree-optimized
посмотреть разница:
-O1
*[definition to foo() omitted]*
;; Function int main() (main, funcdef_no=4, decl_uid=2131, cgraph_uid=4, symbol_order=4) (executed once)
int main() ()
{
struct A a;
<bb 2>:
a.x ={v} 12;
a.x ={v} 13;
a = foo ();
a ={v} {CLOBBER};
return 0;
}
и -O3
:
*[definition to foo() omitted]*
;; Function int main() (main, funcdef_no=4, decl_uid=2131, cgraph_uid=4, symbol_order=4) (executed once)
int main() ()
{
struct A ret;
struct A a;
<bb 2>:
a.x ={v} 12;
a.x ={v} 13;
ret.x ={v} 12;
ret ={v} {CLOBBER};
a ={v} {CLOBBER};
return 0;
}
gdb
показывает в обоих случаях, что a
в конечном счете оптимизирован, но мы беспокоимся о foo()
. Свалки показывают нам, что GCC переупорядочил доступы так, что foo()
даже не нужно и впоследствии весь код в main()
оптимизирован подальше. Это правда? Давайте посмотрим выход сборки для -O1
:
foo():
mov eax, 12
ret
main:
call foo()
mov eax, 0
ret
это, по сути, подтверждает, что Я сказал выше. Все оптимизировано: единственная разница заключается в том, является ли вызов foo()
как хорошо.