Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Программирование Cи / Богатырев_Язык Си в системе Unix

.pdf
Скачиваний:
86
Добавлен:
25.04.2015
Размер:
1.19 Mб
Скачать

А. Богатырёв, 1992-96

- 111 -

Си в UNIX™

Разберитесь с принципом формирования массива UU.

3.10. В современных UNIX-ах с поддержкой различных языков таблица ctype загружается из некоторых системных файлов - для каждого языка своя. Для какого языка - выбирается по содержимому переменной окружения LANG. Если переменная не задана - используется значение "C", английский язык. Загрузка таблиц должна происходить явно, вызовом

...

#include <locale.h>

...

main(){

setlocale(LC_ALL, "");

...

все остальное

...

}

3.11. Вернемся к нашей любимой проблеме со знаковым битом у типа char.

#include <stdio.h> #include <locale.h> #include <ctype.h>

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

char *string = "абвгдежзиклмноп";

setlocale(LC_ALL, "");

for(;c = *string;string++){ #ifdef DEBUG

printf("%c %d %d\n", *string, *string, c);

#endif

if(isprint(c)) printf("%c - печатный символ\n", c);

}

return 0;

}

Эта программа неожиданно печатает

% a.out

в - печатный символ

з- печатный символ

Ивсе. В чем дело???

Рассмотрим к примеру символ 'г'. Его код '\307'. В операторе

c = *string;

 

Символ c получает значение

-57 (десятичное), которое ОТРИЦАТЕЛЬНО. В системном файле

/usr/include/ctype.h макрос isprint определен так:

#define isprint(c)

((_ctype + 1)[c] & (_P|_U|_L|_N|_B))

И значение c используется в нашем случае как отрицательный индекс в массиве, ибо индекс приводится к типу int (signed). Откуда теперь извлекается значение флагов - нам неизвестно; можно только с уверенностью сказать, что НЕ из массива _ctype.

Проблему решает либо использование

isprint(c & 0xFF)

либо

isprint((unsigned char) c)

либо объявление в нашем примере

unsigned char c;

В первом случае мы явно приводим signed к unsigned битовой операцией, обнуляя лишние биты. Во втором и третьем - unsigned char расширяется в unsigned int, который останется положительным. Вероятно, второй путь предпочтительнее.

А. Богатырёв, 1992-96

- 112 -

Си в UNIX™

3.12. Итак, снова напомним, что русские буквы char, а не unsigned char дают отрицательные индексы в массиве.

char c = 'г'; int x[256];

...x[c]...

/* индекс < 0 */

...x['г']...

 

Поэтому байтовые индексы должны быть либо unsigned char, либо & 0xFF. Как в следующем примере:

/* Программа преобразования символов в файле: транслитерация tr abcd prst заменяет строки xxxxdbcaxxxx -> xxxxtrspxxxx

По мотивам книги М.Дансмура и Г.Дейвиса.

*/

#include <stdio.h>

#define ASCII 256 /* число букв в алфавите ASCII */ /* BUFSIZ определено в stdio.h */

char mt[ ASCII ]; /* таблица перекодировки */

/* начальная разметка таблицы */ void mtinit(){

register int i;

for( i=0; i < ASCII; i++ ) mt[i] = (char) i;

}

int main(int argc, char *argv[])

{

register char *tin, *tout; /* unsigned char */ char buffer[ BUFSIZ ];

if( argc != 3 ){

fprintf( stderr, "Вызов: %s что наЧто\n", argv[0] ); return(1);

}

tin = argv[1]; tout = argv[2];

if( strlen(tin) != strlen(tout)){

fprintf( stderr, "строки разной длины\n" ); return(2);

}

mtinit();

do{

mt[ (*tin++) & 0xFF ] = *tout++; /* *tin - имеет тип char.

* & 0xFF подавляет расширение знака */

} while( *tin );

tout = mt;

while( fgets( buffer, BUFSIZ, stdin ) != NULL ){ for( tin = buffer; *tin; tin++ )

*tin = tout[ *tin & 0xFF ]; fputs( buffer, stdout );

}

return(0);

}

3.13.

А. Богатырёв, 1992-96 - 113 - Си в UNIX™

int main(int ac, char *av[]){ char c = 'г';

if('a' <= c && c < 256)

printf("Это одна буква.\n"); return 0;

}

