
Программирование Cи / Богатырев_Язык Си в системе Unix
.pdfА. Богатырёв, 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.