Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Язык программирования Си (1985)

.pdf
Скачиваний:
1056
Добавлен:
15.06.2014
Размер:
558.87 Кб
Скачать

61

Формат чисел с плавающей точкой различен на разных ЭВМ.

Представление чисел с плавающей точкой на разных ЭВМ различно. Поэтому точность результатов арифметических вычислений с плавающей точкой может быть разной на различных ЭВМ. Избегайте или в крайнем случае подробно документируйте все вычисления, зависящие от точности.

Не полагайтесь на определенный порядок и число байт в слове.

Число байт и порядок их размещения а машинном слове различны у разных ЭВМ.

П р и м е р

Следующая функция определена неправильно - на выход будет записан нулевой символ, если какой-то байт в слове имеет меньший адрес, чем младший байт;

#define STDOUT 1

/* неправильно */

putchar(c);

int с;

 

{

write(STDOUT, (char *) &c, 1);

}

В данном примере аргумент с должен описываться как имеющий тип char; в этом случае преобразование типа данных станет ненужным.

Не полагайтесь на определенное число бит в байте.

Поскольку число бит в байте у разных ЭВМ различно, то не предполагайте, что байт всегда занимает 8 бит. Чтобы определить число бит в байте, используйте поименованную константу, содержащуюся в стандартном файле:

/usr/include/values.h

З а м е ч а н и е. Все системные файлы-заголовки размещаются в каталоге /usr/include.

Будьте осторожны с символами, имеющими знак.

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

П р и м е р

Если на данной ЭВМ допустимы символы со знаком, то существует опасность, что индексация символом некоторого массива приведет к выходу за его границы:

#define TABSIZE 256 char с;

extern char table [TABSIZE];

с = table [c]; /* неправильно */

Чтобы избежать этого, опишите переменную с как имеющую тип unsigned char или индексируйте таблицу значением, преобразованным к типу unsigned char.

62

П р и м е р

Следующий фрагмент программы ошибочен - конец файла никогда не будет обнаружен, если символы представляются как беззнаковые значения:

#include <stdio.h>

/* неправильно */

char с;

if ((с = getchar()) != EOF)

 

Если значение символа не может быть отрицательным, то переменная с никогда не станет равной поименованной константе EOF, которая равна -1. Библиотечная функция getchar (см. с. 64) возвращает значение типа int, поэтому с надо описать как переменную типа int.

Не комбинируйте разные поля бит.

Не используйте поля бит для представления данных на внешних носителях.

Поля бит можно сделать мобильными, если не объединять разные поля. Максимальный размер поля бит зависит от размера машинного слова, и поля бит не могут пересекать границу слова. Кроме того, порядок размещения полей в слове (слева направо или справа налево) зависит от типа ЭВМ.

Использование полей бит для описания размещения данных на внешних носителях делает программу немобильной. Для того чтобы упростить эту проблему, поместите определения полей бит в файл заголовка и укажите, что они зависят от типа ЭВМ.

Используйте операцию преобразования типа для указателей.

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

Учитывайте выравнивание при изменении значения указателя.

Если вы преобразуете указатели из одного типа в другой, то при выполнении программы может возникнуть ошибка адресации вследствие ограничений на выравнивание данных в машинной памяти Используйте библиотечную функцию malloc (см. с. 70), воэвращающую указатель на символ, выравненный в памяти в соответствии с требованиями данной ЭВМ, так что этот указатель может быть преобразован в указатель любого типа.

Следите за сравнением указателей, имеющих знак.

Некоторые ЭВМ выполняют сравнение указателей с учетом знака, другие делают беззнаковое сравнение. Это различие несущественно, если сравнивать указатели, содержащие правильные адреса. Если указателю будет присвоено значение -1, то в зависимости от ЭВМ оно бупет рассматриваться или как наибольшее допустимое значение, или как недопустимое значение (меньше минимально допустимого).

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

Следите за переполнением значения указателей.

63

Арифметические преобразования указателей могут привести к переполнению или потере значимости. Такие циклические преобразования значений (от наибольшего к наименьшему или наоборот) могут возникнуть при адресации массива, расположенного в начале или в конце машинной памяти

