Linux программирование в примерах - страница 13
1.2.1. Каналы: сцепление процессов
Без сомнения, вам приходилось использовать конструкцию ('>|
') оболочки для соединения двух или более запущенных программ. Канал действует подобно файлу: один процесс записывает в него, используя обычную операцию записи, а другой процесс считывает из него с помощью операции чтения. Процессы (обычно) не знают, что их ввод/вывод является каналом, а не обычным файлом.
Как ядро скрывает «магию» для устройств, заставляя их действовать подобно файлам, точно так же оно проделывает эту работу для каналов, принимая меры по задержке записи в канал при его наполнении и задержке чтения, когда нет ожидающих чтения данных.
Таким образом, принцип файлового ввода/вывода применительно к каналам служит ключевым механизмом для связывания запушенных программ; не требуется никаких временных файлов. Опять-таки общность и простота работы: никаких особых случаев для кода пользователя.
1.3. Стандартный С против оригинального С
В течение многих лет определение С де-факто можно было найти в первом издании книги Брайана Кернигана и Денниса Ричи «Язык программирования С» (Brian Kernighan & Dennis Ritchie, The С Programming Language). Эта книга описала С, как он существовал для Unix и на системах, на которые его перенесли разработчики лаборатории Bell Labs. На протяжении данной книги мы называем его как «оригинальный С», хотя обычным является также название «С Кернигана и Ричи» («K&R С»), по именам двух авторов книги. (Деннис Ричи разработал и реализовал С.)
Стандарт ISO С 1990 г.[17] формализовал определения языка, включая функции библиотеки С (такие, как >printf()
и >fopen()
). Комитет по стандартам С проделал замечательную работу по стандартизации существующей практики и избежал введения новых возможностей, с одним значительным исключением (и несколькими незначительными). Наиболее заметным изменением языка было использование прототипов функций, заимствованных от С++.
Стандартные языки программирования С, C++ и Java используют прототипы функций для объявлений и определений функций. Прототип описывает не только возвращаемое значение функции, но также и число и тип ее аргументов. С прототипами компилятор может выполнить проверку типов в точке вызова функции:
Объявление
>extern int myfunc(struct my_struct *a,
> struct my_struct *b, double c, int d);
Определение
>int myfunc(struct my_struct *a,
> struct my_struct *b, double c, int d) {
> ...
>}
>...
>struct my_struct s, t;
>int j;
>...
>/* Вызов функции, где-то в другом месте: */
>j = my_func(&s, &t, 3.1415, 42);
Это правильный вызов функции. Но рассмотрите ошибочный вызов:
>j = my_func(-1, -2, 0);
>/* Ошибочные число и типы аргументов */
Компилятор может сразу же определить этот вызов как неверный. Однако, в оригинальном С функции объявляются без указания списка аргументов:
>extern int myfunc();
>/* Возвращает int, аргументы неизвестны */
Более того, определения функций перечисляют имена параметров в заголовке функции, затем объявляют параметры перед телом функции. Параметры типа int объявлять не нужно, и если функция возвращает int, его тоже не нужно объявлять:
>myfunc(a, b, с, d); /* Возвращаемый тип int*/
>struct my_struct *а, *b;
>double с;
>/* Обратите внимание, нет объявления параметра d*/
>{
> ...
>}
Рассмотрите снова тот же ошибочный вызов функции: '>j = my_func(-1, -2 , 0);
'. В оригинальном С у компилятора нет возможности узнать, что вы (ошибочно, полагаем) передали >my_func()
ошибочные аргументы. Подобные ошибочные вызовы обычно приводят к трудно устранимым проблемам времени исполнения (таким, как ошибки сегментации, из-за чего программа завершается), и для работы с такими вещами была создана программа Unix >lint
.
Поэтому, хотя прототипы функции и были радикальным отходом от существующей практики, дополнительную проверку типов посчитали слишком важной, чтобы обходиться без нее, и после небольшого сопротивления она была добавлена в язык.
Для С стандарта 1990 г. код, написанный в оригинальном стиле, является действительным как для объявлений, так и для определений. Это дает возможность продолжать компилировать миллионы строк существующего кода с помощью компилятора, удовлетворяющего стандарту. Новый код, очевидно, должен быть написан с прототипами из-за улучшенных возможностей проверки ошибок времени компилирования.