Увы, эта программа не печатает НИЧЕГО. Просто потому, что signed char в сравнении (в операторе if) приводится к типу int. А как целое число - русская буква отрицательна. Снова решением является либо использование везде (c & 0xFF), либо объявление unsigned char c. В частности, этот пример показывает, что НЕЛЬЗЯ просто так сравнивать две переменные типа char. Нужно принимать предохранительные меры по подавлению расширения знака:

if((ch1 & 0xFF) < (ch2 & 0xFF))...;

Для unsigned char такой проблемы не будет.

3.14. Почему неверно:

#include <stdio.h> main(){

char c;

while((c = getchar()) != EOF) putchar(c);

}

Потому что c описано как char, в то время как EOF - значение типа int равное (-1).

Русская буква "Большой твердый знак" в кодировке КОИ-8 имеет код '\377' (0xFF). Если мы подадим на вход этой программе эту букву, то в сравнении signed char со значением знакового целого EOF, c будет приведено тоже к знаковому целому - расширением знака. 0xFF превратится в (-1), что означает, что поступил символ EOF. Сюрприз!!! Посему данная программа будет делать вид, что в любом файле с большим русским твердым знаком после этого знака (и включая его) дальше ничего нет. Что есть досадное заблуждение.

Решением служит ПРАВИЛЬНОЕ объявление int c.

3.15. Изучите поведение программы

#define TYPE char

void f(TYPE c){

if(c == 'й') printf("Это буква й\n");

printf("c=%c c=\\%03o c=%03d c=0x%0X\n", c, c, c, c);

}

int main(){

f('г'); f('й'); f('z'); f('Z'); return 0;

}

когда TYPE определено как char, unsigned char, int. Объясните поведение. Выдачи в этих трех случаях таковы (int == 32 бита):

c=г c=\37777777707 c=-57 c=0xFFFFFFC7 Это буква й

c=й c=\37777777712 c=-54 c=0xFFFFFFCA c=z c=\172 c=122 c=0x7A

c=Z c=\132 c=090 c=0x5A

c=г c=\307 c=199 c=0xC7 c=й c=\312 c=202 c=0xCA c=z c=\172 c=122 c=0x7A c=Z c=\132 c=090 c=0x5A

и снова как 1 случай.

Рассмотрите альтернативу

if(c == (unsigned char) 'й') printf("Это буква й\n");

А. Богатырёв, 1992-96

- 114 -

Си в UNIX™

где предполагается, что знак у русских букв и у c НЕ расширяется. В данном случае фраза 'Это буква й' не печатается ни с типом char, ни с типом int, поскольку в сравнении c приводится к типу signed int расширением знакового бита (который равен 1). Слева получается отрицательное число!

В таких случаях вновь следует писать

if((unsigned char)c == (unsigned char)'й') printf("Это буква й\n");

3.16. Обычно возникают проблемы при написании функций с переменным числом аргументов. В языке Си эта проблема решается использованием макросов va_args, не зависящих от соглашений о вызовах функций на данной машине, и использующих эти макросы специальных функций. Есть два стиля оформления таких программ: с использованием <varargs.h> и <stdarg.h>. Первый был продемонстрирован в первой главе на примере функции poly(). Для иллюстрации второго приведем пример функции трассировки, записывающей собщение в файл:

#include <stdio.h> #include <stdarg.h>

void trace(char *fmt, ...) { va_list args;

static FILE *fp = NULL;

if(fp == NULL){

if((fp = fopen("TRACE", "w")) == NULL) return;

}

va_start(args, fmt);

/* второй аргумент: арг-т после которого * в заголовке функции идет ... */

vfprintf(fp, fmt, args); /* библиотечная ф-ция */

fflush(fp);

/* вытолкнуть сообщение в файл */

va_end(args);

 

}

 

main(){ trace( "%s\n", "Go home."); trace( "%d %d\n", 12, 34);

}

Символ `...' (троеточие) в заголовке функции обозначает переменный (возможно пустой) список аргументов. Он должен быть самым последним, следуя за всеми обязательными аргументами функции.

Макрос va_arg(args,type), извлекающий из переменного списка аргументов `...' очередное значение типа type, одинаков в обоех моделях. Функция vfprintf может быть написана через функцию vsprintf (в действительности обе функции - стандартные):