П р и м е р

Этот фрагмент программы показывает возможность появления потери значимости:

struct

large x[SIZE], *p;

/* неправильно */

for (p

=x[SIZE - l]; p >=

x; p--)

Если массив х расположен в начале памяти, то возможна ситуация, при которой х - 1 будет не меньше, а больше х вследствие перехода через нижнюю границу диапазона значений указателей (потери значимости).

Не полагайтесь на конкретную кодировку символов.

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

П р и м е р

char с;

if (с >= 'а' && с <= 'z' ) /* неправильно */

Такая проверка символа с на принадлежность к строчным буквам не является мобильной. Чтобы такая проверка правильно выполнялась на других ЭВМ, сделайте так:

char с;

if (islower(c)) /* правильно */

Библиотечная функция islower (см. с. 67) определена в библиотеке стандартных функций ввода-вывода и является машинно-зависимой Поскольку ее спецификация мобильна, то данная функция обеспечивает мобильную проверку символов.

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

Не используйте программные трюки, зависящие от аппаратуры.

Любое повышение эффективности выполнения программы, достигаемое за счет знания особенностей конкретной ЭВМ, обычно не оправдывает связанную с этим потерю мобильности.

14.4. Хорошо организованные программы

Программа называется хорошо организованной, если ее легко читать, модифицировать,

эксплуатировать и, следовательно, переносить на другие ЭВМ.

Все определения, связанные с конкретной операционной средой и конкретной ЭВМ, помещайте в файл заголовка.

Важнейшим средством разработки мобильных программ являются команды препроцессора #include и #define (см. с. 52). Помещайте все определения типов данных, поименованных констант, макроопределения, используемые более чем одной программой, в единый файл заголовка так, чтобы все возможные изменения были локализованы.

64

П р и м е р

#include <values.h>

С помощью этой команды в программу включается стандартный файл заголовка /usr/include/values.h, который содержит аппаратные константы.

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

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

Не помещайте в файлы заголовка определения внешних переменных, которые управляют распределением памяти. Используйте файлы заголовка только для определения команд препроцессора и типов данных.

Для локализации программных фрагментов, зависящих от ЭВМ, используйте функции, условную компиляцию и команду #define.

Функции, зависящие от конкретной ЭВМ, объедините в отдельный исходный файл. Если таких файлов несколько, то соберите их в отдельном каталоге.

Фрагменты исходного кода, зависящие от аппаратуры, заключайте в команды условной компиляции (см. с. 54).

П р и м е р

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

int *stackptr; #ifdef MACHINE

*--stackptr = datum; /* растет вниз */

#else

*++stackptr = datum; /* растет вверх */

#endif

Для локализации характеристик конкретной ЭВМ можно использовать макроопределения (см. с. 53).

П р и м е р

#define BITSPERBYTE 8 #define BITS(TYPE)\ (sizeof(TYPE) * BITSPERBYTE)

Спецификация поименованной константы BITSPERBYTE мобильна, реализация - не мобильна. В макроопределении BITS мобильна как спецификация, так и реализация11.

Проверяйте число и тип аргументов, передаваемых функциям.

11 Спецификацией здесь называется определяемая лексема, а реализацией - определяющее константное выражение. – Прим. перев.

65

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

В файле /usr/mclude/varargs.h описаны средства для мобильного определения функций с переменным числом аргументов. Например, библиотечная функция printf реализована с использованием этих средств.

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

Стандартные библиотечные функции ОС UNIX обеспечивают большой выбор универсальных процедур. Библиотеки ОС UNIX содержат стандартные функции, обеспечивающие доступ к таким средствам операционной системы, как ввод и вывод. Библиотечные функции обеспечивают повышение мобильности, изолируют вашу программу от возможных изменений в операционной системе (см. также с. 60 – 77).

Тщательно определяйте внешние имена.

