Стивен Прата - Язык программирования C. Лекции и упражнения (6-е изд.) 2015
- Название:Язык программирования C. Лекции и упражнения (6-е изд.) 2015
- Автор:
- Жанр:
- Издательство:Вильямс
- Год:0101
- ISBN:нет данных
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Стивен Прата - Язык программирования C. Лекции и упражнения (6-е изд.) 2015 краткое содержание
Язык программирования C. Лекции и упражнения (6-е изд.) 2015 - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
int imin(int, int); /* объявление функции imin()*/ int evill, evill, lesser;
В любом случае основная задача заключается в том, чтобы обеспечить появление объявления функции до ее фактического применения.
338 Глава 9
В стандартной библиотеке ANSI С функции сгруппированы в семейства, каждое из которых имеет собственный заголовочный файл. Такие заголовочные файлы содержат среди прочего объявления функций в семействах. Например, заголовочный файл stdio.h включает объявления для стандартных библиотечных функций ввода-вывода, таких как printf() и scanf(). Заголовочный файл math.h содержит объявления для множества математических функций, например, объявление
double sqrt(double);
которое сообщает компилятору о том, что функция sqrt() принимает параметр типа double и возвращает значение типа double. Не путайте эти объявления с определениями. Объявление функции информирует компилятор о том, какой тин имеет функция, а определение функции предоставляет действительный код. Включение заголовочного файла math.h уведомляет компилятор о том, что возвращаемым типом sqrt() является double, но код для функции sqrt() находится в отдельном файле библиотечных функций.
Создание прототипов функций в ANSI С
Традиционная схема объявления функций, используемая до появления ANSI С, обладала тем недостатком, что предусматривала указание типа для возвращаемого значения функции, но не для ее аргументов. Давайте посмотрим, проблемы какого вида возникают в случае объявления функций по старой форме.
Следующее объявление, применяемое до выхода ANSI С, информирует компилятор о том, что функция imin() возвращает значение типа int:
int imin();
Однако оно ничего не говорит о количестве или типах аргументов этой функции. В результате, если функция imin() используется с некорректным количеством аргументов либо неподходящими их типами, то компилятор не сможет выявить ошибку.
Суть проблемы
Давайте рассмотрим несколько примеров, в которых задействована функция imax(), тесно связанная с imin(). В листинге 9.4 показана программа, в которой функция imax() объявляется устаревшим способом, а затем некорректно применяется.
Листинг 9.4. Программа misuse.с
Функции 339
В первом вызове printf() не указан один аргумент функции imax(), а во втором вызове используются аргументы с плавающей запятой вместо целочисленных. Несмотря на эти ошибки, программа компилируется и запускается.
Вот пример вывода в случае применения XCode 4.6:
Наибольшим значением из 3 и 5 является 1606416656.
Наибольшим значением из 3 и 5 является 3886.
Запуск программы после компиляции с помощью gcc генерирует значения 1 359 379 472 и 1 359 377 160. Оба эти компилятора работают нормально, просто они становятся жертвами некорректного применения прототипов функций в программе.
Что происходит? Механизмы могут отличаться между системами, но вот что происходит в компьютере IBM PC или VAX. Вызывающая функция помещает аргументы во временную память, называемую стеком, и вызванная функция читает их оттуда. Оба эти процесса не скоординированы друг с другом. Вызывающая функция решает, какие типы передавать, на основе фактических аргументов в вызове, а вызванная функция читает значения, основываясь на типах своих формальных параметров. Таким образом, вызов imax(3) помещает в стек одно целочисленное значение. Когда функция imax() запускается, она читает из стека два целочисленных значений. Но в стек было помещено только одно значение, поэтому вторым прочитанным значением будет то, что случайно находилось в стеке в этот момент.
При втором использовании функции imax() ей передаются значения типа float. В результате в стек помещаются два значения типа double. (Вспомните, что при передаче в качестве аргумента тип float повышается до double.) В нашей системе это два 64-битных значения, так что в стек попадают 128 битов данных. Когда функция imax() читает из стека два значения типа int, она извлекает первые 64 бита, т.к. в нашей системе тип int занимает 32 бита. По случайному совпадению эти биты соответствовали двум целочисленным значениям, большим из которых оказалось 3 886.
Решение стандарта ANSI С
Подход к решению проблем с несоответствием аргументов, реализованный в стандарте ANSI С, предусматривает разрешение указывать в объявлении функции также и типы переменных. Результатом является прототип функции — объявление, в котором устанавливается возвращаемый тип, количество аргументов, а также их типы. Чтобы указать, что функция imax() требует два аргумента int, ее можно объявить с помощью одного из следующих прототипов:
int imax(int, int);
int imaxdnt а, int b);
В первой форме применяется список типов, разделенных запятыми. Во второй форме к типам добавлены имена переменных. Помните, что имена переменных являются фиктивными и не обязаны соответствовать именам, используемым в определении функции.
Располагая этой информацией, компилятор может проверить, совпадает ли вызов функции с прототипом. Указано ли правильное количество аргументов? Имеют ли они корректные типы? В случае несовпадения типов, когда оба типа являются числовыми, компилятор преобразует значения фактических аргументов в типы формальных параметров. Например, вызов imax (3.0, 5.0) становится imax (3, 5). Мы модифицировали код в листинге 9.4 для применения прототипа функции. Результат представлен в листинге 9.5.
340 глава 9
Листинг 9.5. Программа proto.с
Попытка компиляции программы из листинга 9.5 приводит к выдаче компилятором сообщения об ошибке, указывающего на то, что вызов imax() содержит слишком мало параметров.
А что можно сказать об ошибках, связанных типами? Для их исследования мы заменили imax(3) вызовом imax(3, 5) и попробовали скомпилировать программу еще раз. На этот раз сообщения об ошибках не было, и мы запустили программу на выполнение. Ниже показан результирующий вывод:
Наибольшим значением из 3 и 5 является 5.
Наибольшим значением из 3 и 5 является 5.
Как и ожидалось, 3.0 и 5.0 во втором вызове были преобразованы в 3 и 5, чтобы функция могла должным образом обработать входные данные.
Хотя сообщения об ошибке отсутствовали, компилятор выдал предупреждение о том, что тип double был преобразован в тип int и возможна потеря данных. Например, вызов
imax(3.9, 5.4)
становится эквивалентным следующему вызову:
imax(3, 5)
Разница между сообщением об ошибке и предупреждением заключается в том, что ошибка предотвращает компиляции, а предупреждение — нет. Некоторые компиляторы выполняют такое приведение типа, не информируя вас. Причина связана с тем, что стандарт не требует вывода предупреждений. Однако многие компиляторы позволяют выбирать уровень выдачи предупреждений, который управляет многословностью компилятора при сообщении о предупреждениях.
Читать дальшеИнтервал:
Закладка: