Зависимость от ЭВМ
Размер данных типа mt зависит от размера машинного слова, различающегося у разных ЭВМ. Если вы не уверены в результате операций над целыми числами, используйте тип long, чтобы избежать проблемы переполнения.
Не гарантируется, что размер данных типа int совпадает с размером машинного слова. В языке Си определяется только, что размер данных типа short меньше или равен размеру данных типа
int. который, в свою очередь, меньше или равен размеру данных типа long. Размер слова может сказаться на обработке двоичных масок. Пример
#define MASK 0177770 /* неправильно*/
mt х,
х &= MASK;
В этом примере три правых бита целого х будут обнуляться только в том случае, если данные типа mt занимают 16 бит. Но если размер данных типа int больше 16 бит, то кроме этого будут обнуляться левые биты данного х. Чтобы избежать таких проблем, используйте следующее макроопределение:
#define MASK (~07) /* правильно */ int х;
х &=- MASK,
Этот пример корректен для всех ЭВМ независимо от размера
данных типа int
Тщательно проверяйте операции сдвига.
Максимальное число бит, которые могут быть сдвинуты вправо или влево, различно на разных ЭВМ. Если заданный в операции сдвиг превысит допустимый максимум, то результаты операции будут непредсказуемы.
Перед сдвигом преобразуйте целые значения к типу unsigned. На некоторых ЭВМ сдвиг выполняется логически, т. е. освобождающиеся разряды обнуляются. На других сдвиг производится арифметически, и освобождающиеся разряды заполняются значением знакового разряда. Однако в языке Си гарантируется, что значения типа unsigned сдвигаются логически.
Используйте поименованные константы.
Использование в программе числовых констант, особенно когда смысл их неочевиден, является плохим стилем программирования. Числовые константы лучше определять в программе символическими именами, связанными с числовыми константами командой препроцессора #defme. Такие определения легко находить и модифицировать, если они размещены в некотором стандартном месте. Обычно это начало программы или файл заголовка.
Пример
#define SCREENWIDTH 80
Такое определение позволяет использовать поименованную константу SCREENWIDTH вместо числа 80. Определяйте размер объекта операцией sizeof Для определения размера некоторого объекта часто используют константы, что снижает мобильность программ. Использование операции sizeof позволяет решить эту проблему.
Пример
#define NUMELEM(ARRAY)
(sizeof(ARRAY) / sizeof(*(ARRAY))) Такое макроопределение обеспечивает мобильный способ определения числа элементов в массиве ARRAY.
Не используйте несколько символов в одной символьной константе.
Поскольку символьные константы представляются значениями типа int, определение языка Си позволяет в принципе задать символьную константу, состоящую из нескольких символов. Однако порядок размещения символов в машинном слове различен на разных ЭВМ.
Не полагайтесь на внутреннюю кодировку целых чисел.
Большинство ЭВМ представляет целые числа в дополнительном коде, но некоторые - в обратном коде. Поэтому не используйте возможности, которые предоставляет дополнительный код. Например, сдвиг на 1 бит влево отрицательного числа (чтобы уменьшить его значение в два раза) не приведет к желаемому результату на ЭВМ с обратным двоичным кодом. ' Формат чисел с плавающей точкой различен на разных ЭВМ.
Представление чисел с плавающей точкой на разных ЭВМ различно. Поэтому точность результатов арифметических вычислений с плавающей точкой может быть разной на различных ЭВМ. Избегайте или в крайнем случае подробно документируйте все вычисления, зависящие от точности.
Не полагайтесь на определенный порядок и число байт в слове.
Число байт и порядок их размещения а машинном слове различны у разных ЭВМ.
Пример
Следующая функция определена неправильно - на выход будет записан нулевой символ, если какой-то байт в слове имеет меньший адрес, чем младший байт;
#define STDOUT 1
putchar(c), /* неправильно */
mt с;
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]; /* неправильно */
Мобильность программ на языке Си 83
Чтобы избежать этого, опишите переменную с как имеющую тип unsigned char или индексируйте таблицу значением, преобразованным к типу unsigned char.
Пример
Следующий фрагмент программы ошибочен - конец файла никогда не будет обнаружен, если символы представляются как беззнаковые значения:
#include <stdio.h> char с; /* неправильно */ if ((с = getcharQ) != EOF)
Если значение символа не может быть отрицательным, то переменная с никогда не станет равной поименованной константе EOF, которая равна -1. Библиотечная функция getchar (см. с. 64) возвращает значение типа int, поэтому с надо описать как переменную типа int. Не комбинируйте разные поля бит.
Не используйте поля бит для представления данных на внешних носителях.
Поля бит можно сделать мобильными, если не объединять разные поля. Максимальный размер поля бит зависит от размера машинного слова, и поля бит не могут пересекать границу слова. Кроме того, порядок размещения полей в слове (слева направо или справа налево) зависит от типа ЭВМ.
Использование полей бит для описания размещения данных на внешних носителях делает программу немобильной. Для того чтобы упростить эту проблему, поместите определения полей бит в файл заголовка и укажите, что они зависят от типа ЭВМ. Используйте операцию преобразования типа для указателей.
В общем случае операции преобразования типа указателя не являются мобильными. Однако можно преобразовать указатель в данное любого целого типа, имеющее достаточно большой размер, и при обратном преобразовании получить исходное значение указателя. Аналогично указатель на некоторый объект может быть преобразован в указатель на меньший объект и обратно.
Учитывайте выравнивание при изменении значения указателя.
Если вы преобразуете указатели из одного типа в другой, то
при выполнении программы может возникнуть ошибка адресации вследствие ограничений на выравнивание данных в машинной памя ти Используйте библиотечную функцию malloc (см с 70),воэвра щающую указатель на символ, выравненный в памяти в соответствии с требованиями данной ЭВМ, так что этот указатель может быть преобразован в указатель любого типа
Следите за сравнением указателей, имеющих знак.
Некоторые ЭВМ выполняют сравнение указателей с учетом знака, другие делают беззнаковое сравнение Это различие несущественно, если сравнивать указатели, содержащие правильные адреса Если указателю будет присвоено значение -1, то в зависимости от ЭВМ оно бупет рассматриваться или как наибольшее допустимое значение, или как недопустимое значение (меньше минимально до пустимого)
Единственная константа, которую можно "безопасно" присваивать указателю, - это нуль, преобразованный к типу соответствующего указателя
Следите за переполнением значения указателей.
Арифметические преобразования указателей могут привести к переполнению или потере значимости Такие циклические преобра зования значений (от наибольшего к наименьшему или наоборот) могут возникнуть при адресации массива, расположенного в начале или в конце машинной памяти
Пример
Этот фрагмент программы показывает возможность появления потери значимости
struct large x [SIZE] *p, /* неправильно */ for (p=x [SIZE l],p>=x,p -)
Если массив х расположен в начале памяти, то возможна ситуация, при которой х - 1 будет не меньше, а больше х вслед ствие перехода через нижнюю границу диапазона значений ука зателей (потери значимости)
Не полагайтесь на конкретную кодировку символов.
Не- используйте в программе предположения, что символы в кодовом наборе располагаются последовательно
Пример
char с,
if (с >= а && с <= 'z ) /* неправильно */ Такая проверка символа с на принадлежность к строчным буквам не является мобильной Чтобы такая проверка правильно выполнялась на других ЭВМ, сделайте так char с,
if (islower(c)) /* правильно */
Библиотечная функция islower (см с 67) определена в библи отеке стандартных функций ввода-вывода и является машин но-зависимой Поскольку ее спецификация мобильна, то данная функция обеспечивает мобильную проверку символов Учтите, что разные символьные коды могут отличаться по числу входящих в них символов Не используйте разность значений двух букв для вычисления лексикографического расстояния между ними
Не используйте программные трюки, зависящие от аппаратуры.
Любое повышение эффективности выполнения программы, достигаемое за счет знания особенностей конкретной ЭВМ, обычно не оправдывает связанную с этим потерю мобильности
Хорошо организованные программы
Программа называется хорошо организованной, если ее легко читать, модифицировать, эксплуатировать и, следовательно, переносить на другие ЭВМ
Все определения, связанные с конкретной операционной средой и конкретной ЭВМ, помещайте в файл заголовка.
Важнейшим средством разработки мобильных программ являются команды препроцессора #mclude и #define (см с 52) Помещайте все определения типов данных, поименованных констант, макроопределения, используемые более чем одной программой, в единый файл заголовка так, чтобы все возможные изменения были локализованы
Пример
#include <values.h>
С помощью этой команды в программу включается стандартный файл заголовка /usr/include/values.h, который содержит аппаратные константы.
Используйте файлы заголовка для общих определений одного проекта, т. е. множества связанных программ. Используйте локальные файлы заголовка для отдельных программ.
Используйте файл заголовка для локализации данных, зависящих от операционной среды, таких как имена файлов и режимы выполнения. Все, что может измениться при переходе на другую операционную систему или в рамках той же системы, помещайте в файлы заголовка, где эти данные легко могут быть модифицированы.
Не помещайте в файлы заголовка определения внешних переменных, которые управляют распределением памяти. Используйте файлы заголовка только для определения команд препроцессора и типов данных.
Для локализации программных фрагментов, зависящих от ЭВМ, используйте функции, условную компиляцию и команду #define.
Функции, зависящие от конкретной ЭВМ, объедините в отдельный исходный файл. Если таких файлов несколько, то соберите их в отдельном каталоге.
Фрагменты исходного кода, зависящие от аппаратуры, заключайте в команды условной компиляции (см. с. 54).
Пример
Следующий фрагмент программы описывает стек, который может наращиваться в разных направлениях в зависимости от аппаратных особенностей ЭВМ. int *stackptr,
#ifdef MACHINE
*--stackptr = datum; /* растет вниз */
#else
*++stackptr = datum; /* растет вверх */
#endif
Для локализации характеристик конкретной ЭВМ можно использовать макроопределения (см. с. 53).
Пример
#defme BITSPERBYTE 8
#define BITS(TYPE)\
(sizeof(TYPE) * BITSPERBYTE)
Спецификация поименованной константы BITSPERBYTE мобильна, реализация - не мобильна. В макроопределении BITS мобильна как спецификация, так и реализация'.
Проверяйте число и тип аргументов, передаваемых функциям.
Убедитесь, что число и тип аргументов, передаваемых функциям при вызове, согласуются с числом и типом формальных параметров этих функций. Даже если при конкретном вьвове можно передавать меньше аргументов, чем определено, не опускайте ни одного из них; опишите пустые фактические аргументы, такие как нулевые значения.
В файле /usr/mclude/varargs. h описаны средства для мобильного определения функций с переменным числом аргументов. Например, библиотечная функция pintf реализована с использованием этих средств.
Используйте стандартные библиотечные функции; не определяйте собственные системные вызовы, если без этого можно обойтись.
Стандартные библиотечные функции ОС UNIX обеспечивают большой выбор универсальных процедур. Библиотеки ОС UNIX содержат стандартные функции, обеспечивающие доступ к таким средствам операционной системы, как ввод и вывод. Библиотечные функции обеспечивают повышение мобильности, изолируют вашу программу от возможных изменений в операционной системе. Тщательно определяйте внешние имена.
Для облегчения эксплуатации и увеличения мобильности программы определяйте все внешние переменные в отдельных исходных файлах. Не забудьте, что все остальные исходные файлы программы должны ссылаться на эти определения с помощью описаний extern. При этом можно использовать разные методы Можно поместить эти внешние описания в начало исходных файлов программы (с помощью включения файлов) или описать эти внешние переменные в функциях, которые их используют
Спецификацией здесь называется определяемая лексема, а реализацией - определяющее константное выражение.
Максимальное число значимых символов в идентификаторах внешних переменных и функций зависит от операционной системы Кроме того, некоторые операционные системы преобразуют все строчные буквы в прописные Компоновщик (редактор связей) со общает о конфликтах имен, но обнаруживает не все возможные ошибки, поэтому не возлагайте на эту программу обязанность разрешать противоречия в именах
Используйте описание typedef для локализации определения типов данных, зависящих от ЭВМ.
Описание typedef обеспечивает локальные определения типов тех данных, которые зависят от конкретной ЭВМ Если вы измените определение типа, заданное описанием typedef, то соответственно изменятся все переменные, описанные с помощью этого производного типа Система обеспечивает набор стандартных определений в файле
/usr/mclude/sys/types h
Пример
typedef unsigned short ino_t, /* индекс файла */
Этот пример показывает типичное использование определения
типа в файле /usr/mclude/sys/types h