int vfprintf(FILE *fp, const char *fmt, va_list args){ /*static*/ char buffer[1024]; int res;

res = vsprintf(buffer, fmt, args); fputs(buffer, fp); return res;

}

Функция vsprintf(str,fmt,args); аналогична функции sprintf(str,fmt,...) - записывает преобразованную по формату строку в байтовый массив str, но используется в контексте, подобном приведенному. В конец сформированной строки sprintf записывает '\0'.

3.17.Напишите функцию printf, понимающую форматы %c (буква), %d (целое), %o (восьмеричное), %x (шестнадцатеричное), %b (двоичное), %r (римское), %s (строка), %ld (длинное целое). Ответ смотри в приложении.

3.18.Для того, чтобы один и тот же исходный текст программы транслировался на разных машинах (в разных системах), приходится выделять в программе системно-зависимые части. Такие части должны поразному выглядеть на разных машинах, поэтому их оформляют в виде так называемых "условно компилируемых" частей:

#ifdef XX

... вариант1

#else

... вариант2

#endif

Эта директива препроцессора ведет себя следующим образом: если макрос с именем XX был определен

#define XX

А. Богатырёв, 1992-96

- 115 -

Си в UNIX™

то в программу подставляется вариант1, если же нет - вариант2. Оператор #else не обязателен - при его отсутствии вариант2 пуст. Существует также оператор #ifndef, который подставляет вариант1 если макрос XX не определен. Есть еще и оператор #elif - else if:

#ifdef макро1

...

#elif макро2

...

#else

...

#endif

Определить макрос можно не только при помощи #define, но и при помощи ключа компилятора, так

cc -DXX file.c ...

соответствует включению в начало файла file.c директивы

#define XX

А для программы

main(){ #ifdef XX

printf( "XX = %d\n", XX);

#else

printf( "XX undefined\n");

#endif

}

ключ

cc -D"XX=2" file.c ...

эквивалентен заданию директивы

#define XX 2

Что будет, если совсем не задать ключ -D в данном примере?

Этот прием используется в частности в тех случаях, когда какие-то стандартные типы или функции в данной системе носят другие названия:

cc -Dvoid=int ...

cc-Dstrchr=index ...

Внекоторых системах компилятор автоматически определяет специальные макросы: так компиляторы в UNIX неявно подставляют один из ключей (или несколько сразу):

-DM_UNIX

-DM_XENIX -Dunix -DM_SYSV -D__SVR4 -DUSG

... бывают и другие

Это позволяет программе "узнать", что ее компилируют для системы UNIX. Более подробно про это написано в документации по команде cc.

3.19. Оператор #ifdef применяется в include-файлах, чтобы исключить повторное включение одного и того же файла. Пусть файлы aa.h и bb.h содержат

aa.h

bb.h

#include "cc.h"

#include "cc.h"

typedef unsigned long ulong;

typedef int cnt_t;

А файлы cc.h и 00.c содержат

 

cc.h

00.c

...

#include "aa.h"

struct II { int x, y; };

#include "bb.h"

...

main(){ ... }

В этом случае текст файла cc.h будет вставлен в 00.c дважды: из aa.h и из bb.h. При компиляции 00.c компилятор сообщит "Переопределение структуры II". Чтобы include-файл не подставлялся еще раз, если он уже однажды был включен, придуман следующий прием - следует оформлять файлы включений так:

А. Богатырёв, 1992-96

- 116 -

Си в UNIX™

/* файл

cc.h */

 

#ifndef _CC_H

 

 

# define _CC_H

/* определяется при первом включении */

 

 

...

 

 

 

struct II { int x, y; };

 

 

...

 

 

#endif /* _CC_H */

 

Второе и последующие включения такого файла будут подставлять пустое место,

что и требуется. Для

файла <sys/types.h> было бы использовано макроопределение _SYS_TYPES_H.

 

3.20. Любой макрос можно отменить, написав директиву

 

#undef имяМакро

 

Пример:

 

 

 

#include <stdio.h>

 

#undef M_UNIX

 

 

#undef M_SYSV

 

 

