Изменчивый указатель на энергонезависимые данные

Предположим, у меня есть следующее объявление:

int* volatile x;

Я считаю, что это определяет "нормальную" переменную изменчивого указателя.

Для меня это может означать одно из двух:

Первое предположение

Указатель может измениться, но число не изменится без уведомления. Это означает, что какой-то другой поток (о котором компилятор не знает) может изменить указатель, но если старый указатель указывал на «12», то новый указатель (новое значение указателя, потому что поток изменяет его ) указывало бы на другое «12».

Мне это кажется довольно бесполезным, и я предполагаю, что это не то, чем является настоящая операция.

Вторая догадка

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

Итак, мой вопрос заключается в следующем:

Что на самом деле делает объявление энергозависимого указателя на энергонезависимые данные?


person DarthRubik    schedule 04.08.2016    source источник
comment
Спецификатор volatile не имеет ничего общего с потоками.   -  person David Schwartz    schedule 05.08.2016
comment
@DavidSchwartz Это сообщает компилятору, что он не знает, как переменная будет изменена (т.е. она может быть изменена потоком, о котором он не знает..... Я думаю, что я не понял, что я упоминаю в вопрос)   -  person DarthRubik    schedule 05.08.2016
comment
Я предполагаю, что это зависит от оптимизатора, но для компилятора может показаться законным не перезагружать значение, если указатель не изменился. На практике кажется, что это будет больше работы, чем просто перезагрузка указанного значения, поэтому оптимизатор, вероятно, просто перезагрузит все.   -  person nate    schedule 05.08.2016
comment
volatile просто означает его наблюдаемое поведение для чтения и записи этого значения. Таким образом, любое упоминание x означает, что вы должны прочитать значение x и не можете предполагать, что знаете его/вы должны записать значение x, и не можете предполагать, что это бессмысленно.   -  person GManNickG    schedule 05.08.2016
comment
@DarthRubik Нет, это не так. Это не имеет ничего общего с потоками.   -  person David Schwartz    schedule 05.08.2016
comment
@DavidSchwartz Если компилятор не знает, что поток существует (т. Е. Другая программа, которая изменяет память в оперативной памяти этой программы)   -  person DarthRubik    schedule 05.08.2016
comment
@DarthRubik Компилятор обычно понятия не имеет, существуют ли потоки или нет, я не знаю ни одной системы, в которой он знал бы так или иначе. Но, тем не менее, язык определенно не говорит, что volatile имеет какое-то отношение к знаниям компилятора о потоках.   -  person David Schwartz    schedule 05.08.2016
comment
@DavidSchwartz Я знаю ..... это был просто пример его использования ..... другими примерами являются аппаратные прерывания, аппаратные регистры (такие как DMA, управление выводами и т. д.), разделяемая память ....... небо это предел   -  person DarthRubik    schedule 05.08.2016
comment
@DarthRubik Дело в том, что потоки не являются одним из них. Спецификатор volatile не имеет ничего общего с потоками.   -  person David Schwartz    schedule 05.08.2016
comment
@DavidSchwartz Обязательно: cxx.isvolatileusefulwiththreads.com   -  person Borgleader    schedule 05.08.2016
comment
@DavidSchwartz Я никогда не использовал потоки в С++, наверное, я просто предположил, что они будут похожи на прерывания в микроконтроллерах.   -  person DarthRubik    schedule 05.08.2016
comment
все комментарии относятся к тому факту, что volatile нельзя использовать для удовлетворения каких-либо известных требований к написанию безопасного для потоков кода. Это не означает, что volatile не меняет поведение многопоточного кода в некоторых случаях, просто изменения не обязательно соответствуют вашим ожиданиям.   -  person pm100    schedule 05.08.2016
comment
@DavidSchwartz Вы говорите, что volatile никогда нельзя использовать с потоками? На любой существующей архитектуре?   -  person curiousguy    schedule 19.07.2018
comment
@curiousguy Единственный случай, когда volatile полезен с потоками, - это когда соответствующий язык, потоки или документация компилятора говорят, что у них есть определенная семантика. В противном случае вы просто предполагаете, что он будет продолжать делать то, что вы хотите, потому что он делал то, что вы хотите, когда вы пытались это сделать. Все разумные стандарты многопоточности обеспечивают гарантированные способы получения любой семантики, которая вам нужна, и вы должны использовать их, потому что они гарантированы.   -  person David Schwartz    schedule 19.07.2018
comment
@DavidSchwartz Вы говорите, что на практике volatile не гарантирует создание семантики потребления на процессоре, который потреблял?   -  person curiousguy    schedule 19.07.2018
comment
@любопытный парень Да. На самом деле, я бы сказал, что практические гарантии — это оксюморон.   -  person David Schwartz    schedule 18.10.2018
comment
@DavidSchwartz Какая правдоподобная семантика, приписываемая volatile, позволяет нарушить семантику потребления? На каком процессоре?   -  person curiousguy    schedule 18.10.2018
comment
@curiousguy Если вы задаете вопросы по C и C ++ и вам нужно даже подумать о деталях реализации ЦП, вы уже делаете что-то невероятно специфичное для платформы. Я допускаю, что volatile может иметь некоторую специфичную для платформы семантику, которая может оказаться полезной на некоторых платформах. Слишком много раз я видел ужасный сбой кода, потому что были сделаны предположения о том, что будущие компиляторы или процессоры смогут оптимизировать. Мы должны принять решение перестать совершать эту ошибку рано или поздно. Я не приму твоего приглашения совершить эту ошибку.   -  person David Schwartz    schedule 18.10.2018
comment
@DavidSchwartz Насколько ошибочно предполагать, что ни один будущий оптимизатор C/C++ никогда не будет предполагать, что чтение изменчивой переменной дает предсказуемое значение? Это уничтожило бы весь смысл.   -  person curiousguy    schedule 18.10.2018
comment
@curiousguy Представьте себе, есть ли в будущем ЦП, в котором предоставление этих предсказуемых значений требует огромных затрат производительности, но обеспечение всей семантики, фактически необходимой для поддержки стандарта (например, volatile std::sig_atomic_t), требует минимальной производительности. Разве разумные компиляторы для этой платформы не будут реализовывать только то, что на самом деле требует стандарт? (И наихудший сценарий, который можно вообразить, был бы, если у авторов компиляторов не было бы другого выбора, кроме как снизить производительность, потому что люди слушали ваш совет и мудро полагались на поведение, не гарантированное стандартом.)   -  person David Schwartz    schedule 18.10.2018
comment
@DavidSchwartz Для такого процессора нет рынка.   -  person curiousguy    schedule 19.10.2018
comment
Когда-то люди действительно утверждали, что не существует рынка процессоров, выполняющих операции с памятью не по порядку. Я предпочитаю учиться на прошлых ошибках, чем повторять их.   -  person David Schwartz    schedule 19.10.2018


