У меня есть простой фрагмент кода C++, как показано ниже:
int A;
int B;
void foo() {
A = B + 1;
// asm volatile("" ::: "memory");
B = 0;
}
Когда я компилирую этот код, сгенерированный код сборки переупорядочивается следующим образом:
foo():
mov eax, DWORD PTR B[rip]
mov DWORD PTR B[rip], 0
add eax, 1
mov DWORD PTR A[rip], eax
ret
B:
.zero 4
A:
.zero 4
Однако, когда я добавляю ограничение памяти (строка с комментариями в коде C++), инструкции не переупорядочиваются. Насколько я понимаю, добавление квалификатора volatile к переменной также должно предотвратить изменение порядка инструкций. Итак, я изменил код, чтобы добавить volatile к переменной B:
int A;
volatile int B;
void foo() {
A = B + 1;
B = 0;
}
К моему удивлению, сгенерированный ассемблерный код по-прежнему показывает переупорядоченные инструкции. Может ли кто-нибудь объяснить, почему квалификатор volatile не предотвратил изменение порядка инструкций в этом случае?
Код доступен в godbolt
Не уверен, о чем ваш вопрос, но вы только отметили B volatile, и доступ к нему не переупорядочивается (сначала читайте, ноль секунд). Если вы хотите, чтобы запись в A была заказана, вам также нужно отметить ее как изменчивую.
Кроме того, то, что происходит и какой ассемблерный код может быть сгенерирован, во многом зависит от компилятора, версии компилятора и флагов оптимизации. Пожалуйста, включите все эти детали в сам вопрос. А также, пожалуйста, сообщите нам, на каком языке вы действительно пишете свой код. Изначально вы пометили C++11 (который я отредактировал на C++), но в самом вопросе вы упоминаете только C. C и C++ - два очень разных языка.
@Someprogrammerdude Вы могли видеть ссылку, которую я разместил, там есть вся необходимая информация godbolt
@Шут Ах! Я полагаю, что неправильно понял значение «переупорядочить». Если переменная является изменчивой, это означает, что любой доступ (чтение и запись) к ней не будет изменен. Первоначально я думал, что это означает, что предложения кода не будут переупорядочены.
вся необходимая информация должна быть в вопросе, а не за ссылками на внешние сайты
Пожалуйста, старайтесь, чтобы ваши вопросы были автономными. Внешние ссылки могут исчезнуть без предупреждения, или их содержимое может измениться и сделать их неактуальными.
ссылка godbolt на C++, так почему вы говорите о C в вопросе? О каком из них идет речь?
@Someprogrammerdude Приношу извинения за неудобства, вызванные размещением внешней ссылки. В будущем я позабочусь о том, чтобы мои вопросы и ответы были автономными.
@ 463035818_is_not_an_ai Извинения за путаницу в вопросе. Я уже исправил нестыковку, и вопрос действительно про C++
Доступы volatile не переупорядочиваются с любыми другими доступами volatile, в том числе к другим местоположениям, поэтому изменение обеих переменных даст вам ожидаемый asm. Но не используйте atomic<T> с memory_order_relaxed вместо этого, если другой поток будет обращаться к этим глобальным переменным. (Когда использовать volatile с многопоточностью?)
Спасибо за ваш ответ @PeterCordes. Я сделал некоторые выводы относительно использования volatile. Вы можете найти их в разделе комментариев ответа Нейта Элдриджа. Пожалуйста, не стесняйтесь проверить их.





Насколько я понимаю, добавление квалификатора volatile к переменной также должно предотвратить переупорядочивание инструкций.
Это большое упрощение. Хотя стандарт C++ не очень явно определяет семантику volatile (говоря только, что «доступ оценивается строго в соответствии с правилами абстрактной машины»), неписаное правило заключается в том, что volatile объекты обрабатываются так, как если бы некий внешний объект (например, аппаратное обеспечение ввода-вывода) может считывать и записывать их асинхронно, и что и чтение, и запись являются побочными эффектами, которые может наблюдать внешний объект. Таким образом, каждое чтение/запись объекта volatile (размером машинного слова или меньше) должно приводить к выполнению ровно одной инструкции загрузки/сохранения.
Из этого следует, что объекты загрузки и сохранения в volatile не будут переупорядочиваться друг с другом. Но в вашей программе A не является volatile, поэтому будем считать, что внешняя сущность его не видит. Следовательно, не имеет значения, как упорядочены обращения к A по отношению к обращениям к B или чему-то еще, и компилятор может изменить их порядок. Такие инструкции, как add eax, 1, которые вообще не обращаются к памяти, также являются честной игрой; внешний объект также не может видеть регистры машины.
Судя по вашему использованию тега concurrency , это одна из многих причин, по которой volatile не является правильным подходом к совместному использованию переменных между потоками, потому что, в отличие от «внешнего объекта», другой поток имеет доступ к вашему не- volatile переменные. В старые времена, до C++11, люди использовали volatile, потому что это было все, что было, и вы могли заставить его работать с использованием явных функций барьера памяти, если бы вы знали что-то о том, как ваш компилятор выполняет оптимизацию (что было обычно без документов). Начиная с C++11 у нас есть std::atomic, и это единственный правильный способ справиться с разделением потоков между потоками, но, к сожалению, ассоциация с volatile сохраняется в устаревших документах и умах старожилов. См. Почему volatile не считается полезным в многопоточном программировании на C или C++? подробнее.
Также актуально: Вводит ли ключевое слово C++ volatile ограничение памяти? (Нет, как вы обнаружили.)
Спасибо за ваше сообщение! Изучив квалификатор volatile, я пришел к выводу, что он служит трем основным целям: 1. Он гарантирует, что переменная volatile всегда будет считываться из памяти, а не из регистра. 2. Это гарантирует, что компилятор не будет оптимизировать код, связанный с переменной volatile. 3. Это гарантирует, что порядок инструкций, включающих переменную volatile и другую переменную volatile, не будет изменен. Я ничего не пропустил?
asm volatile ...— это специфичное для компилятора расширение, которое полностью отличается от стандартного квалификатора C++volatile.