C++ CSS HTML Java JavaScript MySQL Oracle PERL PHP SQL Unix VBScript XHTML XML Сети
1. Простые программы и алгоритмы. Сюрпризы, советы.
 
1.137.  Стандартная функция быстрой сортировки  qsort  (алгоритм  quick  sort)  имеет
такой формат: чтобы отсортировать массив элементов типа TYPE

	TYPE arr[N];
	надо вызывать
	qsort(arr,/* Что сортировать? Не с начала: arr+m	*/
	N,  /* Сколько первых элементов массива?	*/
	/* можно сортировать только часть: n < N  */
	sizeof(TYPE),/* Или sizeof arr[0]	*/
	/* размер одного элемента массива*/
	cmp);

где

	int cmp(TYPE *a1, TYPE *a2);

функция сравнения элементов *a1 и *a2.  Ее аргументы - АДРЕСА двух каких-то элементов
сортируемого  массива.   Функцию  cmp мы должны написать сами - это функция, задающая
упорядочение элементов массива.  Для сортировки по возрастанию функция  cmp()  должна
возвращать целое

	< 0, если  *a1 должно идти раньше *a2	<
	= 0, если  *a1 совпадает с	*a2   ==
	> 0, если  *a1 должно идти после  *a2	>

Для массива строк элементы  массива имеют тип (char  *),  поэтому  аргументы  функции
имеют тип (char **).  Требуемому условию удовлетворяет такая функция:

	char *arr[N]; ...
	cmps(s1, s2) char **s1, **s2;
	{ return strcmp(*s1, *s2); }

(Про strcmp смотри раздел "Массивы и строки").  Заметим,  что  в  некоторых  системах
программирования (например в TurboC++ [*]) вы должны использовать функцию  сравнения  с
прототипом

	int cmp (const void *a1, const void *a2);

и внутри нее явно делать приведение типа:

	cmps (const void *s1, const void *s2)
	{ return strcmp(*(char **)s1, *(char **)s2); }

или можно поступить следующим образом:

	int cmps(char **s1, char **s2){
	return strcmp(*s1, *s2);
	}
	typedef int (*CMPS)(const void *, const void *);
	qsort((void *) array, ..., ..., (CMPS) cmps);

Наконец, возможно и просто объявить

	int cmps(const void *A, const void *B){
	return strcmp(A, B);
	}

Для массива целых годится такая функция сравнения:

	int arr[N]; ...
	cmpi(i1, i2) int *i1, *i2;
	{ return *i1 - *i2; }

Для массива структур, которые мы сортируем по целому полю key, годится

	struct XXX{ int key; ... } arr[N];
	cmpXXX(st1, st2) struct XXX *st1, *st2;
	{ return( st1->key  -  st2->key ); }

Пусть у нас есть массив long. Можно ли использовать

	long arr[N]; ...
	cmpl(L1, L2) long *L1, *L2;
	{ return *L1 - *L2; }

Ответ: оказывается, что нет. Функция cmpl должна возвращать целое,  а  разность  двух
long-ов  имеет  тип  long.   Поэтому компилятор приводит эту разность к типу int (как
правило обрубанием старших битов).  При этом (если long-числа были велики)  результат
может изменить знак! Например:

	main(){
	int n; long a = 1L; long b = 777777777L;
	n = a - b;  /* должно бы быть отрицательным... */
	printf( "%ld %ld %d\n", a, b, n );
	}
____________________
   [*] TurboC - компилятор Си в MS DOS, разработанный фирмой Borland International.

печатает 1 777777777 3472.  Функция сравнения должна выглядеть так:

	cmpl(L1, L2) long *L1, *L2; {
	if( *L1 == *L2 ) return   0;
	if( *L1 <  *L2 ) return (-1);
	return   1;
	}

или

	cmpl(L1, L2) long *L1, *L2; {
	return( *L1 == *L2 ?  0 :
	*L1 <  *L2 ? -1 : 1 );
	}

поскольку важна не величина возвращенного значения, а только ее знак.
	Учтите, что для использования функции сравнения вы должны либо определить  функ-
цию сравнения до ее использования в qsort():

	int cmp(...){ ... } /* реализация */
	...
	qsort(..... , cmp);

