Язык программирования Си (1985)
.pdf61
Формат чисел с плавающей точкой различен на разных ЭВМ.
Представление чисел с плавающей точкой на разных ЭВМ различно. Поэтому точность результатов арифметических вычислений с плавающей точкой может быть разной на различных ЭВМ. Избегайте или в крайнем случае подробно документируйте все вычисления, зависящие от точности.
Не полагайтесь на определенный порядок и число байт в слове.
Число байт и порядок их размещения а машинном слове различны у разных ЭВМ.
П р и м е р
Следующая функция определена неправильно - на выход будет записан нулевой символ, если какой-то байт в слове имеет меньший адрес, чем младший байт;
#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