Для облегчения эксплуатации и увеличения мобильности программы определяйте все внешние переменные в отдельных исходных файлах. Не забудьте, что все остальные исходные файлы программы должны ссылаться на эти определения с помощью описаний extern (см. с. 49 и 51). При этом можно использовать разные методы. Можно поместить эти внешние описания в начало исходных файлов программы (с помощью включения файлов) или описать эти внешние переменные в функциях, которые их используют.

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

Используйте описание typedef для локализации определения типов данных, зависящих от ЭВМ.

Описание typedef (см. с. 47) обеспечивает локальные определения типов тех данных, которые зависят от конкретной ЭВМ. Если вы измените определение типа, заданное описанием typedef, то соответственно изменятся все переменные, описанные с помощью этого производного типа. Система обеспечивает набор стандартных определений в файле

/usr/include/sys/types.h

П р и м е р

typedef unsigned short ino_t; /* индекс файла */

Этот пример показывает типичное использование определения типа в файле

/usr/include/sys/types.h

14.5. Мобильность файлов данных

Для переноса файлов, содержащих двоичные данные, используйте символьный ввод-вывод.

66

Файлы двоичных данных по сути своей не мобильны, поскольку разные ЭВМ используют разное внутреннее представление данных. К сожалению, не существует простого пути для переноса файлов данных. Порядок байт в слове может привести к серьезным проблемам при переносе данных с одной ЭВМ на другую по принципу "байт в байт" Кроме того, коды символов могут быть разными на разных ЭВМ.

Один из способов решения этой проблемы заключается в раз работке специальных программ преобразования для конкретных форматов данных. Другой подход заключается в записи байт, составляющих объект данных, в некотором машинно-независимом порядке Для передачи символьных данных используйте библиотечные функции printf и scanf (см. с. 70 и 74), хотя это и непрактично.

ПРИЛОЖЕНИЕ. НАБОР СИМВОЛОВ КОДА ASCII

З а м е ч а н и е. Комбинация символов ^@, ^A и т.д. обозначает CTRL/@, CTRL/A и т.д. Набор символов может слегка отличаться на разных терминалах.

 

 

 

 

Управляющие символы

 

 

 

 

Код12

Д

В

Ш

 

ASCII

0

 

000

00

^@ NUL (Пусто ПУС)

1

 

001

01

^A SOH (Начало заголовка НЗ)

2

 

002

02

^B STX (Начало текста НТ)

3

 

003

03

^C ETX (Конец текста КТ)

4

 

004

04

^D EOT (Конец передачи КП)

5

 

005

05

^E ENQ (Кто там? КТМ)

6

 

006

06

^F ACK (Подтверждение ДА)

7

 

007

07

^G BEL (Звонок ЗВ)

8

 

010

08

^H BS (Возврат на шаг ВШ)

9

 

011

09

^I TAB HT (Горизонтальная табуляция ТБ)

10

 

012

0A

^J LF (Перевод строки ПС)

11

 

013

0B

^K VT (Вертикальная табуляция ВТ)

12

 

014

0C

^L FF (Перевод формата ПФ)

13

 

015

0D

^M CR (Возврат каретки ВК)

14

 

016

0E

^N SO (Выход ВЫХ)

15

 

017

0F

^O SI (Вход ВХ)

16

 

020

10

^P DLE (Авторегистр1 АР1)

17

 

021

11

^Q DC1 (X-ON)

18

 

022

12

^R DC2

19

 

023

13

^S DC3 (X-OFF)

20

 

024

14

^T DC4

21

 

025

15

^U NAK (Отрицание НЕТ)

22

 

026

16

^V SYN (Синхронизация СИН)

23

 

027

17

^W ETB (Конец блока КБ)

24

 

030

18

^X CAN (Аннулирование АН)

25

 

031

19

^Y EM (Конец носителя КН)

26

 

032

1A

^Z SUB (Замена ЗМ)

27

 

033

1B

^[ ESC (Авторегистр2 АР2)

28

 

034

1C

^\ FS (Разделитель файлов РФ)

29

 

035

1D

^] GS (Разделитель групп РГ)

30

 

036

1E

^^ RS (Разделитель записей РЗ)

31

 

037

1F

^_US (Разделитель элементов РЭ)

