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 не буферизован исходно, поэтому выдаваемые в него сообщения печатаются немедленно.

Мораль: надо быть очень осторожным при смешанном использовании буферизованного и небуферизованного обмена.

Некоторые каналы буферизуются так, что буфер выталкивается не только при заполнении, но и при поступлении символа '\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" );
            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:
                 $ 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     /* ошибка чтения/записи    */
    extern char  *ttyname();
    char *tname = ttyname(fd);
Она выдаст строку, подобную "/dev/tty01". Если fd не связан с терминалом - она вернет
    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);    }
    /* Открытие файла */
    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); /* Собственно системный вызов */
    }
    /* Прочесть очередной буфер из файла */
    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);
    }
    /* Вытолкнуть очередной буфер в файл */
    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;
    }
    /* "Перемотать" файл в начало */
    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*/
    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);
    }

* Заметим еще, что если дескриптор fd связан с терминалом, то можно узнать полное имя этого устройства вызовом стандартной функции

** Ссылка на управляющий терминал процесса хранится в u-area каждого процесса: u_ttyp, u_ttyd, поэтому ядро в состоянии определить какой настоящий терминал следует открыть для вас. Если разные процессы открывают /dev/tty, они могут открыть в итоге разные терминалы, т.е. одно имя приводит к разным устройствам! Смотри главу про UNIX.

© Copyright А. Богатырев, 1992-95
Си в UNIX

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