main() {

 

 

 

 

putchar('!');

 

#undef

putchar

 

 

#define putchar(c) printf( "Буква '%c'\n", c);

 

putchar('?');

#if defined(M_UNIX) || defined(M_SYSV) /* или просто #if M_UNIX */

printf("Это UNIX\n");

#else

printf("Это не UNIX\n"); #endif /* UNIX */

}

Обычно #undef используется именно для переопределения макроса, как putchar в этом примере (дело в том, что putchar - это макрос из <stdio.h>).

Директива #if, использованная нами, является расширением оператора #ifdef и подставляет текст если выполнено указанное условие:

#if defined(MACRO)

/* равно #ifdef(MACRO) */

 

#if !defined(MACRO)

/* равно #ifndef(MACRO) */

 

#if VALUE > 15

/* если целая константа

 

 

#define VALUE 25

 

 

больше 15 (==, !=, <=, ...) */

#if COND1 || COND2

/* если верно любое из условий

*/

#if COND1 && COND2

/* если верны оба условия

*/

Директива #if допускает использование в качестве аргумента довольно сложных выражений, вроде

#if !defined(M1) && (defined(M2) || defined(M3))

3.21. Условная компиляция может использоваться для трассировки программ:

#ifdef DEBUG

 

# define DEBUGF(body)

\

{

\

body;

\

}

 

#else

 

# define DEBUGF(body)

 

#endif

 

int f(int x){ return x*x; } int main(int ac, char *av[]){

int x = 21;

DEBUGF(x = f(x); printf("%s equals to %d\n", "x", x)); printf("x=%d\n", x);

}

При компиляции

А. Богатырёв, 1992-96

- 117 -

Си в UNIX™

cc-DDEBUG file.c

ввыходном потоке программы будет присутствовать отладочная выдача. При компиляции без -DDEBUG этой выдачи не будет.

3.22. В языке C++ (развитие языка Си) слова class, delete, friend, new, operator, overload, template, public, private, protected, this, virtual являются зарезервированными (ключевыми). Это может вызвать небольшую проблему при переносе текста программы на Си в систему программирования C++, например:

#include <termio.h>

...

 

 

int fd_tty = 2;

/* stderr */

struct termio old, new;

ioctl

(fd_tty, TCGETA, &old);

new =

old;

 

new.c_lflag |= ECHO | ICANON; ioctl (fd_tty, TCSETAW, &new);

...

Строки, содержащие имя переменной (или функции) new, окажутся неправильными в C++. Проще всего эта проблема решается переименованием переменной (или функции). Чтобы не производить правки во всем тексте, достаточно переопределить имя при помощи директивы define:

#define new new_modes

... старый текст ...

#undef new

При переносе программы на Си в C++ следует также учесть, что в C++ для каждой функции должен быть задан прототип, прежде чем эта функция будет использована (Си позволяет опускать прототипы для многих функций, особенно возвращающих значения типов int или void).

А. Богатырёв, 1992-96

- 118 -

Си в UNIX™

4. Работа с файлами.

Файлы представляют собой области памяти на внешнем носителе (как правило магнитном диске), предназначенные для:

хранения данных, превосходящих по объему память компьютера (меньше, разумеется, тоже можно);

долговременного хранения информации (она сохраняется при выключении машины).

ВUNIX и в MS DOS файлы не имеют предопределенной структуры и представляют собой просто линейные

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

они могут изменять свой размер;

обращение к элементам этих массивов производится не при помощи операции индексации [], а при помощи специальных системных вызовов и функций;

доступ к элементам файла происходит в так называемой "позиции чтения/записи", которая автоматически продвигается при операциях чтения/записи, т.е. файл просматривается последовательно. Есть, правда, функции для произвольного изменения этой позиции.

Файлы имеют имена и организованы в иерархическую древовидную структуру из каталогов и простых файлов. Об этом и о системе именования файлов прочитайте в документации по UNIX.

4.1. Для работы с каким-либо файлом наша программа должна открыть этот файл - установить связь между именем файла и некоторой переменной в программе. При открытии файла в ядре операционной системы выделяется "связующая" структура file "открытый файл", содержащая:

f_offset:

указатель позиции чтения/записи, который в дальнейшем мы будем обозначать как RWptr. Это long- число, равное расстоянию в байтах от начала файла до позиции чтения/записи;