12 Д – десятичный, В – восьмеричный, Ш – шестнадцатеричный. – Прим. перев.

67

 

 

 

Пробел

 

 

 

 

 

 

 

Код

 

 

 

 

Д

В

Ш

 

 

ASCII

 

 

32

040

20

SPACEBAR SP (Пробел ПРБ)

 

 

 

 

 

Печатные символы

 

 

 

 

 

 

 

Код

 

 

 

 

Д

В

Ш

ASCII

77

115

4D

M

 

 

 

 

78

116

4E

N

33

041

21

!

79

117

4F

O

34

042

22

"

80

120

50

P

35

043

23

#

81

121

51

Q

36

044

24

$

82

122

52

R

37

045

25

%

83

123

53

S

38

046

26

&

84

124

54

T

39

047

27

'

85

125

55

U

40

050

28

(

86

126

56

V

41

051

29

)

87

127

57

W

42

052

2A

*

88

130

58

X

43

053

2B

+

89

131

59

Y

44

054

2C

,

90

132

5A

Z

45

055

2D

-

91

133

5B

[

46

056

2E

.

92

134

5C

\

47

057

2F

/

93

135

5D

]

48

060

30

0

94

136

5E

^

49

061

31

1

95

137

5F

_

50

062

32

2

96

140

60