Ответы (2)


int *volatile x; объявляет изменчивый указатель на энергонезависимое целое число.

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

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


[ РЕДАКТИРОВАТЬ ] Чтобы ответить на комментарий @DavidSchwartz, я должен отметить, что «перечитать из памяти» — это (не педантично точное, но обычно используемое AFAIK) сокращение для «как если бы он был перечитан из памяти в абстрактной машине».

Например, C11 черновик N1570 6.7.3/7 гласит:

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

В том же проекте есть сноска для 6.5.16/3 (операторы присваивания):

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

Таким образом, в конце концов, volatile не требует чтения физической памяти, но наблюдаемое поведение совместимой реализации должно быть как если бы оно было сделано независимо.

person dxiv    schedule 04.08.2016
comment
Это очень распространенный миф. Квалификатор volatile не требует повторного считывания значений из памяти. Если бы это произошло, это привело бы к абсурдно огромным падениям производительности без всякой причины. - person David Schwartz; 05.08.2016
comment
@DavidSchwartz А? Это именно то, что это означает. См. 5.1.2.3/6 в C11. (И да, это может привести к заметному падению производительности по сравнению с энергонезависимым использованием) - person M.M; 05.08.2016
comment
ЦП должен выполнить чтение (или запись), но без ограничения памяти нет гарантии, что разные ядра увидят одно и то же значение. - person Richard Critten; 05.08.2016
comment
Летучие редко используются. Он используется в таких местах, как драйверы, использующие отображение памяти для чтения значений с оборудования, где компилятор, оптимизирующий многократное чтение, влияет на работу. Аналогично при ожидании DMA для завершения записи путем опроса места записи. - person ash; 05.08.2016
comment
@MM Нигде не говорится, что что-то нужно перечитывать из памяти. А современные системы не повторно считывают из памяти при доступе к volatiles. - person David Schwartz; 05.08.2016
comment
@DavidSchwartz говорит, что доступ к изменчивым объектам оценивается строго в соответствии с правилами абстрактной машины, а правила абстрактной машины заключаются в том, что чтение переменной считывается из области хранения, назначенной этой переменной. Любая система, которая не соблюдает это правило, является несоответствующей. - person M.M; 05.08.2016
comment
@MM Либо область хранения, назначенная этой переменной, означает, где она сейчас хранится, либо это означает основную память. В любом случае утверждение ложно. Если это означает, что где бы она ни хранилась сейчас, то это разрешило бы чтение из регистра, если бы там была сохранена переменная тогда. Если это означает основную память, это будет означать, что вы будете читать из основной памяти, даже если переменная в настоящее время хранится, скажем, в кэше L2 другого ядра, что приведет к бессмысленным результатам. Так что нет, вы все еще ошибаетесь. - person David Schwartz; 05.08.2016
comment
@DavidSchwartz в абстрактной машине назначается место хранения, и цитата в моем предыдущем комментарии говорит, что реализация должна это реализовать. Ничего общего с регистрами, основной памятью, кешем и т. д. Возможно, вы пытаетесь сказать, что распространенное заблуждение заключается в том, что компиляторы содержат ошибки из-за реализации volatile. (NB. Не отвечая дальше, это обсуждение не подходит для комментариев; сделайте или дайте ссылку на вопрос по теме) - person M.M; 05.08.2016
comment
@ M.M Так ты все еще говоришь, что это нужно перечитывать по памяти или нет? - person David Schwartz; 05.08.2016
comment
@DavidSchwartz: Вы читали отчет о дефекте volatile (DR476) для C11? - person too honest for this site; 05.08.2016
comment
@ash: Учитывая, что C широко используется во встроенных устройствах, а это подавляющее большинство архитектур, volatile очень широко используется. Это просто не гарантирует согласованности памяти, которая должна быть гарантирована программным обеспечением для прослушивания (например, наличием области памяти без кэширования/неразделяемого/строго упорядоченного). Опрос какой-либо области памяти для завершения прямого доступа к памяти — плохая идея. DMA hgas, чтобы гарантировать, что это завершено. PCI(e) обеспечивает специальный доступ к тому, что должно использоваться контроллерами прямого доступа к памяти. Не то чтобы в поле не было сломанных устройств... - person too honest for this site; 05.08.2016
comment
@Olaf И это явно допускает именно те типы оптимизации, которые предотвратили бы повторное чтение значений из памяти, когда реализация знает, что это не имеет необходимых побочных эффектов. Итак, опять же, утверждение, что требуется повторное чтение из памяти, ложно. - person David Schwartz; 05.08.2016
comment
Эм - да. Но с предоставленной информацией я не вижу оснований предполагать, что у нас есть такой сценарий. Все может быть по-другому для автоматической переменной, в которую не передается указатель на нее (volatile-правильно). - person too honest for this site; 05.08.2016
comment
Ваше новое изменение делает этот ответ все еще полностью вводящим в заблуждение. Вы говорите, но наблюдаемое поведение соответствующей реализации должно быть таким, как если бы она была сделана независимо от того, что, как вы уже согласились, НЕ соответствует действительности. Поскольку мы согласны, что он может изменить кеш, он может изменить память, он может изменить регистр, откуда, черт возьми, вы знаете, что наблюдать? Что, если другой поток наблюдает за памятью, а изменение было внесено в регистр? (И вы можете видеть это в текущих реализациях, где ограничения памяти необходимы, чтобы сделать изменение наблюдаемым ДЛЯ ДРУГИХ ПОТОКОВ, и они не генерируются в коде.) - person David Schwartz; 19.07.2018
comment
@DavidSchwartz Извините, но вы путаете абстрактные машинные правила, которые всегда являются обязательными, с оптимизациями, которые совместимым реализациям разрешено выполнять на основе определенного глубокого знания реальной машины. . Если вы оспариваете это, было бы более продуктивно опубликовать отдельный вопрос с изложением этого. - person dxiv; 20.07.2018

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