f_flag: режимы открытия файла: чтение, запись, чтение и запись, некоторые дополнительные флаги;

f_inode:

расположение файла на диске (в UNIX - в виде ссылки на I-узел файла†);

икое-что еще.

Укаждого процесса имеется таблица открытых им файлов - это массив ссылок на упомянутые "связующие" структуры‡. При открытии файла в этой таблице ищется свободная ячейка, в нее заносится ссылка на структуру "открытый файл" в ядре, и ИНДЕКС этой ячейки выдается в вашу программу в виде целого числа - так называемого "дескриптора файла".

При закрытии файла связная структура в ядре уничтожается, ячейка в таблице считается свободной, т.е. связь программы и файла разрывается.

I-узел (I-node, индексный узел) - своеобразный "паспорт", который есть у каждого файла (в том числе и каталога). В нем содержатся:

- длина файла

long

di_size;

- номер владельца файла

int

di_uid;

- коды доступа и тип файла

ushort di_mode;

- время создания и последней модификации

time_t di_ctime, di_mtime;

- начало таблицы блоков файла

char

di_addr[...];

- количество имен файла

short

di_nlink;

и.т.п.

 

 

Содержимое некоторых полей этого паспорта можно узнать вызовом stat(). Все I-узлы собраны в единую область в начале файловой системы - так называемый I-файл. Все I-узлы пронумерованы, начиная с номера 1. Корневой каталог (файл с именем "/") как правило имеет I-узел номер 2.

‡ У каждого процесса в UNIX также есть свой "паспорт". Часть этого паспорта находится в таблице процессов в ядре ОС, а часть - "приклеена" к самому процессу, однако не доступна из программы непосредственно. Эта вторая часть паспорта носит название "u-area" или структура user. В нее, в частности, входят таблица открытых процессом файлов

struct file *u_ofile[NOFILE]; ссылка на I-узел текущего каталога

struct inode *u_cdir;

а также ссылка на часть паспорта в таблице процессов struct proc *u_procp;

А. Богатырёв, 1992-96

- 119 -

Си в UNIX™

Дескрипторы являются локальными для каждой программы. Т.е. если две программы открыли один и тот же файл - дескрипторы этого файла в каждой из них не обязательно совпадут (хотя и могут). Обратно: одинаковые дескрипторы (номера) в разных программах не обязательно обозначают один и тот же файл. Следует учесть и еще одну вещь: несколько или один процессов могут открыть один и тот же файл одновременно несколько раз. При этом будет создано несколько "связующих" структур (по одной для каждого открытия); каждая из них будет иметь СВОЙ указатель чтения/записи. Возможна и ситуация, когда несколько дескрипторов ссылаются к одной структуре - смотри ниже описание вызова dup2.

fd

u_ofile[]

struct file

 

 

 

0

##

 

 

-------------

 

 

 

 

1---

##----------------

 

>| f_flag

 

|

 

 

2

##

 

 

| f_count=3

|

 

 

3---

##----------------

 

>| f_inode

---------

 

*

 

...

## *--------------

 

>| f_offset

|

|

 

процесс1 |

 

 

------!------

 

 

|

 

 

|

 

 

!

 

 

V

 

0

## |

struct file

 

!

struct inode

 

1

## |

-------------

 

!

-------------

 

 

 

2---

##-*

| f_flag

|

!

| i_count=2 |

 

3---

##---

>| f_count=1 |

!

| i_addr[]----

*

...

##

| f_inode----------

 

!--

>|

...

| | адреса

процесс2

| f_offset

|

!

-------------

 

 

| блоков

 

 

-------!-----

 

*=========*

 

| файла

 

 

!

 

 

 

!

 

V

 

0

!

указатели R/W

!

i_size-1

 

@@@@@@@@@@@!@@@@@@@@@@@@@@@@@@@@@!@@@@@@

 

 

 

 

файл на диске

 

 

 

/* открыть файл */

int fd = open(char имя_файла[], int как_открыть);

... /* какие-то операции с файлом */ close(fd); /* закрыть */

Параметр как_открыть:

#include <fcntl.h>

O_RDONLY - только для чтения.

O_WRONLY - только для записи. O_RDWR - для чтения и записи.

O_APPEND - иногда используется вместе с открытием для записи, "добавление" в файл:

