+7 (495) 229-0436   shopadmin@itshop.ru 119334, г. Москва, ул. Бардина, д. 4, корп. 3
 
 
Вход
 
 
Каталог
 
 
Подписка на новости
Новости ITShop
Windows 7 и Office: Новости и советы
Обучение и сертификация Microsoft
Вопросы и ответы по MSSQLServer
Delphi - проблемы и решения
Adobe Photoshop: алхимия дизайна
 
Ваш отзыв
Оцените качество магазина ITShop.ru на Яндекс.Маркете. Если вам нравится наш магазин - скажите об этом Google!
 
 
Способы оплаты
 
Курс расчета
 
 1 у.е. = 92.01 руб.
 
 Цены показывать:
 
 
 
 
  
Новости, статьи, акции
 

Однажды вы читали о ключевом слове volatile…

06.12.2012 16:42
DmitryMe

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

Сегодня рассмотрим менее экзотический сценарий использования ключевого слова  volatile .

Стандарт C++ определяет так называемое наблюдаемое поведение как последовательность операций ввода-вывода и чтения-записи данных, объявленных как  volatile  (1.9/6). В пределах сохранения наблюдаемого поведения компилятору позволено оптимизировать код как угодно.

Вот например… Ваш код выделяет память средствами операционной системы, и вы хотите, чтобы операционная система выделила физические страницы памяти под всю запрошенную область. Многие ОС выделяют страницы при первом реальном обращении, а это может приводить к дополнительным задержкам, а вы, например, хотите этих задержек избежать и перенести их на более ранний момент. Вы можете написать такой код:

for( char* ptr = start; ptr < start + size; ptr += MemoryPageSize ) {
     *ptr;
}

Этот код проходит по всей области и читает по одному байту из каждой страницы памяти. Одна проблема - компилятор этот код оптимизирует и полностью удалит. Имеет полное право - этот код не влияет на наблюдаемое поведение. Ваши переживания о выделении страниц операционной системой и вызванных этим задержке к наблюдаемому поведению не относятся.

Что же делать, что же делать… А, точно! Давайте мы запретим компилятору оптимизировать этот код.

#pragma optimize( "", off )
for( char* ptr = start; ptr < start + size; ptr += MemoryPageSize ) {
     *ptr;
}
#pragma optimize( "", on )

Отлично, в результате…

1. использована  #pragma , которая делает код плохо переносимым, плюс…
2. оптимизация выключается полностью, а это увеличивает объем машинного кода в три раза, плюс в Visual C++, например, эта  #pragma  может быть использована только снаружи функции, соответственно, рассчитывать на встраивание этого кода в вызывающий код и дальнейшую оптимизацию тоже не приходится.

Здесь отлично помогло бы ключевое слово  volatile :

for( volatile char* ptr = start; ptr < start + size; ptr += MemoryPageSize ) {
     *ptr;
}

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

Теперь попробуем перезаписать память во имя безопасности и паранойи (это не бред, вот как это бывает в реальной жизни). В том посте упоминается некая волшебная функция  SecureZeroMemory() , которая якобы гарантированно перезаписывает нулями указанную область памяти. Если вы используете memset()  или эквивалентный ей написанный самостоятельно цикл, например, такой:

for( size_t index = 0; index < size; index++ )
     ptr[index] = 0;

для локальной переменной, то есть риск, что компилятор удалит этот цикл, потому что цикл не влияет на наблюдаемое поведение (доводы в том посте к наблюдаемому поведению тоже не относятся).

Что же делать, что же делать… А, мы "обманем" компилятор… Вот что можно найти по запросу "prevent memset optimization":

1. замена локальной переменной на переменную в динамической памяти со всеми вытекающими накладными расходами и риском утечки (сообщение в архиве рассылки linux-kernel)
2. макрос с ассемблерной магией (сообщение в архиве рассылки linux-kernel
3. предложение использовать специальный символ препроцессора, который запрещает встраивание  memset()  по месту и затрудняет компилятору оптимизацию (естественно, такая возможность должна быть поддержана в используемой версии библиотеки, плюс Visual C++ 10 умеет оптимизировать даже код функций, помеченных как не подлежащие встраиванию)
4. всевозможные последовательности чтения-записи с использованием глобальных переменных (кода становится заметно больше и такой код не потокобезопасен)
5. последующее чтение с сообщением об ошибке в случае, если считаны не те данные, что были записаны (компилятор имеет право заметить, что "не тех" данных оказаться не может, и удалить этот код)

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

Вы можете скомпилировать функцию перезаписи в отдельную единицу трансляции, чтобы компилятор "не увидел", что она делает. После очередной смены компилятора в игру вступит генерация кода линкером (LTCG в Visual C++, LTO в gcc или как это называется в используемом вами компилятором) - и компилятор прозреет и увидит, что перезапись памяти "не имеет смысла", и удалит ее.

Не зря появилась поговорка  you can"t lie to a compiler .

А что если посмотреть на типичную реализацию  SecureZeroMemory() ? Она по сути такая:

volatile char *volatilePtr = static_cast<volatile char*>(ptr);
for( size_t index; index < size; index++ )
        * volatilePtr = 0;
}