Однако volatile ничего не говорит об указываемом значении. Поэтому он может меняться и может иметь разные значения в разных потоках.

person Steve Emmerson    schedule 04.08.2016
comment
...различные кеши это неправильно. Для этого вам нужно использовать соответствующий забор памяти. - person Richard Critten; 05.08.2016
comment
Не могли бы вы пояснить, что вы имеете в виду под вторым абзацем? - person M.M; 05.08.2016
comment
@MM указатель - это переменная, которая находится в пространстве памяти; это пространство рассматривается как изменчивое (при доступе к этой переменной), но компилятор может оптимизировать чтение памяти, на которую указывает эта переменная (например, *x + *x требует двух чтений указателя, но может дать только одно чтение). чтение адреса, на который указывает x). - person ash; 05.08.2016
comment
@ash, ваш комментарий верен, но я не понимаю, как он может меняться и может иметь разные значения в разных потоках. резюмирует ваш комментарий. Все переменные имеют только одно значение, независимо от количества потоков. - person M.M; 05.08.2016
comment
@ash может дать только одно чтение Какой процессор делает это преобразование целесообразным? Есть ли компилятор, который пытается выполнить такое преобразование? - person curiousguy; 19.07.2018
comment
Процессоры @curiousguy x86 подходят. Они считывают данные из памяти и кэшируют до того, как они понадобятся, и повторно используют прочитанные значения в инструкциях. - person David Schwartz; 19.07.2018
comment
@DavidSchwartz Какой процессор может считывать данные до того, как они потребуются, когда указатель еще не прочитан? - person curiousguy; 19.07.2018
comment
@curiousguy Процессор x86. Он выполняет спекулятивные выборки. Нет теоретического предела тому, насколько умным он может быть, хотя, конечно, есть практические пределы. - person David Schwartz; 19.07.2018
comment
@DavidSchwartz Я вижу: ЦП может попытаться угадать адрес, выполнить предварительную выборку, загрузить адрес, проверить предположение, использовать предварительно выбранное значение. Верный? - person curiousguy; 19.07.2018
comment
@любопытный парень Да. И это действительно так. И даже если ни один текущий процессор этого не делает, было бы безумием писать код, предполагая, что будущие процессоры не будут этого делать. Если ваш код полагается на то, что поведение останется неизменным, когда это поведение не гарантировано, это ужасный код. Разумные системы предоставляют достаточные гарантии для написания программного обеспечения, и вы должны использовать эти гарантии, чтобы ваш код не дал сбоев с новым процессором, новым компилятором, оптимизацией или чем-то еще. - person David Schwartz; 19.07.2018
comment
Я понимаю. Но то, загружает ли *x + *x *x один или два раза, когда x имеет изменчивый тип, не влияет на корректность программ машинного перевода. (Единственный случай, когда может иметь значение то, что *x читается ровно дважды, - это когда чтение этого адреса имеет побочные эффекты. Тогда адрес будет отображен таким образом, что он не будет кэшироваться, и ЦП не будет пытаться оптимизировать.) - person curiousguy; 19.07.2018
comment
@curiousguy Да, но может ли i+=*x; j+=*y; i+=*x; загружать *x один или два раза. Зачем полагаться на поведение, которое явно не гарантируется, когда ваша платформа предоставляет вам механизмы, поведение которых гарантировано? Это безумие. - person David Schwartz; 19.07.2018
comment
@DavidSchwartz механизмы, поведение которых гарантировано Но комитет признал, что порядок потребления был плохо определен (действительно, это нелепая спецификация), на самом деле не поддерживается большинством компиляторов (или даже с попыткой поддержки, которая случайно терпит неудачу) , где люди могли бы вместо этого использовать volatile (на тех процессорах, у которых load=consume). - person curiousguy; 20.07.2018
comment
Давайте продолжим обсуждение в чате. - person David Schwartz; 20.07.2018
comment
@curiousguy — компиляторы оптимизируют общие подвыражения. Итак, в моем примере *x + *x без оптимизации преобразуется в машинные инструкции разыменования двух указателей. При оптимизации он будет прочитан только один раз (скорее всего - тут сложнее). Использование ключевого слова volatile предотвратит эту оптимизацию. Это важно, потому что иногда чтение памяти представляет собой нечто большее, например чтение памяти, отображаемой с аппаратного устройства, и изменение количества операций чтения может привести к неожиданным результатам. - person ash; 26.07.2018
comment
@curiousguy - что касается многопоточных приложений, одно чтение по сравнению с двумя может иметь очень реальные различия в результатах, потому что другой поток может изменять значение между чтениями. И да, сроки безумно сжаты, но это может произойти и происходит. - person ash; 26.07.2018
comment
@ash Да, иногда прочитанная ячейка памяти не имеет семантики чтения. (Точно так же, как в Linux read из файла, который является устройством, может вызвать любое действие, которое код драйвера устройства сочтет целесообразным.) Использование устройства с отображением памяти требует соответствующей настройки блока управления памятью ЦП, чтобы избежать кэширования. OTOH, читающий атомарную переменную (поле volatile в Java), имеет семантику чтения. - person curiousguy; 27.07.2018
comment
@ash *x + *x, без оптимизации преобразуется в машинные инструкции разыменования двух указателей с использованием коммутативного оператора, такого как operator+(int,int), обнуляет его, но у вас есть проблема с оценкой, когда вы выполняете два действия чтения в одном операторе без точка последовательности. В общем, вам нужно наложить порядок на операции: рассмотрим *x - *x. Поведение будет неопределенным и зависит от компилятора. - person curiousguy; 27.07.2018
comment
@curiousguy - это простое выражение *x + *x предназначено только для пояснений. - person ash; 28.07.2018