либо предварительно объявить имя функции сравнения, чтобы компилятор понимал, что это
именно функция:

	int cmp();
	qsort(..... , cmp);
	...
	int cmp(...){ ... } /* реализация */
1.138.  Пусть у нас есть две программы, пользующиеся одной и той же структурой данных
W:

	a.c	b.c<fcntl.h>	#include <fcntl.h>
	struct W{ int x,y; }a;	struct W{ int x,y; }b;
	main(){  int fd;	main(){  int fd;
	a.x = 12; a.y = 77;	fd = open("f", O_RDONLY);
	fd = creat("f", 0644);	read(fd, &b, sizeof b);
	write(fd, &a, sizeof a);	close(fd);
	close(fd);	printf("%d %d\n", b.x, b.y);
	}	}

Что будет, если мы изменим структуру на

	struct W { long x,y; };
	или
	struct W { char c; int x,y; };

в файле a.c и забудем сделать это в b.c
-	типы данных (typedef);
-	структуры и объединения;
-	константы (определения #define);
-	прототипы функций;
то их определения лучше выносить в общий include-файл (header-файл), дабы  все  прог-
раммы  придерживались  одних  и тех же общих соглашений.  Даже если эти соглашения со

 написано, ведь при этом немудрено какое-нибудь место  и
пропустить!

	W.h
	-----------------------
	struct W{ long x, y; };

	a.c	b.c<fcntl.h>	#include <fcntl.h>
	#include "W.h"	#include "W.h"
	struct W a;	struct W b;
	main(){  ...	main(){  ...
	printf("%ld...

Кроме того, вынесение общих фрагментов текста программы (определений структур,  конс-
тант,  и.т.п.) в отдельный файл экономит наши силы и время - вместо того, чтобы наби-
вать один и тот же текст много раз в разных файлах, мы теперь пишем  в  каждом  файле
единственную  строку  -  директиву #include. Кроме того, экономится и место на диске,
ведь программа  стала  короче!   Файлы  включения  имеют  суффикс  .h,  что  означает
"header-file" (файл-заголовок).
	Синхронную перекомпиляцию всех программ в случае изменения  include-файла  можно
задать в файле Makefile - программе для координатора make[*]:

	all: a b
	echo Запуск a и b
	a ; b
	a: a.c W.h
	cc a.c -o a
	b: b.c W.h
	cc b.c -o b

Правила make имеют вид

	цель: список_целей_от_которых_зависит
	команда

команда  описывает  что  нужно  сделать,  чтобы  изготовить  файл  цель   из   файлов
список_целей_от_которых_зависит.   Команда  выполняется  только если файл цель еще не
существует, либо хоть один из файлов справа от  двоеточия  является  более  "молодым"
(свежим), чем целевой файл (смотри поле st_mtime и сисвызов stat в главе про UNIX).

1.139.  Программа на Си может быть размещена в нескольких файлах.  Каждый файл высту-
пает  в  роли "модуля", в котором собраны сходные по назначению функции и переменные.
Некоторые переменные и функции можно сделать невидимыми для других модулей. Для этого
надо объявить их static:
-	Объявление переменной внутри функции как static  делает  переменную  статической
	(т.е. она будет сохранять свое значение при выходе из функции) и ограничивает ее
	видимость пределами данной функции.
-	Переменные, описанные вне  функций,  и  так  являются  статическими  (по  классу
	памяти). Однако слово static и в этом случае позволяет управлять видимостью этих
	переменных - они будут видимы только в пределах данного файла.
-	Функции, объявленные как static, также видимы только в пределах данного файла[*] Подробное описание make смотри в документации по системе UNIX.

	при входе в функцию и уничтожается при выходе) и видимы только внутри  ее  тела.
	Аргументы функции нельзя объявлять static:

	f(x) static x; { x++; }

	незаконно.

 слова static функции статические локальные переменные, объявленные в  теле  функции  со
	словом static.
-	Видимые во всех файлах (глобальные имена).

Глобальные имена образуют интерфейс модуля и могут быть использованы в  других  моду-
лях. Локальные имена извне модуля недоступны.
	Если мы используем в файле-модуле функции и  переменные,  входящие  в  интерфейс
другого  файла-модуля, мы должны объявить их как extern ("внешние"). Для функций опи-
сатели extern и int можно опускать:

	// файл A.c
	int x, y, z;	// глобальные
	char ss[200];	// глоб.
	static int v, w;	// локальные
	static char *s, p[20];	// лок.
	int f(){ ... }	// глоб.
	char *g(){ ... }	// глоб.
	static int h(){ ... }	// лок.
	static char *sf(){ ... }  // лок.
	int fi(){ ... }	// глоб.
	// файл B.c
	extern int x, y;
	extern z;	// int можно опустить
	extern char ss[];   // размер можно опустить
	extern int f();
	char *g();	// extern можно опустить
	extern fi();	// int можно опустить

Хорошим тоном является написание комментария - из какого модуля или библиотеки импор-
тируется переменная или функция:

	extern int x, y;  /* import from A.c	*/
	char *tgetstr();  /* import from termlib */

Следующая программа собирается из файлов A.c и B.c командой[**]
____________________
   [**] Можно задать Makefile вида

	CFLAGS = -O
	AB:	A.o	B.o
	cc A.o B.o -o AB
	A.o:	A.c
	cc -c $(CFLAGS) A.c
	B.o:	B.c
	cc -c $(CFLAGS) B.c

и собирать программу просто вызывая команду make.

	cc A.c B.c -o AB

Почему компилятор сообщает "x дважды определено"?

	файл A.c	файл B.c
	-----------------------------------------
	int x=12;	int x=25;
	main(){	f(y) int *y;
	f(&x);	{
	printf("%d\n", x);	*y += x;
	}	}

Ответ: потому, что в каждом файле описана глобальная переменная x.  Надо в  одном  из
них  (или  в  обоих  сразу)  сделать  x локальным именем (исключить его из интерфейса
модуля):
   static int x=...;
Почему в следующем примере компилятор сообщает "_f дважды определено"?

	файл A.c	файл B.c
	----------------------------------------------------
	int x;	extern int x;
	main(){ f(5); g(77); }  g(n){ f(x+n); }
	f(n)  { x=n;	}  f(m){ printf("%d\n", m); }

Ответ: надо сделать в файле B.c функцию f локальной: static f(m)...
	Хоть в одном файле должна быть определена функция main, вызываемая системой  при
запуске  программы. Если такой функции нигде нет - компилятор выдает сообщение "_main
неопределено". Функция main должна быть определена один раз! В файле она может  нахо-
диться  в  любом  месте  -  не требуется, чтобы она была самой первой (или последней)
функцией файла[**].

1.140.  В чем ошибка?

	файл A.c	файл B.c
	----------------------------------------------------
	extern int x;	extern int x;
	main(){ x=2;	f(){
	f();	printf("%d\n", x);
	}	}

Ответ: переменная x в обоих файлах объявлена как extern, в результате память для  нее
нигде  не  выделена,  т.е.  x  не  определена ни в одном файле.  Уберите одно из слов
extern!

1.141.  В чем ошибка?

	файл A.c	файл B.c
	----------------------------------------------------
	int x;	extern double x;
	...	...

Типы[**] Если вы пользуетесь "новым" стилем объявления функций, но не используете  прото-
типы,  то  следует определять каждую функцию до первого места ее использования, чтобы
компилятору в точке вызова был известен ее заголовок. Это приведет к тому, что main()
окажется последней функцией в файле - ее не вызывает никто, зато она вызывает кого-то
еще.

общую выполняемую программу компоновщик знает лишь имена переменных и функций, но  не
их типы и прототипы.  В результате программа нормально скомпилируется и соберется, но
результат ее выполнения будет непредсказуем! Поэтому объявления extern  тоже  полезно
выносить в include-файлы:

	файл proto.h
	------------------
	extern int x;

	файл A.c	файл B.c
	------------------	------------------
	#include "proto.h"	#include "proto.h"
	int x;	...

то, что переменная x в A.c оказывается описанной и как  extern  -  вполне  допустимо,
т.к. в момент настоящего объявления этой переменной это слово начнет просто игнориро-
ваться (лишь бы типы в объявлении с extern и без него совпадали - иначе ошибка!).

1.142.  Что печатает программа и почему?

	int a = 1;  /* пример Bjarne Stroustrup-а */
	void f(){
	int b = 1;
	static int c = 1;
	printf("a=%d b=%d c=%d\n", a++, b++, c++);
	}
	void main(){
	while(a < 4) f();
	}

Ответ:

	a=1 b=1 c=1
	a=2 b=1 c=2
	a=3 b=1 c=3
1.143.  Автоматическая переменная видима только внутри блока, в котором она  описана.
Что напечатает программа?

	/* файл A.c x++, n);
	if(n) g(n-1); else x = 0;
	}
	/* файл B.c */
	extern x;	/*глобал*/
	f(n){	/*локал функции*/
	x++;	/*глобал*/
	{ int x;	/*локал блока*/
	x = n+1;  /*локал*/

	n = 2*x;  /*локал*/
	}
	x = n-1;	/*глобал*/
	}
1.144.  Функция, которая
-	не содержит внутри себя  статических  переменных,  хранящих  состояние  процесса
	обработки данных (функция без "памяти");
-	получает значения параметров только через свои аргументы (но не через глобальные
	статические переменные);
-	возвращает значения только через аргументы, либо  как  значение  функции  (через
	return);
называется реентерабельной (повторно входимой)  или  чистой
"потоков  обработки"  друг  на  друга.   Первый пункт требований позволяет функции не
зависеть ни  от какого конкретного процесса обработки данных, т.к.  она  не  "помнит"
обработанных  ею ранее данных и не строит свое поведение в зависимости от них. Вторые
два пункта - это требование, чтобы все без исключения пути передачи данных в  функцию
и  из  нее  (интерфейс  функции)  были перечислены в ее заголовке. Это лишает функцию
"побочных эффектов", не предусмотренных  программистом  при  ее  вызове  (программист
обычно  смотрит только на заголовок функции, и не выискивает "тайные" связи функции с
программой через глобальные переменные, если только  это  специально  не  оговорено).
Вот пример не реентерабельной функции:

	FILE *fp; ...  /* глобальный аргумент */
	char delayedInput ()
	{
	static char prevchar;  /* память */
	char c;
	c = prevchar;
	prevchar = getc (fp);
	return c;
	}

А вот ее реентерабельный эквивалент:

	char delayedInput (char *prevchar, FILE *fp)
	{
	char c;
	c = *prevchar;
	*prevchar = getc (fp);
	return c;
	}
	/* вызов: */
	FILE *fp1, *fp2; char prev1, prev2, c1, c2;
	... x1 = delayedInput (&prev1, fp1);
	x2 = delayedInput (&prev2, fp2); ...

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

	Вот еще один пример на эту тему. Не-реентерабельный вариант:

	int x, y, result;
	int f (){
	static int z = 4;
	y = x + z; z = y - 1;
	return x/2;
	}
	Вызов:	x=13; result = f(); printf("%d\n", y);

А вот реентерабельный эквивалент:

	int y, result, zmem = 4;
	int f (/*IN*/ int x, /*OUT*/ int *ay, /*INOUT*/ int *az){
	*az = (*ay = x + *az) - 1;
	return x/2;
	}
	Вызов:	result = f(13, &y, &zmem); printf("%d\n", y);
1.145.  То, что формат заголовка функции должен быть известен компилятору до  момента
ее использования, побуждает нас помещать определение функции до точки ее вызова. Так,
если main вызывает f, а f вызывает g, то в файле функции расположатся в порядке

	g()   {	}
	f()   { ... g(); ... }
	main(){ ... f(); ... }

Программа обычно разрабатывается "сверху-вниз" - от main к деталям.  Си же  вынуждает
нас  размещать  функции  в программе в обратном порядке, и в итоге программа читается
снизу-вверх - от деталей к main, и читать ее следует от конца файла к началу!
Так мы вынуждены писать, чтобы удовлетворить Си-компилятор:

	#include <stdio.h>

	unsigned long g(unsigned char *s){
	const int BITS = (sizeof(long) * 8);
	unsigned long sum = 0;

	for(;*s; s++){
	sum ^= *s;
	/* cyclic rotate left */
	sum = (sum<<1)|(sum>>(BITS-1));
	}
	return sum;
	}
	void f(char *s){
	printf("%s %lu\n", s, g((unsigned char *)s));
	}
	int main(int ac, char *av[]){
	int i;

	for(i=1; i < ac; i++)
	f(av[i]);
	return 0;
	}

А вот как мы разрабатываем программу:
	#include <stdio.h>

	int main(int ac, char *av[]){
	int i;

	for(i=1; i < ac; i++)
	f(av[i]);
	return 0;
	}
	void f(char *s){
	printf("%s %lu\n", s, g((unsigned char *)s));
	}
	unsigned long g(unsigned char *s){
	const int BITS = (sizeof(long) * 8);
	unsigned long sum = 0;

	for(;*s; s++){
	sum ^= *s;
	/* cyclic rotate left */
	sum = (sum<<1)|(sum>>(BITS-1));
	}
	return sum;
	}

и вот какую ругань производит Си-компилятор в ответ на эту программу:

	"0000.c", line 10: identifier redeclared: f
	current : function(pointer to char) returning void
	previous: function() returning int : "0000.c", line 7
	"0000.c", line 13: identifier redeclared: g
	current : function(pointer to uchar) returning ulong
	previous: function() returning int : "0000.c", line 11

Решением проблемы является - задать прототипы (объявления заголовков) всех функций  в
начале файла (или даже вынести их в header-файл).

	#include <stdio.h>

	int main(int ac, char *av[]);
	void f(char *s);
	unsigned long g(unsigned char *s);
	...

Тогда функции будет можно располагать в тексте в любом порядке.

1.146.  Рассмотрим процесс сборки программы из нескольких файлов на языке Си.   Пусть
мы  имеем  файлы file1.c, file2.c, file3.c (один из них должен содержать среди других
функций функцию main).  Ключ компилятора -o заставляет  создавать  выполняемую  прог-
рамму  с  именем, указанным после этого ключа. Если этот ключ не задан - будет создан
выполняемый файл a.out

	cc file1.c file2.c file3.c -o file

Мы получили выполняемую программу file.  Это эквивалентно 4-м командам:

	cc -c file1.c	получится	file1.o
	cc -c file2.c	file2.o
	cc -c file3.c	file3.o
	cc file1.o file2.o file3.o -o file

Ключ -c заставляет  компилятор  превратить  файл  на  языке  Си  в  "объектный"  файл

(содержащий  машинные  команды;  не будем вдаваться в подробности). Четвертая команда
"склеивает" объектные файлы в единое целое - выполняемую программу[*]  Если  же  каких-то  функций  не  окажется и там - будет выдано сообщение об
ошибке.
	Если у нас уже есть какие-то готовые объектные  файлы,  мы  можем  транслировать
только новые Си-файлы:

	cc -c file4.c
	cc file1.o file2.o file3.o file4.o -o file
	или (что то же самое,
	но cc сам разберется, что надо делать)
	cc file1.o file2.o file3.o file4.c -o file

Существующие у нас объектные файлы с отлаженными функциями удобно собрать  в  библио-
теку  - файл специальной структуры, содержащий все указанные файлы (все файлы склеены
в один длинный файл, разделяясь специальными заголовками, см. include-файл <ar.h>):

	ar r file.a file1.o file2.o file3.o

Будет создана библиотека file.a, содержащая перечисленные .o файлы (имена библиотек в
UNIX  имеют  суффикс  .a  - от слова archive, архив).  После этого можно использовать
библиотеку:

	cc file4.o file5.o file.a -o file

Механизм таков: если в файлах file4.o и file5.o
Тонкость: из библиотеки берутся не ВСЕ файлы, а лишь те, которые содержат определения
недостающих функций[**].  Если, в свою очередь, файлы, извлекаемые из библиотеки,  будут
содержать  неопределенные функции - библиотека (библиотеки) будут просмотрены еще раз
и.т.д. (на самом деле достаточно максимум двух проходов, так как при первом просмотре
библиотеки  можно  составить  ее  каталог:  где какие функции в ней содержатся и кого
вызывают).  Можно указывать и несколько библиотек:

	cc file6.c file7.o  \
	file.a mylib.a /lib/libLIBR1.a -o file

Таким образом, в команде cc можно смешивать имена файлов: исходных текстов на Си  .c,
объектных файлов .o и файлов-библиотек .a.
	Просмотр  библиотек,  находящихся  в  стандартных  местах  (каталогах   /lib   и
/usr/lib),  можно  включить  и  еще  одним способом: указав ключ -l.  Если библиотека
называется

	/lib/libLIBR1.a   или	/usr/lib/libLIBR2.a

то подключение делается ключами

	-lLIBR1	и	-lLIBR2
____________________
   [*] На самом деле, для "склейки" объектных файлов в выполняемую  программу,  команда
/bin/cc  вызывает программу /bin/ld - link editor, linker, редактор связей, компонов-
щик.
   [**] 1 функция;  либо  "пачка"  функций,  вызывающих  друг
друга.

соответственно.

	cc file1.c file2.c file3.o mylib.a -lLIBR1 -o file

Список библиотек и ключей -l должен идти после имен всех исходных .c и  объектных  .o
файлов.
	Библиотека стандартных функций языка  Си  /lib/libc.a  (ключ  -lc)  подключается
автоматически  ("подключить" библиотеку - значит вынудить компилятор просматривать ее
при сборке, если какие-то функции, использованные вами, не были вами определены),  то
есть  просматривается  всегда  (именно  эта  библиотека  содержит коды, например, для
printf, strcat, read).
	Многие прикладные пакеты функций поставляются именно в  виде  библиотек.   Такие
библиотеки состоят из ряда .o файлов, содержащих объектные коды для различных функций
(т.е. функции в скомпилированном виде).  Исходные тексты от большинства библиотек  не
поставляются  (так как являются коммерческой тайной). Тем не менее, вы можете исполь-
зовать эти функции, так как вам предоставляются разработчиком:
-	описание (документация).
-	include-файлы,  содержащие  форматы  данных  используемые  функциями  библиотеки
	(именно  эти  файлы включались #include в исходные тексты библ. функций.  Теперь
	уже вы должны включать их в свою программу).
Таким образом вы знаете, как надо вызывать библиотечные  функции  и  какие  структуры
данных вы должны использовать в своей программе для обращения к ним (хотя и не имеете
текстов самих библиотечных функций, т.е. не знаете, как они  устроены.  Например,  вы
часто  используете  printf(),  но  задумываетесь  ли вы о ее внутреннем устройстве?).
Некоторые библиотечные функции могут быть вообще написаны не на Си, а  на  ассемблере
или другом языке программирования[*][*].  Еще раз обращаю ваше внимание,  что  библиотека
содержит  не исходные тексты функций, а скомпилированные коды (и include-файлы содер-
жат (как правило) не тексты функций, а только описание форматов данных)!   Библиотека
может также содержать статические данные, вроде массивов строк-сообщений об ошибках.
	Посмотреть список файлов, содержащихся в библиотеке, можно командой

	ar tv имяФайлаБиблиотеки

а список имен функций - командой

	nm имяФайлаБиблиотеки

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

	ar x имяФайлаБиблиотеки имяФайла1 ...
	ar d имяФайлаБиблиотеки имяФайла1 ...

где ... означает список имен файлов.
	"Лицом" библиотек служат прилагаемые к ним  include-файлы.   Системные  include-
файлы, содержащие общие форматы данных для стандартных библиотечных функций, хранятся
в каталоге /usr/include  и подключаются так:

	для /usr/include/файл.h	надо  #include <файл.h>
	для /usr/include/sys/файл.h	#include <sys/файл.h>

____________________
   [*][*] Обратите внимание, что библиотечные функции не являются частью ЯЗЫКА Си как та-
кового.   То,  что  в  других  языках  (PL/1, Algol-68, Pascal) является частью языка
(встроено в язык)- в Си вынесено на уровень библиотек.  Например, в Си нет  оператора
вывода;  функция вывода printf - это библиотечная функция
онально расширяемым.

(sys - это каталог, где описаны форматы данных, используемых ядром  ОС  и  системными
вызовами).  Ваши собственные include-файлы (посмотрите в предыдущий раздел!) ищутся в
текущем каталоге и включаются при помощи

	#include "файл.h"	/*  ./файл.h	*/
	#include "../h/файл.h"	/*  ../h/файл.h	*/
	#include "/usr/my/файл.h" /*  /usr/my/файл.h */

  все  файлы .c могут использовать общие
include-файлы; их подстановку в текст, а также обработку #define  произведет  препро-
цессор cpp

	file1.c	file2.c	file3.c
	|	|	|	"препроцессор"
	| cpp	| cpp	| cpp
	|	|	|	"компиляция"
	| cc -c	| cc -c	| cc -c
	|	|	|
	file1.o	file2.o	file3.o
	|	|	|
	-----------*-----------
	|	Неявно добавятся:
	ld  |<----- /lib/libc.a (библ. станд. функций)
	|	/lib/crt0.o (стартер)
	"связывание" |
	"компоновка" |<----- Явно указанные библиотеки:
	|	-lm	/lib/libm.a
	V
	a.out
1.147. наподобие

	static char id[] = "This is /usr/abs/mybin/xprogram";

Тогда в случае аварии в файловой системе, если вдруг ваш файл "потеряется" (то есть у
него  пропадет  имя  -  например из-за порчи каталога), то он будет найден программой
проверки файловой системы - fsck - и помещен в каталог  /lost+found  под  специальным
кодовым  именем,  ничего  общего  не имеющим со старым.  Чтобы понять, что это был за
файл и во что его следует переименовать (чтобы восстановить правильное имя), мы  при-
меним команду

	strings имя_файла

Эта команда покажет все длинные строки из печатных символов,  содержащиеся  в  данном
файле,  в  частности  и  нашу строку id[].  Увидев ее, мы сразу поймем, что файл надо
переименовать так:

	mv имя_файла /usr/abs/mybin/xprogram
1.148.  Где размещать include-файлы и как программа узнает, где же они лежат?   Стан-
дартные  системные  include-файлы  размещены  в /usr/include и подкаталогах.  Если мы
пишем некую свою программу (проект) и используем директивы

	#include "имяФайла.h"
то обычно include-файлы имяФайла.h  и  тем  же  проектом).
Хорошее место для всех ваших личных include-файлов - каталог (вами созданный)

	$HOME/include

где $HOME - ваш домашний каталог.  Хорошее место для общих include-файлов - каталог

	/usr/local/include

Как сказать компилятору, что #include "" файлы надо брать из определенного  места,  а
не из текущего каталога? Это делает ключ компилятора

	cc -Iимя_каталога ...

Например:

	/* Файл x.c */
	#include "x.h"

	int main(int ac, char *av[]){
	....
	return 0;
	}

И файл x.h находится в каталоге /home/abs/include/x.h (/home/abs - мой домашний ката-
лог).  Запуск программы на компиляцию выглядит так:

	cc -I/home/abs/include -O x.c -o x
	или
	cc -I$HOME/include -O x.c -o x

Или, если моя программа x.c находится в /home/abs/progs

	cc -I../include -O x.c -o x

Ключ -O задает вызов компилятора с оптимизацией.
	Ключ -I оказывает влияние и на #include <> директивы тоже.  Для  ОС  Solaris  на
машинах Sun программы для оконной системы X Window System содержат строки вроде

	#include <X11/Xlib.h>
	#include <X11/Xutil.h>

На Sun эти файлы находятся не в /usr/include/X11, а в /usr/openwin/include/X11.  Поэ-
тому запуск на компиляцию оконных программ на Sun выглядит так:

	cc -O -I/usr/openwin/include xprogram.c \
	-o xprogram -L/usr/openwin/lib -lX11

где -lX11 задает подключение графической оконной библиотеки Xlib.
	Если include-файлы находятся во многих каталогах, то можно задать поиск  в  нес-
кольких каталогах, к примеру:

	cc -I/usr/openwin/include -I/usr/local/include -I$HOME/include ...

[Назад] [Содержание] [Вперед]

Главная