; close (0); /* 0 - стандартный ввод */ fcntl (fd, F_DUPFD, 0 ); /* 0 - стандартный ввод */ close (fd); Это перенаправление ввода соответствует конструкции $ a.out < имя_файла написанной на командном языке СиШелл. Для перенаправления вывода замените 0 на 1, stdin на stdout, open на creat, "r" на "w". Рассмотрим механику работы вызова dup2 |-: new = open("файл1",...); dup2(new, old); close(new); таблица открытых файлов процесса ...## ## new----##---> файл1 new---##---> файл1 ## ## old----##---> файл2 old---## файл2 ## ## 0:до вызова 1:разрыв связи old с файл2 dup2() (закрытие канала old, если он был открыт) ## ## new----##--*--> файл1 new ## *----> файл1 ## | ## | old----##--* old--##--* ## ## 2:установка old на файл1 3:после оператора close(new); на этом dup2 завершен. дескриптор new закрыт. Здесь файл1 и файл2 - связующие структуры "открытый файл" в ядре, о которых рассказы- валось выше (в них содержатся указатели чтения/записи). После вызова dup2 дескрипторы new и old ссылаются на общую такую структуру и поэтому имеют один и тот же R/W- указатель. Это означает, что в программе new и old являются синонимами и могут использоваться даже вперемежку: dup2(new, old); write(new, "a", 1); write(old, "b", 1); write(new, "c", 1); запишет в файл1 строку "abc". Программа ____________________ |- Функция int system(char *команда); выполняет команду, записанную в строке команда, вызывая для этого интерпретатор ко- манд /bin/sh -c "команда" А. Богатырев, 1992-95 - 159 - Си в UNIX int fd; printf( "Hi there\n"); fd = creat( "newout", 0640 ); dup2(fd, 1); close(fd); printf( "Hey, You!\n"); выдаст первое сообщение на терминал, а второе - в файл newout, поскольку printf выдает данные в канал stdout, связанный с дескриптором 1. 4.33. Напишите программу, которая будет выдавать подряд в стандартный вывод все файлы, чьи имена указаны в аргументах командной строки. Используйте argc для органи- зации цикла. Добавьте сквозную нумерацию строк и печать номера строки. 4.34. Напишите программу, распечатывающую первую директиву препроцессора, встретив- шуюся в файле ввода. #include <stdio.h> char buf[512], word[] = "#"; main(){ char *s; int len = strlen(word); while((s=fgets(buf, sizeof buf, stdin)) && strncmp(s, word, len)); fputs(s? s: "Не найдено.\n", stdout); } 4.35. Напишите программу, которая переключает свой стандартный вывод в новый файл имяФайла каждый раз, когда во входном потоке встречается строка вида >>>имяФайла Ответ: #include <stdio.h> char line[512]; main(){ FILE *fp = fopen("00", "w"); while(gets(line) != NULL) if( !strncmp(line, ">>>", 3)){ if( freopen(line+3, "a", fp) == NULL){ fprintf(stderr, "Can't write to '%s'\n", line+3); fp = fopen("00", "a"); } } else fprintf(fp, "%s\n", line); } 4.36. Библиотека буферизованного обмена stdio содержит функции, подобные некоторым системным вызовам. Вот функции - аналоги read и write: Стандартная функция fread из библиотеки стандартных функций Си предназначена для чтения нетекстовой (как правило) информации из файла: ____________________ и возвращает код ответа этой программы. Функция popen (pipe open) также запускает интерпретатор команд, при этом перенаправив его стандартный вывод в трубу (pipe). Другой конец этой трубы можно читать через канал fp, т.е. можно прочесть в свою прог- рамму выдачу запущенной команды. ____________________ |- dup2 читается как "dup to", в английском жаргоне принято обозначать предлог "to" цифрой 2, поскольку слова "to" и "two" произносятся одинаково: "ту". "From me 2 You". Также 4 читается как "for". А. Богатырев, 1992-95 - 160 - Си в UNIX int fread(addr, size, count, fp) register char *addr; unsigned size, count; FILE *fp; { register c; unsigned ndone=0, sz; if(size) for( ; ndone < count ; ndone++){ sz = size; do{ if((c = getc(fp)) >= 0 ) *addr++ = c; else return ndone; }while( --sz ); } return ndone; } Заметьте, что count - это не количество БАЙТ (как в read), а количество ШТУК размером size байт. Функция выдает число целиком прочитанных ею ШТУК. Существует аналогичная функция fwrite для записи в файл. Пример: #include <stdio.h> #define MAXPTS 200 #define N 127 char filename[] = "pts.dat"; struct point { int x,y; } pts[MAXPTS], pp= { -1, -2}; main(){ int n, i; FILE *fp = fopen(filename, "w"); for(i=0; i < N; i++) /* генерация точек */ pts[i].x = i, pts[i].y = i * i; /* запись массива из N точек в файл */ fwrite((char *)pts, sizeof(struct point), N, fp); fwrite((char *)&pp, sizeof pp, 1, fp); fp = freopen(filename, "r", fp); /* или fclose(fp); fp=fopen(filename, "r"); */ /* чтение точек из файла в массив */ n = fread(pts, sizeof pts[0], MAXPTS, fp); for(i=0; i < n; i++) printf("Точка #%d(%d,%d)\n",i,pts[i].x,pts[i].y); } Файлы, созданные fwrite, не переносимы на машины другого типа, поскольку в них хра- нится не текст, а двоичные данные в формате, используемом данным процессором. Такой файл не может быть понят человеком - он не содержит изображений данных в виде текста, а содержит "сырые" байты. Поэтому чаще пользуются функциями работы с текстовыми фай- лами: fprintf, fscanf, fputs, fgets. Данные, хранимые в виде текста, имеют еще одно преимущество помимо переносимости: их легко при нужде подправить текстовым редакто- ром. Зато они занимают больше места! Аналогом системного вызова lseek служит функция fseek: fseek(fp, offset, whence); Она полностью аналогична lseek, за исключением возвращаемого ею значения. Она НЕ возвращает новую позицию указателя чтения/записи! Чтобы узнать эту позицию применя- ется специальная функция long ftell(fp); Она вносит поправку на положение указателя в буфере канала fp. fseek сбрасывает флаг "был достигнут конец файла", который проверяется макросом feof(fp); А. Богатырев, 1992-95 - 161 - Си в UNIX 4.37. Найдите ошибку в программе (программа распечатывает корневой каталог в "ста- ром" формате каталогов - с фиксированной длиной имен): #include <stdio.h> #include <sys/types.h> #include <sys/dir.h> main(){ FILE *fp; struct direct d; char buf[DIRSIZ+1]; buf[DIRSIZ] = '\0'; fp = fopen( '/', "r" ); while( fread( &d, sizeof d, 1, fp) == 1 ){ if( !d.d_ino ) continue; /* файл стерт */ strncpy( buf, d.d_name, DIRSIZ); printf( "%s\n", buf ); } fclose(fp); } Указание: смотри в fopen(). Внимательнее к строкам и символам! '/' и "/" - это совершенно разные вещи (хотя синтаксической ошибки нет!). Переделайте эту программу, чтобы название каталога поступало из аргументов main (а если название не задано - используйте текущий каталог "."). 4.38. Функциями fputs( строка, fp); printf( формат, ...); fprintf(fp, формат, ...); невозможно вывести строку формат, содержащую в середине байт '\0', поскольку он слу- жит для них признаком конца строки. Однако такой байт может понадобиться в файле, если мы формируем некоторые нетекстовые данные, например управляющую последователь- ность переключения шрифтов для принтера. Как быть? Есть много вариантов решения. Пусть мы хотим выдать в канал fp последовательность из 4х байт "\033e\0\5". Мы можем сделать это посимвольно: putc('\033',fp); putc('e', fp); putc('\000',fp); putc('\005',fp); (можно просто в цикле), либо использовать один из способов: fprintf( fp, "\033e%c\5", '\0'); write ( fileno(fp), "\033e\0\5", 4 ); fwrite ( "\033e\0\5", sizeof(char), 4, fp); где 4 - количество выводимых байтов. 4.39. Напишите функции для "быстрого доступа" к строкам файла. Идея такова: сначала прочитать весь файл от начала до конца и смещения начал строк (адреса по файлу) запомнить в массив чисел типа long (точнее, off_t), используя функции fgets() и ftell(). Для быстрого чтения n-ой строки используйте функции fseek() и fgets(). #include <stdio.h> #define MAXLINES 2000 /* Максим. число строк в файле*/ FILE *fp; /* Указатель на файл */ int nlines; /* Число строк в файле */ long offsets[MAXLINES];/* Адреса начал строк */ extern long ftell();/*Выдает смещение от начала файла*/ А. Богатырев, 1992-95 - 162 - Си в UNIX char buffer[256]; /* Буфер для чтения строк */ /* Разметка массива адресов начал строк */ void getSeeks(){ int c; offsets[0] =0L; while((c = getc(fp)) != EOF) if(c =='\n') /* Конец строки - начало новой */ offsets[++nlines] = ftell(fp); /* Если последняя строка файла не имеет \n на конце, */ /* но не пуста, то ее все равно надо посчитать */ if(ftell(fp) != offsets[nlines]) nlines++; printf( "%d строк в файле\n", nlines); } char *getLine(n){ /* Прочесть строку номер n */ fseek(fp, offsets[n], 0); return fgets(buffer, sizeof buffer, fp); } void main(){ /* печать файла задом-наперед */ int i; fp = fopen("INPUT", "r"); getSeeks(); for( i=nlines-1; i>=0; --i) printf( "%3d:%s", i, getLine(i)); } 4.40. Что будет выдано на экран в результате выполнения программы? #include <stdio.h> main(){ printf( "Hello, " ); printf( "sunny " ); write( 1, "world", 5 ); } Ответ: очень хочется ответить, что будет напечатано "Hello, sunny world", поскольку printf выводит в канал stdout, связанный с дескриптором 1, а дескриптор 1 связан по- умолчанию с терминалом. Увы, эта догадка верна лишь отчасти! Будет напечатано "worldHello, sunny ". Это происходит потому, что вывод при помощи функции printf буферизован, а при помощи сисвызова write - нет. printf помещает строку сначала в буфер канала stdout, затем write выдает свое сообщение непосредственно на экран, затем по окончании программы буфер выталкивается на экран. Чтобы получить правильный эффект, следует перед write() написать вызов явного выталкивания буфера канала stdout: fflush( stdout ); Еще одно возможное решение - отмена буферизации канала stdout: перед первым printf можно написать setbuf(stdout, NULL); Имейте в виду, что канал вывода сообщений об ошибках stderr не буферизован исходно, поэтому выдаваемые в него сообщения печатаются немедленно. Мораль: надо быть очень осторожным при смешанном использовании буферизованного и небуферизованного обмена. А. Богатырев, 1992-95 - 163 - Си в UNIX Некоторые каналы буферизуются так, что буфер выталкивается не только при запол- нении, но и при поступлении символа '\n' ("построчная буферизация"). Канал stdout именно таков: printf("Hello\n"); печатается сразу (т.к. printf выводит в stdout и есть '\n'). Включить такой режим буферизации можно так: setlinebuf(fp); или в других версиях setvbuf(fp, NULL, _IOLBF, BUFSIZ); Учтите, что любое изменение способа буферизации должно быть сделано ДО первого обра- щения к каналу! 4.41. Напишите программу, выдающую три звуковых сигнала. Гудок на терминале вызыва- ется выдачей символа '\7' ('\a' по стандарту ANSI). Чтобы гудки звучали раздельно, надо делать паузу после каждого из них. (Учтите, что вывод при помощи printf() и putchar() буферизован, поэтому после выдачи каждого гудка (в буфер) надо вызывать функцию fflush() для сброса буфера). Ответ: Способ 1: register i; for(i=0; i<3; i++){ putchar( '\7' ); fflush(stdout); sleep(1); /* пауза 1 сек. */ } Способ 2: register i; for(i=0; i<3; i++){ write(1, "\7", 1 ); sleep(1); } 4.42. Почему задержка не ощущается? printf( "Пауза..."); sleep ( 5 ); /* ждем 5 сек. */ printf( "продолжаем\n" ); Ответ: из-за буферизации канала stdout. Первая фраза попадает в буфер и, если он не заполнился, не выдается на экран. Дальше программа "молчаливо" ждет 5 секунд. Обе фразы будут выданы уже после задержки! Чтобы первый printf() выдал свою фразу ДО задержки, следует перед функцией sleep() вставить вызов fflush(stdout) для явного выталкивания буфера. Замечание: канал stderr не буферизован, поэтому проблему можно решить и так: fprintf( stderr, "Пауза..." ); 4.43. Еще один пример про буферизацию. Почему программа печатает EOF? #include <stdio.h> FILE *fwr, *frd; char b[40], *s; int n = 1917; main(){ fwr = fopen( "aFile", "w" ); А. Богатырев, 1992-95 - 164 - Си в UNIX frd = fopen( "aFile", "r" ); fprintf( fwr, "%d: Hello, dude!", n); s = fgets( b, sizeof b, frd ); printf( "%s\n", s ? s : "EOF" ); } Ответ: потому что к моменту чтения буфер канала fwr еще не вытолкнут в файл: файл пуст! Надо вставить fflush(fwr); после fprintf(). Вот еще подобный случай: FILE *fp = fopen("users", "w"); ... fprintf(fp, ...); ... system("sort users | uniq > 00; mv 00 users"); К моменту вызова команды сортировки буфер канала fp (точнее, последний из накопленных за время работы буферов) может быть еще не вытолкнут в файл. Следует либо закрыть файл fclose(fp) непосредственно перед вызовом system, либо вставить туда же fflush(fp); 4.44. В UNIX многие внешние устройства (практически все!) с точки зрения программ являются просто файлами. Файлы-устройства имеют имена, но не занимают места на диске (не имеют блоков). Зато им соответствуют специальные программы-драйверы в ядре. При открытии такого файла-устройства мы на самом деле инициализируем драйвер этого уст- ройства, и в дальнейшем он выполняет наши запросы read, write, lseek аппаратно- зависимым образом. Для операций, специфичных для данного устройства, предусмотрен сисвызов ioctl (input/output control): ioctl(fd, РОД_РАБОТЫ, аргумент); где аргумент часто бывает адресом структуры, содержащей пакет аргументов, а РОД_РАБОТЫ - одно из целых чисел, специфичных для данного устройства (для каждого устр-ва есть свой собственный список допустимых операций). Обычно РОД_РАБОТЫ имеет некоторое мнемоническое обозначение. В качестве примера приведем операцию TCGETA, применимую только к терминалам и узнающую текущие моды драйвера терминала (см. главу "Экранные библиотеки"). То, что эта операция неприменима к другим устройствам и к обычным файлам (не устройствам), позволяет нам использовать ее для проверки - является ли открытый файл терминалом (или клавиатурой): #include <termio.h> int isatty(fd){ struct termio tt; return ioctl(fd, TCGETA, &tt) < 0 ? 0 : 1; } main(){ printf("%s\n", isatty(0 /* STDIN */)? "term":"no"); } Функция isatty является стандартной функцией|-. Есть "псевдоустройства", которые представляют собой драйверы логических уст- ройств, не связанных напрямую с аппаратурой, либо связанных лишь косвенно. Примером такого устройства является псевдотерминал (см. пример в приложении). Наиболее упот- ребительны два псевдоустройства: /dev/null Это устройство, представляющее собой "черную дыру". Чтение из него немедленно выдает признак конца файла: read(...)==0; а записываемая в него информация нигде не сохраняется (пропадает). Этот файл используется, например, в том случае, когда мы хотим проигнорировать вывод какой-либо программы (сообщения об ошибках, трассировку), нигде его не сохраняя. Тогда мы просто перенаправляем ее вывод в /dev/null: А. Богатырев, 1992-95 - 165 - Си в UNIX $ a.out > /dev/null & Еще один пример использования: $ cp /dev/hd00 /dev/null Содержимое всего винчестера копируется "в никуда". При этом, если на диске есть сбойные блоки - система выдает на консоль сообщения об ошибках чтения. Так мы можем быстро выяснить, есть ли на диске плохие блоки. /dev/tty Открытие файла с таким именем в действительности открывает для нас управляющий терминал, на котором запущена данная программа; даже если ее ввод и вывод были перенаправлены в какие-то другие файлы|=. Поэтому, если мы хотим выдать сообще- ние, которое должно появиться именно на экране, мы должны поступать так: #include <stdio.h> void message(char *s){ FILE *fptty = fopen("/dev/tty", "w"); fprintf(fptty, "%s\n", s); fclose (fptty); } main(){ message("Tear down the wall!"); } Это устройство доступно и для записи (на экран) и для чтения (с клавиатуры). Файлы устройств нечувствительны к флагу открытия O_TRUNC - он не имеет для них смысла и просто игнорируется. Поэтому невозможно случайно уничтожить файл-устройство (к при- меру /dev/tty) вызовом fd=creat("/dev/tty", 0644); Файлы-устройства создаются вызовом mknod, а уничтожаются обычным unlink-ом. Более подробно про это - в главе "Взаимодействие с UNIX". 4.45. Эмуляция основ библиотеки STDIO, по мотивам 4.2 BSD. #include <fcntl.h> #define BUFSIZ 512 /* стандартный размер буфера */ #define _NFILE 20 #define EOF (-1) /* признак конца файла */ #define NULL ((char *) 0) #define IOREAD 0x0001 /* для чтения */ #define IOWRT 0x0002 /* для записи */ #define IORW 0x0004 /* для чтения и записи */ #define IONBF 0x0008 /* не буферизован */ #define IOTTY 0x0010 /* вывод на терминал */ #define IOALLOC 0x0020 /* выделен буфер malloc-ом */ #define IOEOF 0x0040 /* достигнут конец файла */ #define IOERR 0x0080 /* ошибка чтения/записи */ ____________________ |- Заметим еще, что если дескриптор fd связан с терминалом, то можно узнать полное имя этого устройства вызовом стандартной функции extern char *ttyname(); char *tname = ttyname(fd); Она выдаст строку, подобную "/dev/tty01". Если fd не связан с терминалом - она вернет А. Богатырев, 1992-95 - 166 - Си в UNIX extern char *malloc(); extern long lseek(); typedef unsigned char uchar; uchar sibuf[BUFSIZ], sobuf[BUFSIZ]; typedef struct _iobuf { int cnt; /* счетчик */ uchar *ptr, *base; /* указатель в буфер и на его начало */ int bufsiz, flag, file; /* размер буфера, флаги, дескриптор */ } FILE; FILE iob[_NFILE] = { { 0, NULL, NULL, 0, IOREAD, 0 }, { 0, NULL, NULL, 0, IOWRT|IOTTY, 1 }, { 0, NULL, NULL, 0, IOWRT|IONBF, 2 }, }; #define stdin (&iob[0]) #define stdout (&iob[1]) #define stderr (&iob[2]) #define putchar(c) putc((c), stdout) #define getchar() getc(stdin) #define fileno(fp) ((fp)->file) #define feof(fp) (((fp)->flag & IOEOF) != 0) #define ferror(fp) (((fp)->flag & IOERR) != 0) #define clearerr(fp) ((void) ((fp)->flag &= ~(IOERR | IOEOF))) #define getc(fp) (--(fp)->cnt < 0 ? \ filbuf(fp) : (int) *(fp)->ptr++) #define putc(x, fp) (--(fp)->cnt < 0 ? \ flsbuf((uchar) (x), (fp)) : \ (int) (*(fp)->ptr++ = (uchar) (x))) int fputc(int c, FILE *fp){ return putc(c, fp); } int fgetc( FILE *fp){ return getc(fp); } ____________________ NULL. ____________________ |= Ссылка на управляющий терминал процесса хранится в u-area каждого процесса: u_ttyp, u_ttyd, поэтому ядро в состоянии определить какой настоящий терминал следует открыть для вас. Если разные процессы открывают /dev/tty, они могут открыть в итоге разные терминалы, т.е. одно имя приводит к разным устройствам! Смотри главу про UNIX. А. Богатырев, 1992-95 - 167 - Си в UNIX /* Открытие файла */ FILE *fopen(char *name, char *how){ register FILE *fp; register i, rw; for(fp = iob, i=0; i < _NFILE; i++, fp++) if(fp->flag == 0) goto found; return NULL; /* нет свободного слота */ found: rw = how[1] == '+'; if(*how == 'r'){ if((fp->file = open(name, rw ? O_RDWR:O_RDONLY)) < 0) return NULL; fp->flag = IOREAD; } else { if((fp->file = open(name, (rw ? O_RDWR:O_WRONLY)| O_CREAT | (*how == 'a' ? O_APPEND : O_TRUNC), 0666 )) < 0) return NULL; fp->flag = IOWRT; } if(rw) fp->flag = IORW; fp->bufsiz = fp->cnt = 0; fp->base = fp->ptr = NULL; return fp; } /* Принудительный сброс буфера */ void fflush(FILE *fp){ uchar *base; int full= 0; if((fp->flag & (IONBF|IOWRT)) == IOWRT && (base = fp->base) != NULL && (full=fp->ptr - base) > 0){ fp->ptr = base; fp->cnt = fp->bufsiz; if(write(fileno(fp), base, full) != full) fp->flag |= IOERR; } } /* Закрытие файла */ void fclose(FILE *fp){ if((fp->flag & (IOREAD|IOWRT|IORW)) == 0 ) return; fflush(fp); close(fileno(fp)); if(fp->flag & IOALLOC) free(fp->base); fp->base = fp->ptr = NULL; fp->cnt = fp->bufsiz = fp->flag = 0; fp->file = (-1); } /* Закрытие файлов при exit()-е */ void _cleanup(){ register i; for(i=0; i < _NFILE; i++) fclose(iob + i); } /* Завершить текущий процесс */ void exit(uchar code){ _cleanup(); _exit(code); /* Собственно системный вызов */ } А. Богатырев, 1992-95 - 168 - Си в UNIX /* Прочесть очередной буфер из файла */ int filbuf(FILE *fp){ static uchar smallbuf[_NFILE]; if(fp->flag & IORW){ if(fp->flag & IOWRT){ fflush(fp); fp->flag &= ~IOWRT; } fp->flag |= IOREAD; /* операция чтения */ } if((fp->flag & IOREAD) == 0 || feof(fp)) return EOF; while( fp->base == NULL ) /* отвести буфер */ if( fp->flag & IONBF ){ /* небуферизованный */ fp->base = &smallbuf[fileno(fp)]; fp->bufsiz = sizeof(uchar); } else if( fp == stdin ){ /* статический буфер */ fp->base = sibuf; fp->bufsiz = sizeof(sibuf); } else if((fp->base = malloc(fp->bufsiz = BUFSIZ)) == NULL) fp->flag |= IONBF; /* не будем буферизовать */ else fp->flag |= IOALLOC; /* буфер выделен */ if( fp == stdin && (stdout->flag & IOTTY)) fflush(stdout); fp->ptr = fp->base; /* сбросить на начало буфера */ if((fp->cnt = read(fileno(fp), fp->base, fp->bufsiz)) == 0 ){ fp->flag |= IOEOF; if(fp->flag & IORW) fp->flag &= ~IOREAD; return EOF; } else if( fp->cnt < 0 ){ fp->flag |= IOERR; fp->cnt = 0; return EOF; } return getc(fp); } А. Богатырев, 1992-95 - 169 - Си в UNIX /* Вытолкнуть очередной буфер в файл */ int flsbuf(int c, FILE *fp){ uchar *base; int full, cret = c; if( fp->flag & IORW ){ fp->flag &= ~(IOEOF|IOREAD); fp->flag |= IOWRT; /* операция записи */ } if((fp->flag & IOWRT) == 0) return EOF; tryAgain: if(fp->flag & IONBF){ /* не буферизован */ if(write(fileno(fp), &c, 1) != 1) { fp->flag |= IOERR; cret=EOF; } fp->cnt = 0; } else { /* канал буферизован */ if((base = fp->base) == NULL){ /* буфера еще нет */ if(fp == stdout){ if(isatty(fileno(stdout))) fp->flag |= IOTTY; else fp->flag &= ~IOTTY; fp->base = fp->ptr = sobuf; /* статический буфер */ fp->bufsiz = sizeof(sobuf); goto tryAgain; } if((base = fp->base = malloc(fp->bufsiz = BUFSIZ))== NULL){ fp->bufsiz = 0; fp->flag |= IONBF; goto tryAgain; } else fp->flag |= IOALLOC; } else if ((full = fp->ptr - base) > 0) if(write(fileno(fp), fp->ptr = base, full) != full) { fp->flag |= IOERR; cret = EOF; } fp->cnt = fp->bufsiz - 1; *base++ = c; fp->ptr = base; } return cret; } /* Вернуть символ в буфер */ int ungetc(int c, FILE *fp){ if(c == EOF || fp->flag & IONBF || fp->base == NULL) return EOF; if((fp->flag & IOREAD)==0 || fp->ptr <= fp->base) if(fp->ptr == fp->base && fp->cnt == 0) fp->ptr++; else return EOF; fp->cnt++; return(* --fp->ptr = c); } /* Изменить размер буфера */ void setbuffer(FILE *fp, uchar *buf, int size){ fflush(fp); if(fp->base && (fp->flag & IOALLOC)) free(fp->base); fp->flag &= ~(IOALLOC|IONBF); if((fp->base = fp->ptr = buf) == NULL){ fp->flag |= IONBF; fp->bufsiz = 0; } else fp->bufsiz = size; fp->cnt = 0; } А. Богатырев, 1992-95 - 170 - Си в UNIX /* "Перемотать" файл в начало */ void rewind(FILE *fp){ fflush(fp); lseek(fileno(fp), 0L, 0); fp->cnt = 0; fp->ptr = fp->base; clearerr(fp); if(fp->flag & IORW) fp->flag &= ~(IOREAD|IOWRT); } /* Позиционирование указателя чтения/записи */ #ifdef COMMENT base ptr случай IOREAD | |<----cnt---->| 0L |б у |ф е р | |=======######@@@@@@@@@@@@@@======== файл file | |<-p->|<-dl-->| |<----pos---->| | | |<----offset(new)-->| | |<----RWptr---------------->| где pos = RWptr - cnt; // указатель с поправкой offset = pos + p = RWptr - cnt + p = lseek(file,0L,1) - cnt + p отсюда: (для SEEK_SET) p = offset+cnt-lseek(file,0L,1); или (для SEEK_CUR) dl = RWptr - offset = p - cnt lseek(file, dl, 1); Условие, что указатель можно сдвинуть просто в буфере: if( cnt > 0 && p <= cnt && base <= ptr + p ){ ptr += p; cnt -= p; } #endif /*COMMENT*/ А. Богатырев, 1992-95 - 171 - Си в UNIX int fseek(FILE *fp, long offset, int whence){ register resync, c; long p = (-1); clearerr(fp); if( fp->flag & (IOWRT|IORW)){ fflush(fp); if(fp->flag & IORW){ fp->cnt = 0; fp->ptr = fp->base; fp->flag &= ~IOWRT; } p = lseek(fileno(fp), offset, whence); } else if( fp->flag & IOREAD ){ if(whence < 2 && fp->base && !(fp->flag & IONBF)){ c = fp->cnt; p = offset; if(whence == 0) /* SEEK_SET */ p += c - lseek(fileno(fp), 0L, 1); else offset -= c; if(!(fp->flag & IORW) && c > 0 && p <= c && p >= fp->base - fp->ptr ){ fp->ptr += (int) p; fp->cnt -= (int) p; return 0; /* done */ } resync = offset & 01; } else resync = 0; if(fp->flag & IORW){ fp->ptr = fp->base; fp->flag &= ~IOREAD; resync = 0; } p = lseek(fileno(fp), offset-resync, whence); fp->cnt = 0; /* вынудить filbuf(); */ if(resync) getc(fp); } return (p== -1 ? -1 : 0); } /* Узнать текущую позицию указателя */ long ftell(FILE *fp){ long tres; register adjust; if(fp->cnt < 0) fp->cnt = 0; if(fp->flag & IOREAD) adjust = -(fp->cnt); else if(fp->flag & (IOWRT|IORW)){ adjust = 0; if(fp->flag & IOWRT && fp->base && !(fp->flag & IONBF)) /* буферизован */ adjust = fp->ptr - fp->base; } else return (-1L); if((tres = lseek(fileno(fp), 0L, 1)) < 0) return tres; return (tres + adjust); } А. Богатырев, 1992-95 - 172 - Си в UNIX 5. Структуры данных. Структуры ("записи") представляют собой агрегаты разнородных данных (полей раз- ного типа); в отличие от массивов, где все элементы имеют один и тот же тип. struct { int x, y; /* два целых поля */ char s[10]; /* и одно - для строки */ } s1; Структурный тип может иметь имя: struct XYS { int x, y; /* два целых поля */ char str[10]; /* и одно - для строки */ }; Здесь мы объявили тип, но не отвели ни одной переменной этого типа (хотя могли бы). Теперь опишем переменную этого типа и указатель на нее: struct XYS s2, *sptr = &s2; Доступ к полям структуры производится по имени поля (а не по индексу, как у масси- вов): имя_структурной_переменной.имя_поля указатель_на_структуру -> имя_поля то есть не а #define ВЕС 0 struct { int вес, рост; } x; #define РОСТ 1 x.рост = 175; int x[2]; x[РОСТ] = 175; Например s1.x = 13; strcpy(s2.str, "Finish"); sptr->y = 27; Структура может содержать структуры другого типа в качестве полей: struct XYS_Z { struct XYS xys; int z; } a1; a1.xys.x = 71; a1.z = 12; Структура того же самого типа не может содержаться в качестве поля - рекурсивные определения запрещены. Зато нередко используются поля - ссылки на структуры такого же типа (или другого). Это позволяет организовывать списки структур: struct node { int value; struct node *next; }; Очень часто используются массивы структур: А. Богатырев, 1992-95 - 173 - Си в UNIX struct XYS array[20]; int i = 5, j; array[i].x = 12; j = array[i].x; Статические структуры можно описывать с инициализацией, перечисляя значения их полей в {} через запятую: extern struct node n2; struct node n1 = { 1, &n2 }, n2 = { 2, &n1 }, n3 = { 3, NULL }; В этом примере n2 описано предварительно для того, чтобы &n2 в строке инициализации n1 было определено. Структуры одинакового типа можно присваивать целиком (что соответствует присваи- ванию каждого из полей): struct XYS s1, s2; ... s2 = s1; в отличие от массивов, которые присваивать целиком нельзя: int a[5], b[5]; a = b; /* ОШИБОЧНО ! */ Пример обращения к полям структуры: typedef struct _Point { short x, y; /* координаты точки */ char *s; /* метка точки */ } Point; Point p; Point *pptr; short *iptr; struct _Curve { Point points[25]; /* вершины ломанной */ int color; /* цвет линии */ } aLine[10], *linePtr = & aLine[0]; ... pptr = &p; /* указатель на структуру p */ p.x = 1; p.y = 2; p.s = "Grue"; linePtr->points[2].x = 54; aLine[5].points[0].y = 17; В ы р а ж е н и е значение ---------+------------+------------+-----------+----------- p.x | pptr->x | (*pptr).x | (&p)->x | 1 ---------+------------+------------+-----------+----------- &p->x | ошибка -----------+----------------+------------------+----------- iptr= &p.x | iptr= &pptr->x | iptr= &(pptr->x) | адрес поля -----------+----------------+--------+---------+----------- *pptr->s | *(pptr->s) | *p.s | p.s[0] | 'G' -----------+----------------+--------+---------+----------- pptr->s[1] | (&p)->s[1] | p.s[1] | 'r' -----------+----------------+------------------+----------- &p->s[1] | ошибка -----------+----------------+------------------+----------- (*pptr).s | pptr->s | p.s | "Grue" -----------+----------------+------------------+----------- *pptr.s | ошибка -----------------------------------------------+----------- А. Богатырев, 1992-95 - 174 - Си в UNIX Вообще (&p)->field = p.field pptr->field = (*pptr).field Объединения - это агрегаты данных, которые могут хранить в себе значения данных разных типов на одном и том же месте. struct a{ int x, y; char *s; } A; union b{ int i; char *s; struct a aa; } B; Структура: ________________________ A: | A.x int | Три поля ------------------------ расположены подряд. | A.y int | Получается как бы ------------------------ "карточка" с графами. | A.s char * | ------------------------ А у объединений поля расположены "параллельно", на одном месте в памяти. _______________________________________________________ B: | B.i int | B.s char * | B.aa : B.aa.x int | -----------| | struct a : B.aa.y int | ---------------| : B.aa.s char * | |___________________________| Это как бы "ящик" в который можно поместить значение любого типа из перечисленных, но не ВСЕ ВМЕСТЕ ("и то и это", как у структур), а ПО ОЧЕРЕДИ ("или/или"). Размер его достаточно велик, чтоб вместить самый большой из перечисленных типов данных. Мы можем занести в union значение и интерпретировать его как другой тип данных - это иногда используется в машинно-зависимых программах. Вот пример, выясняющий поря- док байтов в short числах: union lb { char s[2]; short i; } x; unsigned hi, lo; x.i = (02 << 8) | 01; hi = x.s[1]; lo = x.s[0]; printf( "%d %d\n", hi, lo); или так: #include <stdio.h> union { int i; unsigned char s[sizeof(int)]; } u; void main(){ unsigned char *p; int n; u.i = 0x12345678; for(n=0, p=u.s; n < sizeof(int); n++, p++){ printf("%02X ", *p); } putchar('\n'); } А. Богатырев, 1992-95 - 175 - Си в UNIX или порядок слов в long числах: union xx { long l; struct ab { short a; /* low word */ short b; /* high word */ } ab; } c; main(){ /* На IBM PC 80386 печатает 00020001 */ c.ab.a = 1; c.ab.b = 2; printf("%08lx\n", c.l ); } 5.1. Найдите ошибки в описании структурного шаблона: structure { int arr[12], char string, int *sum } 5.2. Разработайте структурный шаблон, который содержал бы название месяца, трехбук- венную аббревиатуру месяца, количество дней в месяце и номер месяца. Инициализируйте его для невисокосного года. struct month { char name[10]; /* или char *name; */ char abbrev[4]; /* или char *abbrev; */ int days; int num; }; struct month months[12] = { /* индекс */ {"Январь" , "Янв", 31, 1 }, /* 0 */ {"Февраль", "Фев", 28, 2 }, /* 1 */ ... {"Декабрь", "Дек", 31, 12}, /* 11 */ }, *mptr = & months[0]; /* или *mptr = months */ main(){ struct month *mptr; printf( "%s\n", mptr[1].name ); printf( "%s %d\n", mptr->name, mptr->num ); } Напишите функцию, сохраняющую массив months в файл; функцию, считывающую его из файла. Используйте fprintf и fscanf. В чем будет разница в функции чтения, когда поле name описано как char name[10] и как char *name? Ответ: во втором случае для сохранения прочитанной строки надо заказывать память динамически при помощи malloc() и сохранять в ней строку при помощи strcpy(), т.к. память для хранения самой строки в структуре не зарезервирована (а только для указа- теля на нее). Найдите ошибку в операторах функции main(). Почему печатается не "Февраль", а какой-то мусор? Указание: куда указывает указатель mptr, описанный в main()? Ответ: в "неизвестно куда" - это локальная переменная (причем не получившая начального значе- ния - в ней содержится мусор), а не то же самое, что указатель mptr, описанный выше! Уберите описание mptr из main. А. Богатырев, 1992-95 - 176 - Си в UNIX Заметим, что для распечатки всех или нескольких полей структуры следует ЯВНО перечислить в printf() все нужные поля и указать форматы, соответствующие типам этих полей. Не существует формата или стандартной