`

51

063

33

3

97

141

61

a

52

064

34

4

98

142

62

b

53

065

35

5

99

143

63

c

54

066

36

6

100

144

64

d

55

067

37

7

101

145

65

e

56

070

38

8

102

146

66

f

57

071

39

9

103

147

67

g

58

072

3A

:

104

150

68

h

59

073

3B

;

105

151

69

i

60

074

3C

<

106

152

6A

j

61

075

3D

=

107

153

6B

k

62

076

3E

>

108

154

6C

l

63

077

3F

?

109

155

6D

m

64

100

40

@

110

156

6E

n

65

101

41

A

111

157

6F

o

66

102

42

B

112

160

70

p

67

103

43

C

113

161

71

q

68

104

44

D

114

162

72

r

69

105

45

E

115

163

73

s

70

106

46

F

116

164

74

t

71

107

47

G

117

165

75

u

72

110

48

H

118

166

76

v

73

111

49

I

119

167

77

w

74

112

4A

J

120

170

78

x

75

113

4B

K

121

171

79

y

76

114

4C

L

122

172

7A

z

68

123

173

7B

{

124

174

7C

|

125

175

7D

}

126

176

7E

~

127

177

7F

DEL,

 

 

 

RUB

 

 

 

(Забой, ЗБ)

69

Список литературы

S. C. Johnson, “LINT, A C Program Checker”, UNIX System Programmers’s Manual, Volume 2, AT&T Bell Laboratories. Published by Holt, Rinehart, and Winston, New York, 1983, 1979. Pages 278-290.

S. C. Johnson and D. M. Ritchie, “UNIX Time-Sharing System: Portability of C Programs and the UNIX System”, The Bell System Technical Journal, July-August 1978, (Volume 57, No. 6, Part 2), pages 2021-2048. AT&T Bell Laboratories. Murray Hill, NJ 07974.

R.W. Kernighan and D. M. Ritchie, The C Programming Language, Prentice-Hall, Englewood Cliffs, NJ, 1978. (Перевод см. в [Д6].)

T. Plum, C Programming Guidelines, Plum-Hall, Cardiff, NJ, 1984.

Дополнительный список литературы

Д1. Банахан М., Раттер Э. Введение в операционную систему UNIX: Пер. с англ. – М.: Радио и связь, 1985. – 344 с.

Д2. Баурн С. Операционная система UNIX: Пер. с англ. – М.: Мир, 1986. – 463 с.

Д3. Готье Р. Руководство по операционной системе UNIX: Пер. с англ. – М.: Финансы и статистика, 1985. – 232 с.

Д4. Иванов А. Г. Язык программирования Си: Предварительное описание // Прикладная информатика. – 1985. – Вып. 1. – С. 68 – 113.

Д5. Инструментальная мобильная операционная система ИНМОС / М.И.Беляков, А.Ю.Ливеровский, В.П.Семик, В.И.Шяудкулис. – М.: Финансы и статистика, 1985. – 231 с.

Д6. Керниган Б., Ритчи Д., Фьюэр А. Язык программирования Си. Задачи по языку Си: Пер. с англ. – М.: Финансы и статистика, 1985. – 279 с.

Д7. Кристиан К. Введение в операционную систему UNIX: Пер. с англ. – М.: Финансы и статистика, 1985. – 318 с.

Д8. Томас Р., Йейтс Дж. Операционная система UNIX. Руководство для пользователей: Пер. с англ. – М.: Радио и связь, 1986. – 352 с.

Д9. Хэнкок Л., Кригер М. Введение в программирование на языке Си: Пер. с англ. – М.: Радио и связь, 1986. – 192 с.

Предметный указатель

Аргументы. см. Параметры

строковый, 47

Библиотеки стандартные, 48

Верификатор программ lint, 59

Блок, 24

Включение файлов, 39

определение локальных переменных, 35

Вывод форматированный, 52

Ввод форматированный, 55

функции стандартные, 52

строка форматная, 55, 56, 57

Выражения, 13

функции стандартные, 55

константные, 27

Ввод-вывод, 44

преобразования операндов, 23

блочный, 48

Доступ

доступ к каналам, 46

к каналам, 46

доступ к файлам, 45

к параметрам среды, 31

символьный, 47

к файлам, 45

70

Зарезервированные слова, 8 Идентификаторы, 8 Инициализация

переменных, 36 цикла, 29

Класс памяти, 8 auto, 35 extern, 38 register, 35 static, 36

Код символьный ASCII, 10, 11, 50, 51, 58, 66, 67

Комментарии, 7 Компиляция условная, 40 Константы

длинные целые, 9 перечислимые, 11 поименованные, 60 с плавающей точкой, 10 символьные, 10 строковые, 11 целые, 9

Макроопределения, 39, 50, 51 мобильные, 64 стандартные, 45

Макросы, 39 Массивы

инициализация, 37 операции, 20 описание, 32

Метка варианта, 27 оператора, 24

Мобильность программ, 58 файлов данных, 65

Обработка строк, 49 Объединения, 34

выравнивание в памяти, 34 операции, 21 описание, 34

Операнды метаобозначения, 13 порядок обработки, 23

преобразования в выражениях, 23 Оператор

break, 25 continue, 25 do-while, 28 for, 28 goto, 26 if-else, 26 return, 25 switch, 27 while, 28

вложенность, 24 вызова функции, 25 присваивания, 24 пустой, 25 составной, 24

Оператор-выражение, 24 Операции

адресные, 20

-обращения по адресу *, 20

-получения адреса &, 20 арифметические, 13

-вычитания -, 13

-деления /, 14

-получения остатка от деления %, 14

-сложения +, 13

-увеличения ++, 14

--побочные эффекты, 14

-уменьшения --, 15

--побочные эффекты, 14

-умножения *, 14

логические, 18 над массивами [ ], 20

над объединениями и структурами . ->, 21

отношения == != < <= > >=, 17

присваивания =, 15 смешанные

-вызова функции ( ), 22

-запятая ,, 21

-определения размера sizeof, 21

-преобразования типа (тип), 21 условная ?:, 21

Операционная система UNIX, 6, 7, 58, 59, 65

Описание, 31 внешних объектов (переменных и

функций), 38, 65 массивов и указателей, 32 область действия, 35 объединений, 34 параметров, 35, 36, 44 перечислений, 34 полей бит, 33 структур, 33

Определение

переменных

-глобальных, 36

-локальных, 35 типов данных, 31, 63, 65 функций, 29

Параметры макроса, 39 функции, 30, 65

- main, 31

Переменные автоматические, 35

внешние, 36, 38, 52, 65

временные, 35 глобальные, 36 локальные, 35 постоянные, 35 регистровые, 35 статические, 36

Перечисления, 34 Поля бит, 33, 62