И все - компилятор более не имеет права удалять запись…

КРАЙНЕ НЕОЖИДАННО… вопреки всем суевериям  зачеркнутое утверждение выше неверно .

На самом деле - имеет. Стандарт говорит, что последовательность чтения-записи должна сохраняться только для данных с квалификатором  volatile . Вот для таких:

volatile buffer[size];

Если сами данные не имеют квалификатора  volatile , а квалификатор  volatile добавляется указателю на эти данные, чтение-запись этих данных уже не относится к наблюдаемому поведению:

buffer[size];
SecureZeroMemory(buffer, sizeof(buffer));

Вся надежда на разработчиков компилятора - в настоящий момент и Visual C++, и gcc не оптимизируют обращения к памяти через указатели с квалификатором volatile  - в том числе потому, что это один из важных сценариев использования таких указателей.

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

Причина этому банальна - это "не нужно".

Ситуации, когда переменная с подлежащими записи данными выходит из области видимости, а затем занимаемая ей память переиспользуется под другую переменную и из новой переменной выполняется чтение без предварительной инициализации, относятся к неопределенному поведению. Стандарт ясно говорит, что в таких случаях допустимо любое поведение. Обычно просто читается "мусор", который был записан в эту память раньше.

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

Использование указателей на  volatile  является, скорее всего, самым эффективным способом решения проблемы. Во-первых, разработчики компиляторов обычно сознательно выключают оптимизацию доступа к памяти. Во-вторых, накладные расходы минимальны. В-третьих, относительно легко проверить, работает этот способ или нет на конкретной реализации, - достаточно посмотреть, какой машинный код будет сгенерирован для тривиальных примеров выше из этого поста.

volatile  - не только для драйверов и операционных систем.

Дмитрий Мещеряков,
департамент продуктов для разработчиков

Ссылки по теме

  
Помощь
Задать вопрос
 программы
 обучение
 экзамены
 компьютеры
Бесплатный звонок
ICQ-консультанты
Skype-консультанты

Общая справка
Как оформить заказ
Тарифы доставки
Способы оплаты
Прайс-лист
Карта сайта
 
Бестселлеры
Курсы обучения "Atlassian JIRA - система управления проектами и задачами на предприятии"
Microsoft Windows 10 Профессиональная 32-bit/64-bit. Все языки. Электронный ключ
Microsoft Office для Дома и Учебы 2019. Все языки. Электронный ключ
Курс "Oracle. Программирование на SQL и PL/SQL"
Курс "Основы TOGAF® 9"
Microsoft Office 365 Персональный 32-bit/x64. 1 ПК/MAC + 1 Планшет + 1 Телефон. Все языки. Подписка на 1 год. Электронный ключ
Курс "Нотация BPMN 2.0. Ее использование для моделирования бизнес-процессов и их регламентации"
 

О нас
Интернет-магазин ITShop.ru предлагает широкий спектр услуг информационных технологий и ПО.

На протяжении многих лет интернет-магазин предлагает товары и услуги, ориентированные на бизнес-пользователей и специалистов по информационным технологиям.

Хорошие отзывы постоянных клиентов и высокий уровень специалистов позволяет получить наивысший результат при совместной работе.

В нашем магазине вы можете приобрести лицензионное ПО выбрав необходимое из широкого спектра и ассортимента по самым доступным ценам. Наши менеджеры любезно помогут определиться с выбором ПО, которое необходимо именно вам. Также мы проводим учебные курсы. Мы приглашаем к сотрудничеству учебные центры, организаторов семинаров и бизнес-тренингов, преподавателей. Сфера сотрудничества - продвижение бизнес-тренингов и курсов обучения по информационным технологиям.



 

О нас

 
Главная
Каталог
Новинки
Акции
Вакансии
 

Помощь

 
Общая справка
Как оформить заказ
Тарифы доставки
Способы оплаты
Прайс-лист
Карта сайта
 

Способы оплаты

 

Проекты Interface Ltd.

 
Interface.ru   ITShop.ru   Interface.ru/training   Olap.ru   ITnews.ru  
 

119334, г. Москва, ул. Бардина, д. 4, корп. 3
+7 (495) 229-0436   shopadmin@itshop.ru
Проверить аттестат
© ООО "Interface Ltd."
Продаем программное обеспечение с 1990 года