O_WRONLY|O_APPEND, O_RDWR|O_APPEND

Если файл еще не существовал, то его нельзя открыть: open вернет значение (-1), сигнализирующее об ошибке. В этом случае файл надо создать:

int fd = creat(char имя_файла[], int коды_доступа);

Дескриптор fd будет открыт для записи в этот новый пустой файл. Если же файл уже существовал, creat опустошает его, т.е. уничтожает его прежнее содержимое и делает его длину равной 0L байт. Коды_доступа задают права пользователей на доступ к файлу. Это число задает битовую шкалу из 9и бит, соответствующих строке

биты: 876 543 210 rwx rwx rwx

r - можно читать файл

w - можно записывать в файл

x - можно выполнять программу из этого файла

Первая группа - эта права владельца файла, вторая - членов его группы, третяя - всех прочих. Эти коды для владельца файла имеют еще и мнемонические имена (используемые в вызове stat):

#include <sys/stat.h>

/* Там определено: */

#define S_IREAD

0400

#define S_IWRITE

0200

#define S_IEXEC

0100

Подробности - в руководствах по системе UNIX. Отметим в частности, что open() может вернуть код ошибки fd < 0 не только в случае, когда файл не существует (errno==ENOENT), но и в случае, когда вам не разрешен соответствующий доступ к этому файлу (errno==EACCES; про переменную кода ошибки errno см. в главе "Взаимодействие с UNIX").

А. Богатырёв, 1992-96 - 120 - Си в UNIX™

Вызов creat - это просто разновидность вызова open в форме

fd = open( имя_файла,

O_WRONLY|O_TRUNC|O_CREAT, коды_доступа);

O_TRUNC

означает, что если файл уже существует, то он должен быть опустошен при открытии. Коды доступа и владелец не изменяются.

O_CREAT

означает, что файл должен быть создан, если его не было (без этого флага файл не создастся, а open вернет fd < 0). Этот флаг требует задания третьего аргумента коды_доступа†. Если файл уже существует - этот флаг не имеет никакого эффекта, но зато вступает в действие O_TRUNC.

Существует также флаг

O_EXCL

который может использоваться совместно с O_CREAT. Он делает следующее: если файл уже существует, open вернет код ошибки (errno==EEXIST). Если файл не существовал - срабатывает O_CREAT и файл создается. Это позволяет предохранить уже существующие файлы от уничтожения.

Файл удаляется при помощи

int unlink(char имя_файла[]);

У каждой программы по умолчанию открыты три первых дескриптора, обычно связанные

0

- с клавиатурой

(для чтения)

1

- с

дисплеем

(выдача

результатов)

2

- с

дисплеем

(выдача

сообщений об ошибках)

Если при вызове close(fd) дескриптор fd не соответствует открытому файлу (не был открыт) - ничего не происходит.

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

4.2. Напишите программу, которая копирует содержимое одного файла в другой (новый) файл. При этом используйте системные вызовы чтения и записи read и write. Эти сисвызовы пересылают массивы байт из памяти в файл и наоборот. Но любую переменную можно рассматривать как массив байт, если забыть о структуре данных в переменной!

Читайте и записывайте файлы большими кусками, кратными 512 байтам. Это уменьшит число обращений к диску. Схема:

char buffer[512]; int n; int fd_inp, fd_outp;

...

while((n = read (fd_inp, buffer, sizeof buffer)) > 0) write(fd_outp, buffer, n);

Приведем несколько примеров использования write:

char c = 'a';

int i = 13, j = 15; char s[20] = "foobar";

† Заметим, что на самом деле коды доступа у нового файла будут равны di_mode = (коды_доступа & ~u_cmask) | IFREG;

(для каталога вместо IFREG будет IFDIR), где маска u_cmask задается системным вызовом umask(u_cmask);

(вызов выдает прежнее значение маски) и в дальнейшем наследуется всеми потомками данного процесса (она хранится в u-area процесса). Эта маска позволяет запретить доступ к определенным операциям для всех создаваемых нами файлов, несмотря на явно заданные коды доступа, например

umask(0077); /* ???------ */

делает значащими только первые 3 бита кодов доступа (для владельца файла). Остальные биты будут равны нулю.

Все это относится и к созданию каталогов вызовом mkdir.