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-файл), дабы все прог-
раммы придерживались одних и тех же общих соглашений. Даже если эти соглашения со
А. Богатырев, 1992-95 - 70 - Си в UNIX
временем изменятся, то они изменятся во всех файлах синхронно и как бы сами собой. В
нашем случае исправлять определение структуры придется только в include-файле, а не
выискивать все места, где оно написано, ведь при этом немудрено какое-нибудь место и
пропустить!
W.h
-----------------------
struct W{ long x, y; };
a.c b.c
-------------------------- ------------------
#include #include
#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.
А. Богатырев, 1992-95 - 71 - Си в 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.
А. Богатырев, 1992-95 - 72 - Си в UNIX
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()
окажется последней функцией в файле - ее не вызывает никто, зато она вызывает кого-то
еще.
А. Богатырев, 1992-95 - 73 - Си в UNIX
общую выполняемую программу компоновщик знает лишь имена переменных и функций, но не
их типы и прототипы. В результате программа нормально скомпилируется и соберется, но
результат ее выполнения будет непредсказуем! Поэтому объявления 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 */
int x=666; /*глоб.*/
main(){
f(3);
printf(" ::x = %d\n", x);
g(2); g(5);
printf(" ::x = %d\n", x);
}
g(n){
static int x=17; /*видима только в g*/
printf("g::x = %2d g::n = %d\n", x++, n);
if(n) g(n-1); else x = 0;
}
/* файл B.c */
extern x; /*глобал*/
f(n){ /*локал функции*/
x++; /*глобал*/
{ int x; /*локал блока*/
x = n+1; /*локал*/
А. Богатырев, 1992-95 - 74 - Си в UNIX
n = 2*x; /*локал*/
}
x = n-1; /*глобал*/
}
1.144. Функция, которая
- не содержит внутри себя статических переменных, хранящих состояние процесса
обработки данных (функция без "памяти");
- получает значения параметров только через свои аргументы (но не через глобальные
статические переменные);
- возвращает значения только через аргументы, либо как значение функции (через
return);
называется реентерабельной (повторно входимой) или чистой (pure). Такая функция
может параллельно (или псевдопараллельно) использоваться несколькими "потоками" обра-
ботки информации в нашей программе, без какого-либо непредвиденного влияния этих
"потоков обработки" друг на друга. Первый пункт требований позволяет функции не
зависеть ни от какого конкретного процесса обработки данных, т.к. она не "помнит"
обработанных ею ранее данных и не строит свое поведение в зависимости от них. Вторые
два пункта - это требование, чтобы все без исключения пути передачи данных в функцию
и из нее (интерфейс функции) были перечислены в ее заголовке. Это лишает функцию