Добавил:
СПбГУТ * ИКСС * Программная инженерия Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Язык Си. Лабораторные работы / Справочник. Часть 2 (СПбГУТ).doc
Скачиваний:
47
Добавлен:
10.09.2019
Размер:
801.79 Кб
Скачать

1.35.5. Способы передача параметров

Наибольшее распространение в языках программирования нашли два способа передачи параметров: передача по значению и передача по ссылке.

Строго говоря, в языке Си имеется только один способ передачи параметров – передача по значению. Однако применение указателей в качестве параметров функций позволяет моделировать передачу по ссылке. В данном разделе рассматривается только передача параметров по значению.

Существо передачи параметров по значению состоит в следующем:

• Для каждого формального параметра в стеке выделяется память.

• Вычисляется значение каждого фактического параметра.

• Каждый формальный параметр инициализируется значением соответствующего ему фактического параметра.

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

1.35.6. Понятие о встраиваемых (inline) функциях

Рассмотренный выше способ организации функций является основным в языке Си. Принципиальным его недостатком являются накладные временные расходы. Стандарт С99 позволяет объявлять встраиваемые функции с помощью ключевого слова inline, как показано ниже:

inline double sqr(double x);

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

1.36. Организация модулей в языке Си

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

Модуль, содержащий функцию main(), называют главным модулем, а остальные – вторичными. Для организации совместной работы моду­лей обычно каждый вторичный модуль снабжается заголовочным файлом – файлом, который имеет то же самое основное имя, что и файл с исходным ко­дом, и расширение .h. Заголовочный файл определяет интерфейс модуля, а файл с исходным кодом – его реализацию.

1.36.1. Пример организации модуля

Приведем пример. Пусть требуется создать модуль, в котором программист предполагает размещать определения собственных математических функций. Выберем для создаваемого модуля имя my_math. Модуль будет содержать два файла: my_math.c и my_math.h. В файле my_math.c будут находиться определения функций, а в файле my_math.h – их прототипы. Кроме того, напишем главный модуль, содержащий клиентский код.

// Файл main.c – головной модуль #include<stdio.h> #include “my_math.h” int main(void) { double x = 3;

printf(“sqr(3) = %10.3f\n”, sqr(x)); printf(“cube(3) = %10.3f\n”, cube(x)); // ... } // Файл my_math.h – заголовочный файл вторичного модуля my_math double cube(double x);

double sqr(double x); // Файл my_math.c – −реализация модуля my_math #include “my_math.h” double cube(double x) { return x * sqr(x); } double sqr(double x) { return x * x; }

В модуле my_math содержатся объявления (файл my_math.h) и определения (файл my_main.c) двух функций: sqr() и cube(). Обратите внимание, что заголовочный файл разработанного модуля подключен не только к файлу реализации клиента, но подключен и к собственному файлу реализации. Рассмотрим, к каким неприятностям приведет отсутствие этих подключений.

Работа с модулем my_math. Выполним пять экспериментов с модулем my_math. В первом эксперименте будет использован программный код, приведенный выше, а в других экспериментах этот код будет изменен.

Эксперимент 1. На экране монитора получаем ожидаемый результат.

sqr(3) = 9 cube(3) = 27

Эксперимент 2. Предположим, что программист допустил ошибку, забыв написать директиву #include “my_math.h” в файле main.c. Теперь оба вызова функций sqr(3) и cube(3) будут компилироваться в отсутствии их прототипов. Такая ситуация совершенно не допустима. Компилятор языка С++ в подобной ситуации выдает сообщение об ошибке, а компилятор языка Си ограничивает предупреждением. В результате программа может поступить на выполнение. Опасность такой ошибки вытекает из той процедуры, которой вынужден воспользоваться компилятор при отсутствии прототипа. Компилятор строит так называемый прототип умолчания, который имеет следующий формат: int имя_функции(); . Пустые скобки в прототипе умолчания компилятор воспринимает как отсутствие информации о формальных параметрах. Всю необходимую ему информацию о этих параметрах он будет извлекать из фактических параметров вызова. При этом ошибки, допущенные при написании вызова, приведут к ошибкам в откомпилированном коде. Пусть, например, программист вместо sqr(3) написал sqr(3, 2). Компилятор будет считать, что функция имеет два параметра типа int. Ясно, что такая ошибка в программе при подключённом заголовочном файле была бы выявлена компилятором, и программист получил бы сообщение об ошибке, а программа не пошла бы на выполнение. При отключенном заголовочном файле никаких сообщений компилятор не выдает. Заметим, что не будет корректно компилироваться и вызов функции sqr(3). Здесь ошибок в вызове функции нет. Несмотря на это, будут получены некорректные результаты.

Это обусловлено двумя обстоятельствами: функция возвращает значение типа double, а не int, как это предусматривает прототип умолчания, и компилятор не выполнит преобразования целочисленного фактического параметра 2 к типу double формального параметра. Предлагаем читателю самостоятельно выполнить этот эксперимент.

Эксперимент 3. Вернем директиву #include “my_math.h” в файле main.c и удалим ее из файла my_math.с.

В момент компиляции определения функции cube(), содержащийся в ней вызов функции sqr() будет компилироваться в отсутствии прототипа. Дело в том, что определение функции sqr() во время компиляции ее вызова еще не доступно компилятору. Создается ситуация, похожая на ту, что была в эксперименте 2. Имеется и важное отличие, состоящее в том, что определение функции все же доступно компилятору. Более интеллектуальный компилятор может выдать сообщение об ошибке, считая, что функция sqr() имеет два отличающиеся прототипа. Другие компиляторы не выдают сообщение об ошибке. Например, при работе в системе Code::Blocks 13.12 такое сообщение выдавалось, а при работе в Visual Studio 2013 – нет. Подключение заголовочного файла эту проблему снимает. Предлагаем убедиться самостоятельно в справедливости изложенного.

Эксперимент 4. Этот эксперимент является продолжением эксперимента 3. Изменим в файле my_math.c порядок следования определений функций sqr() и cube(). Теперь вызов функции будет компилироваться в присутствии прототипа. Дело в том, что определение функции выполняет роль прототипа. Убедитесь в том, что компиляция будет выполняться корректно.

Эксперимент 5. Подключение заголовочного файла к собственному файлу реализации модуля позволяет с помощью компилятора выявить некоторые ошибки, которые мог допустить программист при редактировании программного кода. Одной из таких ошибок является несоответствие между заголовком функции и её прототипом. Пусть в результате редактирования заголовочного файла его содержимое имеет вид (изменения вносятся только в заголовочный файл):

float cube(float x); float sqr(float x);

Экспериментально исследуйте работу программы в двух случаях: (1) при наличии директивы #include “my_math.h” в файле my_math.с и (2) при ее отсутствии. Убедитесь в том, что при наличии директивы будет выдаваться сообщение об ошибке.