Квалификатор типа volatile
Квалификатор volatile сообщает компилятору, что переменная может иметь значение, которое изменяется действиями, внешними по отношению к программе. Он обычно указывается для аппаратных адресов и для данных, которые совместно используются с другими программами или потоками, выполняющимися одновременно. Например, адрес может ссылаться на текущее показание системных часов. Значение по этому адресу меняется с изменением показаний времени вне зависимости от того, что делает программа. Либо же адрес может применяться для получения информации, переданной, скажем, из другого компьютера.
Синтаксис этого квалификатора подобен синтаксису const:
volatile int loci; /* loci является изменчивой ячейкой */ volatile int * ploc; /* ploc указывает на изменчивую ячейку */
520 Глава 12
Эти операторы объявляют loci как значение volatile и ploc как указатель на значение volatile.
Концепция квалификатора volatile довольно интересна, и вы наверняка хотите узнать, почему комитет ANSI счел необходимым сделать volatile ключевым словом. Причина в том, что оно облегчает проведение оптимизации компилятором. Предположим, например, что есть такой код:
vail = х;
/* код, в котором х не используется */
val2 = х;
Интеллектуальный (оптимизирующий) компилятор может заметить, что объект х используется два раза без изменения в промежутке его значения. Он временно может сохранить значение х в регистре. Затем, когда х понадобится для val2, появляется возможность сэкономить время, прочитав значение из регистра, а не из исходной ячейки памяти. Такая процедура называется кешированием. Обычно кеширование является полезной оптимизацией, но не в случае, когда значение х изменяется в промежутке между двумя операторами каким-то другим действием. Без ключевого слова volatile у компилятора нет никаких средств, чтобы выяснить, может ли это случиться. Следовательно, во избежание ошибки компилятор не мог реализовать кеширование. Так было до выхода стандарта ANSI. Однако теперь, если в объявлении отсутствует ключевое слово volatile, компилятор может предположить, что значение не изменяется между двумя его применениями, и попытаться оптимизировать данный код.
Значение может быть одновременно и const, и volatile. Например, значение аппаратных часов обычно не должно изменяться программой, что делает его const, но может быть изменено внешним действием, поэтому оно является volatile. Просто поместите оба квалификатора в объявление, как показано ниже; порядок их следования роли не играет:
volatile const int loc;
const volatile int * ploc;
Квалификатор типа restrict
Ключевое слово restrict расширяет вычислительную поддержку, выдавая компилятору разрешение на оптимизацию определенных разновидностей кода. Оно может быть применено только к указателям и сообщает о том, что тот или иной указатель представляет собой единственное первичное средство доступа к объекту данных. Чтобы понять, почему это полезно, необходимо рассмотреть несколько примеров. Взгляните на показанные ниже объявления:
int ar[10];
int * restrict restar = (int *) malloc(10 * sizeof(int));
int * par = ar;
Здесь указатель restar является единственным первичным средством доступа в память, выделенную malloc(). Следовательно, он может быть квалифицирован с помощью ключевого слова restrict. Однако указатель par не является ни первичным, ни единственным средством доступа к данным в массиве ar, поэтому он не может быть квалифицирован как restrict.
Теперь рассмотрим несколько искусственный пример, в котором n имеет тип int:
for (n = 0; n < 10; n++)
{
par[n] += 5; restar[n] += 5;
Классы хранения, связывание и управление памятью 521
ar[n] *= 2; рar[n] += 3; restar[n] + = 3;
}
Зная, что указатель restar — единственное первичное средство доступа к блоку данных, на который он ссылается, компилятор может заменить два оператора, в которых задействован restar, одним оператором, дающим тот же результат:
restar[n] += 8; /* корректная замена */
Однако сведение в один двух операторов, в которых участвует par, вызывает вычислительную ошибку:
par[n] += 8; / * дает неправильный ответ */
Причина получения неправильного ответа связана с тем, что внутри цикла ar используется для изменения значения данных между двумя случаями доступа к тем же данным с помощью par.
Без ключевого слова restrict компилятор должен рассчитывать на худший случай, а именно — на то, что какой-то другой идентификатор мог изменить данные между двумя применениями указателя. При наличии restrict компилятор получает свободу в поиске вычислительных сокращений.
Ключевое слово restrict можно использовать в качестве квалификатора для параметров функции, которые являются указателями. Это значит, что компилятор может предположить, что внутри тела функции данные, указываемые такими параметрами, не модифицируются с помощью других идентификаторов, и есть возможность попробовать оптимизации, которые иначе бы не предпринимались. Например, библиотека С содержит две функции для копирования байтов из одного места в другое. В стандарте С99 они имеют следующие прототипы:
Читать дальше