Ответ 1
Переупорядочение, которое может выполнять GCC, не связано с переупорядочением процессора (x86).
Начните с переупорядочения компилятора. Правила языка C таковы, что GCC запрещается переупорядочивать volatile
нагрузки и сохранять обращения к памяти по отношению друг к другу или удалять их, когда между ними происходит точка последовательности (благодаря bobc для этого пояснения). То есть на выходе сборки появятся эти обращения к памяти и будут упорядочены точно в указанном порядке. С другой стороны, образы non- volatile
могут быть переупорядочены по отношению ко всем другим доступам, volatile
или нет, при условии, что (по правилу as-if) конечный результат вычисления одинаков.
Например, загрузка без volatile
в C-коде может выполняться столько раз, сколько код говорит, но в другом порядке (например, если компилятор считает более удобным сделать это раньше или позже, когда больше регистры доступны). Это можно сделать меньше раз, чем говорит код (например, если копия значения по-прежнему доступна в регистре в середине большого выражения). Или его можно даже удалить (например, если компилятор может доказать бесполезность загрузки или если он полностью переместил переменную в регистр).
Чтобы предотвратить переупорядочивание компилятора в другое время, вы должны использовать специфический для компилятора барьер. Для этой цели GCC использует __asm__ __volatile__("":::"memory");
.
Это отличается от переупорядочения процессора, a.k.a. модели упорядочения памяти. Древние процессоры выполняли инструкции точно в том порядке, в котором они появились в программе; Это называется программным упорядочением или сильной моделью упорядочения памяти. Однако современные процессоры иногда прибегают к "читам", чтобы работать быстрее, немного ослабив модель памяти.
То, как процессоры x86 ослабили модель памяти, задокументировано в руководствах разработчиков программного обеспечения Intel, том 3, глава 8, раздел 8.2.2 "Заказ памяти в P6 и более поздних семействах процессоров". Это, в частности, то, что он читает:
- Чтения не переупорядочиваются с другими чтениями.
- Писания не переупорядочиваются с помощью более старых чтений.
- Записи в памяти не переупорядочиваются с другими записями с [некоторыми] исключениями.
- Считывание может быть переупорядочено с помощью более старых записей в разных местах, но не с более старых записей в том же месте.
- Считывание или запись не могут быть переупорядочены с инструкциями ввода/вывода, заблокированными инструкциями или инструкциями по сериализации.
- Считывание не может пройти более ранние инструкции LFENCE и MFENCE.
- Писания не могут проходить более ранние инструкции LFENCE, SFENCE и MFENCE.
- Инструкции LFENCE не могут проходить более ранние чтения.
- Команды SFENCE не могут передавать более ранние записи.
- Инструкции MFENCE не могут проходить более ранние чтения или записи.
Он также дает очень хорошие примеры того, что можно и не может переупорядочить, в разделе 8.2.3 "Примеры, иллюстрирующие принципы упорядочения памяти".
Как вы можете видеть, используются инструкции FENCE для предотвращения ненадлежащего перебора процессора x86.
Наконец, вы можете быть заинтересованы в этой ссылке, которая идет более подробно и поставляется с примерами сборки, которые вы жаждете.