Стивен Прата - Язык программирования C. Лекции и упражнения (6-е изд.) 2015
- Название:Язык программирования C. Лекции и упражнения (6-е изд.) 2015
- Автор:
- Жанр:
- Издательство:Вильямс
- Год:0101
- ISBN:нет данных
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Стивен Прата - Язык программирования C. Лекции и упражнения (6-е изд.) 2015 краткое содержание
Язык программирования C. Лекции и упражнения (6-е изд.) 2015 - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
/* file1.с -- определение нескольких глобальных констант */ const double PI = 3.14159; const char * MONTHS [12] =
{"Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль",
"Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь"];
/* file2.c -- использование констант, определенных где-то в другом месте */ extern const double PI; extern const * MONTHS]];
Второй подход предполагает помещение констант во включаемый файл. Здесь придется предпринять дополнительное действие, связанное с применением статического внешнего класса хранения:
/* constant.h -- определение нескольких глобальных констант */ static const double PI = 3.14159; static const char * MONTHS [12] =
{"Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль",
"Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь");
/* file1.с -- использование констант, определенных где-то в другом месте */ #include "constant.h"
/* file2.c -- использование констант, определенных где-то в другом месте */ #include "constant.h"
Если вы не укажете ключевое слово static, то включение заголовочного файла constant.h в file1.с и file2.c приведет к тому, что каждый файл будет иметь определяющее объявление того же самого идентификатора, что стандартом ANSI не поддерживается. (Тем не менее, некоторые компиляторы разрешают это.) Делая идентификатор внешним и статическим, вы фактически предоставляете каждому файлу о-г дельную копию данных. Такой прием не будет работать, если по замыслу файлы должны использовать эти данные для связи друг с другом, потому что каждый файл будет видеть только свою копию данных. Однако поскольку данные являются константными (из-за наличия ключевого слова const) и идентичными (т.к. оба файла включают тот же самый заголовочный файл), проблемы не возникают.
Преимущество подхода с заголовочным файлом состоит в том, что вы не обязаны помнить о применении определяющих объявлений в одном файле и ссылочных объявлений в другом; все файлы просто включают тот же самый заголовочный файл. Недостаток связан с тем, что данные дублируются. В предшествующих примерах это не приводит к значительной проблеме, но она может возникнуть, если в состав константных данных входят крупные массивы.
Квалификатор типа 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 они имеют следующие прототипы:
Читать дальшеИнтервал:
Закладка: