efghijklmn...
D
#00 после
S
...abcdefEhijklmn...
D
#01 перед
S
...abcdefEhijklmn...
D
#01 после
S
...abcdeDEhijklmn...
D
#02 перед
S
...abcdeDEhijklmn...
D
#02 после
S
...abcdCDEhijklmn...
D
#03 перед
S
...abcdCDEhijklmn...
D
#03 после
S
...abcBCDEhijklmn...
D
#04 перед
S
...abcBCDEhijklmn...
D
#04 после
S
...abABCDEhijklmn...
D
Теперь bcopy() - удобная функция для копирования и сдвига массивов, в частности мас-
сивов указателей. Пусть у нас есть массив строк (выделенных malloc-ом):
char *lines[NLINES];
Тогда циклическая перестановка строк выглядит так:
А. Богатырев, 1992-95 - 111 - Си в UNIX
void scrollUp(){
char *save = lines[0];
bcopy((char *) lines+1, /* from */
(char *) lines, /* to */
sizeof(char *) * (NLINES-1));
lines[NLINES-1] = save;
}
void scrollDown(){
char *save = lines[NLINES-1];
bcopy((char *) &lines[0], /* from */
(char *) &lines[1], /* to */
sizeof(char *) * (NLINES-1));
lines[0] = save;
}
Возможно, что написание по аналогии функции для копирования массивов элементов типа
(void *) - обобщенных указателей - может оказаться еще понятнее и эффективнее. Такая
функция - memmove - стандартно существует в UNIX SVR4. Заметьте, что порядок аргу-
ментов в ней обратный по отношению к bcopy. Следует отметить, что в SVR4 все функции
mem... имеют указатели типа (void *) и счетчик типа size_t - тип для количества байт
(вместо unsigned long); в частности длина файла имеет именно этот тип (смотри систем-
ные вызовы lseek и stat).
#include <sys/types.h>
void memmove(void *Dst, const void *Src,
register size_t n){
register caddr_t src = (caddr_t) Src,
dst = (caddr_t) Dst;
if(dst==src || n <= 0) return;
if(src < dst && dst < src + n) {
dst += n-1;
src += n-1;
while(--n >= 0)
*dst-- = *src--;
}else memcpy(dst, src, n);
}
caddr_t - это тип для указателей на БАЙТ, фактически это (unsigned char *). Зачем
вообще понадобилось использовать caddr_t? Затем, что для
void *pointer;
int n;
значение
pointer + n
не определено и невычислимо, ибо sizeof(void) не имеет смысла - это не 0, а просто
ошибка, диагностируемая компилятором!
2.59. Еще об опечатках: вот что бывает, когда вместо знака `=' печатается `-' (на
клавиатуре они находятся рядом...).
А. Богатырев, 1992-95 - 112 - Си в UNIX
#include <stdio.h>
#include <strings.h>
char *strdup(const char *s){
extern void *malloc();
return strcpy((char *)malloc(strlen(s)+1), s);
}
char *ptr;
void main(int ac, char *av[]){
ptr - strdup("hello"); /* подразумевалось ptr = ... */
*ptr = 'H';
printf("%s\n", ptr);
free(ptr);
exit(0);
}
Дело в том, что запись (а часто и чтение) по *pointer, где pointer==NULL, приводит к
аварийному прекращению программы. В нашей программе ptr осталось равным NULL - указа-
телем в никуда. В операционной системе UNIX на машинах с аппаратной защитой памяти,
страница памяти, содержащая адрес NULL (0) бывает закрыта на запись, поэтому любое
обращение по записи в эту страницу вызывает прерывание от диспетчера памяти и аварий-
ное прекращение процесса. Система сама помогает ловить ваши ошибки (но уже во время
выполнения программы). Это ОЧЕНЬ частая ошибка - запись по адресу NULL. MS DOS в
таких случаях предпочитает просто зависнуть, и вы бываете вынуждены играть аккорд из
трех клавиш - Ctrl/Alt/Del, так и не поняв в чем дело.
2.60. Раз уж речь зашла о функции strdup (кстати, это стандартная функция), приведем
еще одну функцию для сохранения строк.
char *savefromto(register char *from, char *upto)
{
char *ptr, *s;
if((ptr = (char *) malloc(upto - from + 1)) == NULL)
return NULL;
for(s = ptr; from < upto; from++)
*s++ = *from;
*s = '\0';
return ptr;
}
Сам символ (*upto) не сохраняется, а заменяется на '\0'.
2.61. Упрощенный аналог функции printf.
А. Богатырев, 1992-95 - 113 - Си в UNIX
/*
* Машинно - независимый printf() (упрощенный вариант).
* printf - Форматный Вывод.
*/
#include <stdio.h>
#include <ctype.h>
#include <varargs.h>
#include <errno.h>
#include <string.h>
extern int errno; /* код системной ошибки, формат %m */
/* чтение значения числа */
#define GETN(n,fmt) \
n = 0; \
while(isdigit(*fmt)){ \
n = n*10 + (*fmt - '0'); \
fmt++; \
}
void myprintf(fmt, va_alist)
register char *fmt; va_dcl
{
va_list ap;
char c, *s; int i;
int width, /* минимальная ширина поля */
prec, /* макс. длина данного */
sign, /* выравнивание: 1 - вправо, -1 - влево */
zero, /* ширина поля начинается с 0 */
glong; /* требуется длинное целое */
va_start(ap);
for(;;){
while((c = *fmt++) != '%'){
if( c == '\0' ) goto out;
putchar(c);
}
sign = 1; zero = 0; glong = 0;
if(*fmt == '-'){ sign = (-1); fmt++; }
if(*fmt == '0'){ zero = 1; fmt++; }
if(*fmt == '*'){
width = va_arg(ap, int);
if(width < 0){ width = -width; sign = -sign; }
fmt++;
}else{
GETN(width, fmt);
}
width *= sign;
if(*fmt == '.'){
if(*++fmt == '*'){
prec = va_arg(ap, int); fmt++;
}else{
GETN(prec, fmt);
}
}else prec = (-1); /* произвольно */
if( *fmt == 'l' ){
glong = 1; fmt++;
}
А. Богатырев, 1992-95 - 114 - Си в UNIX
switch(c = *fmt++){
case 'c':
putchar(va_arg(ap, int)); break;
case 's':
prStr(width, prec, va_arg(ap, char *)); break;
case 'm':
prStr(width, prec, strerror(errno)); break;
/* strerror преобразует код ошибки в строку-расшифровку */
case 'u':
prUnsigned(width,
glong ? va_arg(ap, unsigned long) :
(unsigned long) va_arg(ap, unsigned int),
10 /* base */, zero); break;
case 'd':
prInteger(width,
glong ? va_arg(ap, long) : (long) va_arg(ap, int),
10 /* base */, zero); break;
case 'o':
prUnsigned(width,
glong ? va_arg(ap, unsigned long) :
(unsigned long) va_arg(ap, unsigned int),
8 /* base */, zero); break;
case 'x':
prUnsigned(width,
glong ? va_arg(ap, unsigned long) :
(unsigned long) va_arg(ap, unsigned int),
16 /* base */, zero); break;
case 'X':
prUnsigned(width,
glong ? va_arg(ap, unsigned long) :
(unsigned long) va_arg(ap, unsigned int),
-16 /* base */, zero); break;
case 'b':
prUnsigned(width,
glong ? va_arg(ap, unsigned long) :
(unsigned long) va_arg(ap, unsigned int),
2 /* base */, zero); break;
case 'a': /* address */
prUnsigned(width,
(long) (char *) va_arg(ap, char *),
16 /* base */, zero); break;
case 'A': /* address */
prUnsigned(width,
(long) (char *) va_arg(ap, char *),
-16 /* base */, zero); break;
case 'r':
prRoman(width, prec, va_arg(ap, int)); break;
case '%':
putchar('%'); break;
default:
putchar(c); break;
}
}
out:
va_end(ap);
}
А. Богатырев, 1992-95 - 115 - Си в UNIX
/* --------------------------------------------------------- */
int strnlen(s, maxlen) char *s;
{
register n;
for( n=0; *s && n < maxlen; n++, s++ );
return n;
}
/* Печать строки */
static prStr(width, prec, s) char *s;
{
int ln; /* сколько символов выводить */
int toLeft = 0; /* к какому краю прижимать */
if(s == NULL){ pr( "(NULL)", 6); return; }
/* Измерить длину и обрубить длинную строку.
* Дело в том, что строка может не иметь \0 на конце, тогда
* strlen(s) может привести к обращению в запрещенные адреса */
ln = (prec > 0 ? strnlen(s, prec) : strlen(s));
/* ширина поля */
if( ! width ) width = (prec > 0 ? prec : ln);
if( width < 0){ width = -width; toLeft = 1; }
if( width > ln){
/* дополнить поле пробелами */
if(toLeft){ pr(s, ln); prSpace(width - ln, ' '); }
else { prSpace(width - ln, ' '); pr(s, ln); }
} else { pr(s, ln); }
}
/* Печать строки длиной l */
static pr(s, ln) register char *s; register ln;
{
for( ; ln > 0 ; ln-- )
putchar( *s++ );
}
/* Печать n символов c */
static prSpace(n, c) register n; char c;{
for( ; n > 0 ; n-- )
putchar( c );
}
/* --------------------------------------------------------- */
static char *ds;
/* Римские цифры */
static prRoman(w,p,n){
char bd[60];
ds = bd;
if( n < 0 ){ n = -n; *ds++ = '-'; }
prRdig(n,6);
*ds = '\0';
prStr(w, p, bd);
}
А. Богатырев, 1992-95 - 116 - Си в UNIX
static prRdig(n, d){
if( !n ) return;
if( d ) prRdig( n/10, d - 2);
tack(n%10, d);
}
static tack(n, d){
static char im[] = " MDCLXVI";
/* ..1000 500 100 50 10 5 1 */
if( !n ) return;
if( 1 <= n && n <= 3 ){
repeat(n, im[d+2]); return;
}
if( n == 4 )
*ds++ = im[d+2];
if( n == 4 || n == 5 ){
*ds++ = im[d+1]; return;
}
if( 6 <= n && n <= 8 ){
*ds++ = im[d+1];
repeat(n - 5, im[d+2] );
return;
}
/* n == 9 */
*ds++ = im[d+2]; *ds++ = im[d];
}
static repeat(n, c) char c;
{ while( n-- > 0 ) *ds++ = c; }
/* --------------------------------------------------------- */
static char aChar = 'A';
static prInteger(w, n, base, zero) long n;
{
/* преобразуем число в строку */
char bd[128];
int neg = 0; /* < 0 */
if( n < 0 ){ neg = 1; n = -n; }
if( base < 0 ){ base = -base; aChar = 'A'; }
else { aChar = 'a'; }
ds = bd; prUDig( n, base ); *ds = '\0';
/* Теперь печатаем строку */
prIntStr( bd, w, zero, neg );
}
А. Богатырев, 1992-95 - 117 - Си в UNIX
static prUnsigned(w, n, base, zero) unsigned long n;
{
char bd[128];
if( base < 0 ){ base = -base; aChar = 'A'; }
else { aChar = 'a'; }
ds = bd; prUDig( n, base ); *ds = '\0';
/* Теперь печатаем строку */
prIntStr( bd, w, zero, 0 );
}
static prUDig( n, base ) unsigned long n;
{
unsigned long aSign;
if((aSign = n/base ) > 0 )
prUDig( aSign, base );
aSign = n % base;
*ds++ = (aSign < 10 ? '0' + aSign : aChar + (aSign - 10));
}
static prIntStr( s, width, zero, neg ) char *s;
{
int ln; /* сколько символов выводить */
int toLeft = 0; /* к какому краю прижимать */
ln = strlen(s); /* длина строки s */
/* Ширина поля: вычислить, если не указано явно */
if( ! width ){
width = ln; /* ширина поля */
if( neg ) width++; /* 1 символ для минуса */
}
if( width < 0 ){ width = -width; toLeft = 1; }
if( ! neg ){ /* Положительное число */
if(width > ln){
if(toLeft){ pr(s, ln); prSpace(width - ln, ' '); }
else { prSpace(width - ln, zero ? '0' : ' '); pr(s, ln); }
} else { pr(s, ln); }
}else{ /* Отрицательное число */
if(width > ln){
/* Надо заполнять оставшуюся часть поля */
width -- ; /* width содержит одну позицию для минуса */
if(toLeft){ putchar('-'); pr(s, ln); prSpace(width - ln, ' '); }
else{
if( ! zero ){
prSpace(width - ln, ' '); putchar('-'); pr(s,ln);
} else {
putchar('-'); prSpace(width - ln, '0'); pr(s, ln);
}
}
} else { putchar('-'); pr(s, ln); }
}
}
А. Богатырев, 1992-95 - 118 - Си в UNIX
/* --------------------------------------------------------- */
main(){
int i, n;
static char s[] = "Hello, world!\n";
static char p[] = "Hello, world";
long t = 7654321L;
myprintf( "%%abc%Y\n");
myprintf( "%s\n", "abs" );
myprintf( "%5s|\n", "abs" );
myprintf( "%-5s|\n", "abs" );
myprintf( "%5s|\n", "xyzXYZ" );
myprintf( "%-5s|\n", "xyzXYZ" );
myprintf( "%5.5s|\n", "xyzXYZ" );
myprintf( "%-5.5s|\n", "xyzXYZ" );
myprintf( "%r\n", 444 );
myprintf( "%r\n", 999 );
myprintf( "%r\n", 16 );
myprintf( "%r\n", 18 );
myprintf( "%r\n", 479 );
myprintf( "%d\n", 1234 );
myprintf( "%d\n", -1234 );
myprintf( "%ld\n", 97487483 );
myprintf( "%2d|%2d|\n", 1, -3 );
myprintf( "%-2d|%-2d|\n", 1, -3 );
myprintf( "%02d|%2d|\n", 1, -3 );
myprintf( "%-02d|%-2d|\n", 1, -3 );
myprintf( "%5d|\n", -12 );
myprintf( "%05d|\n", -12 );
myprintf( "%-5d|\n", -12 );
myprintf( "%-05d|\n", -12 );
for( i = -6; i < 6; i++ )
myprintf( "width=%2d|%0*d|%0*d|%*d|%*d|\n", i,
i, 123, i, -123, i, 123, i, -123);
myprintf( "%s at location %a\n", s, s );
myprintf( "%ld\n", t );
n = 1; t = 1L;
for( i=0; i < 34; i++ ){
myprintf( "for %2d |%016b|%d|%u|\n\t |%032lb|%ld|%lu|\n",
i, n, n, n, t, t, t );
n *= 2;
t *= 2;
}
myprintf( "%8x %8X\n", 7777, 7777 );
myprintf( "|%s|\n", p );
myprintf( "|%10s|\n", p );
myprintf( "|%-10s|\n", p );
myprintf( "|%20s|\n", p );
myprintf( "|%-20s|\n", p );
myprintf( "|%20.10s|\n", p );
myprintf( "|%-20.10s|\n", p );
myprintf( "|%.10s|\n", p );
}
А. Богатырев, 1992-95 - 119 - Си в UNIX
Выдача этой программы:
%abcY
abs
abs|
abs |
xyzXYZ|
xyzXYZ|
xyzXY|
xyzXY|
CDXLIV
CMXCIX
XVI
XVIII
CDLXXIX
1234
-1234
97487483
1|-3|
1 |-3|
01|-3|
1 |-3|
-12|
-0012|
-12 |
-12 |
width=-6|123 |-123 |123 |-123 |
width=-5|123 |-123 |123 |-123 |
width=-4|123 |-123|123 |-123|
width=-3|123|-123|123|-123|
width=-2|123|-123|123|-123|
width=-1|123|-123|123|-123|
width= 0|123|-123|123|-123|
width= 1|123|-123|123|-123|
width= 2|123|-123|123|-123|
width= 3|123|-123|123|-123|
width= 4|0123|-123| 123|-123|
width= 5|00123|-0123| 123| -123|
Hello, world!
at location 400980
7654321
for 0 |0000000000000001|1|1|
|00000000000000000000000000000001|1|1|
for 1 |0000000000000010|2|2|
|00000000000000000000000000000010|2|2|
for 2 |0000000000000100|4|4|
|00000000000000000000000000000100|4|4|
for 3 |0000000000001000|8|8|
|00000000000000000000000000001000|8|8|
for 4 |0000000000010000|16|16|
|00000000000000000000000000010000|16|16|
for 5 |0000000000100000|32|32|
|00000000000000000000000000100000|32|32|
for 6 |0000000001000000|64|64|
|00000000000000000000000001000000|64|64|
for 7 |0000000010000000|128|128|
|00000000000000000000000010000000|128|128|
for 8 |0000000100000000|256|256|
|00000000000000000000000100000000|256|256|
for 9 |0000001000000000|512|512|
|00000000000000000000001000000000|512|512|
for 10 |0000010000000000|1024|1024|
А. Богатырев, 1992-95 - 120 - Си в UNIX
|00000000000000000000010000000000|1024|1024|
for 11 |0000100000000000|2048|2048|
|00000000000000000000100000000000|2048|2048|
for 12 |0001000000000000|4096|4096|
|00000000000000000001000000000000|4096|4096|
for 13 |0010000000000000|8192|8192|
|00000000000000000010000000000000|8192|8192|
for 14 |0100000000000000|16384|16384|
|00000000000000000100000000000000|16384|16384|
for 15 |1000000000000000|32768|32768|
|00000000000000001000000000000000|32768|32768|
for 16 |10000000000000000|65536|65536|
|00000000000000010000000000000000|65536|65536|
for 17 |100000000000000000|131072|131072|
|00000000000000100000000000000000|131072|131072|
for 18 |1000000000000000000|262144|262144|
|00000000000001000000000000000000|262144|262144|
for 19 |10000000000000000000|524288|524288|
|00000000000010000000000000000000|524288|524288|
for 20 |100000000000000000000|1048576|1048576|
|00000000000100000000000000000000|1048576|1048576|
for 21 |1000000000000000000000|2097152|2097152|
|00000000001000000000000000000000|2097152|2097152|
for 22 |10000000000000000000000|4194304|4194304|
|00000000010000000000000000000000|4194304|4194304|
for 23 |100000000000000000000000|8388608|8388608|
|00000000100000000000000000000000|8388608|8388608|
for 24 |1000000000000000000000000|16777216|16777216|
|00000001000000000000000000000000|16777216|16777216|
for 25 |10000000000000000000000000|33554432|33554432|
|00000010000000000000000000000000|33554432|33554432|
for 26 |100000000000000000000000000|67108864|67108864|
|00000100000000000000000000000000|67108864|67108864|
for 27 |1000000000000000000000000000|134217728|134217728|
|00001000000000000000000000000000|134217728|134217728|
for 28 |10000000000000000000000000000|268435456|268435456|
|00010000000000000000000000000000|268435456|268435456|
for 29 |100000000000000000000000000000|536870912|536870912|
|00100000000000000000000000000000|536870912|536870912|
for 30 |1000000000000000000000000000000|1073741824|1073741824|
|01000000000000000000000000000000|1073741824|1073741824|
for 31 |10000000000000000000000000000000|-2147483648|2147483648|
|10000000000000000000000000000000|-2147483648|2147483648|
for 32 |0000000000000000|0|0|
|00000000000000000000000000000000|0|0|
for 33 |0000000000000000|0|0|
|00000000000000000000000000000000|0|0|
1e61 1E61
|Hello, world|
|Hello, world|
|Hello, world|
| Hello, world|
|Hello, world |
| Hello, wor|
|Hello, wor |
|Hello, wor|
2.62. Рассмотрим программу суммирования векторов:
А. Богатырев, 1992-95 - 121 - Си в UNIX
int A[1024], B[1024], C[1024];
...
for(i=0; i < 1024; i++) C[i] = A[i] + B[i];
А почему бы не
for(i=1024-1; i >=0 ; --i) ...;
А почему бы не в произвольном порядке?
foreach i in (0..1023) ...;
Данный пример показывает, что некоторые операции обладают врожденным паралеллизмом,
ведь все 1024 сложений можно было бы выполнять параллельно! Однако тупой компилятор
будет складывать их именно в том порядке, в котором вы ему велели. Только самые сов-
ременные компиляторы на многопроцессорных системах умеют автоматически распараллели-
вать такие циклы. Сам язык Си не содержит средств указания параллельности (разве что
снова - библиотеки и системные вызовы для этого).
А. Богатырев, 1992-95 - 122 - Си в UNIX
3. Мобильность и машинная зависимость программ. Проблемы с русскими буквами.
Программа считается мобильной, если она без каких-либо изменений ее исходного
текста (либо после настройки некоторых констант при помощи #define и #ifdef) трансли-
руется и работает на разных типах машин (с разной разрядностью, системой команд,
архитектурой, периферией) под управлением операционных систем одного семейства. Заме-
тим, что мобильными могут быть только исходные тексты программ, объектные модули для
разных процессоров, естественно, несовместимы!
3.1. Напишите программу, печатающую размер типов данных char, short, int, long,
float, double, (char *) в байтах. Используйте для этого встроенную операцию sizeof.
3.2. Составьте мобильную программу, выясняющую значения следующих величин для любой
машины, на которой работает программа:
1) Наибольшее допустимое знаковое целое.
2) Наибольшее беззнаковое целое.
3) Наибольшее по абсолютной величине отрицательное целое.
4) Точность значения |x|, отличающегося от 0, где x - вещественное число.
5) Наименьшее значение e, такое что машина различает числа 1 и 1+e (для веществен-
ных чисел).
3.3. Составьте мобильную программу, выясняющую длину машинного слова ЭВМ (число
битов в переменной типа int). Указание: для этого можно использовать битовые сдвиги.
3.4. Надо ли писать в своих программах определения
#define EOF (-1)
#define NULL ((char *) 0) /* или ((void *)0) */
Ответ: НЕТ. Во-первых, эти константы уже определены в include-файле, подключаемом по
директиве
#include <stdio.h>
поэтому правильнее написать именно эту директиву. Во-вторых, это было бы просто неп-
равильно: конкретные значения этих констант на данной машине (в данной реализации
системы) могут быть другими! Чтобы придерживаться тех соглашений, которых придержива-
ются все стандартные функции данной реализации, вы ДОЛЖНЫ брать эти константы из
<stdio.h>.
По той же причине следует писать
#include <fcntl.h>
int fd = open( имяФайла, O_RDONLY); /* O_WRONLY, O_RDWR */
вместо
int fd = open( имяФайла, 0); /* 1, 2 */
3.5. Почему может завершаться по защите памяти следующая программа?
#include <sys/types.h>
#include <stdio.h>
time_t t;
extern time_t time();
...
t = time(0);
/* узнать текущее время в секундах с 1 Янв. 1970 г.*/
Ответ: дело в том, что прототип системного вызова time() это:
time_t time( time_t *t );
то есть аргумент должен быть указателем. Мы же вместо указателя написали в качестве
А. Богатырев, 1992-95 - 123 - Си в UNIX
аргумента 0 (типа int). На машине IBM PC AT 286 указатель - это 2 слова, а целое -
одно. Недостающее слово будет взято из стека произвольно. В результате time() полу-
чает в качестве аргумента не нулевой указатель, а мусор. Правильно будет написать:
t = time(NULL);
либо (по определению time())
time( &t );
а еще более корректно так:
t = time((time_t *)NULL);
Мораль: везде, где требуется нулевой указатель, следует писать NULL (или явное приве-
дение нуля к типу указателя), а не просто 0.
3.6. Найдите ошибку:
void f(x, s) long x; char *s;
{
printf( "%ld %s\n", x, s );
}
void main(){
f( 12, "hello" );
}
Эта программа работает на IBM PC 386, но не работает на IBM PC 286.
Ответ. Здесь возникает та же проблема, что и в примере про sin(12). Дело в том,
что f требует первый аргумент типа long (4 байта на IBM PC 286), мы же передаем ей
int (2 байта). В итоге в x попадает неверное значение; но более того, недостающие
байты отбираются у следующего аргумента - s. В итоге и адрес строки становится непра-
вильным, программа обращается по несуществующему адресу и падает. На IBM PC 386 и
int и long имеют длину 4 байта, поэтому там эта ошибка не проявляется!
Опять-таки, это повод для использования прототипов функций (когда вы прочитаете
про них - вернитесь к этому примеру!). Напишите прототип
void f(long x, char *s);
и ошибки не будет.
В данном примере мы использовали тип void, которого не сушествовало в ранних
версиях языка Си. Этот тип означает, что функция не возвращает значения (то есть
является "процедурой" в смысле языков Pascal или Algol). Если мы не напишем слово
void перед f, то компилятор будет считать функцию f возвращающей целое (int), хотя
эта функция ничего не возвращает (в ней нет оператора return). В большинстве случаев
это не принесет вреда и программа будет работать. Но зато если мы напишем
int x = f((long) 666, "good bye" );
то x получит непредсказуемое значение. Если же f описана как void, то написанный опе-
ратор заставит компилятор сообщить об ошибке.
Тип (void *) означает указатель на что угодно (понятно, что к такому указателю
операции [], *, -> неприменимы: сначала следует явно привести указатель к содержа-
тельному типу "указатель на тип"). В частности, сейчас стало принято считать, что
функция динамического выделения памяти (memory allocation) malloc() (которая отводит
в куче|= область памяти заказанного размера и выдает указатель на нее) имеет прототип:
____________________
|- В данной книге слова "указатель" и "ссылка" употребляются в одном и том же
смысле. Если вы обратитесь к языку Си++, то обнаружите, что там эти два термина
(pointer и reference) означают разные понятия (хотя и сходные).
____________________
А. Богатырев, 1992-95 - 124 - Си в UNIX
void *malloc(unsigned size); /* size байт */
char *s = (char *) malloc( strlen(buf)+1 );
struct ST *p = (struct ST *) malloc( sizeof(struct ST));
/* или sizeof(*p) */
хотя раньше принято было char *malloc();
3.7. Поговорим про оператор sizeof. Отметим распространенную ошибку, когда sizeof
принимают за функцию. Это не так! sizeof вычисляется компилятором при трансляции
программы, а не программой во время выполнения. Пусть
char a[] = "abcdefg";
char *b = "hijklmn";
Тогда
sizeof(a) есть 8 (байт \0 на конце - считается)
sizeof(b) есть 2 на PDP-11 (размер указателя)
strlen(a) есть 7
strlen(b) есть 7
Если мы сделаем
b = "This ia a new line";
strcpy(a, "abc");
то все равно
sizeof(b) останется равно 2
sizeof(a) 8
Таким образом sizeof выдает количество зарезервированной для переменной памяти (в
байтах), независимо от текущего ее содержимого.
Операция sizeof применима даже к выражениям. В этом случае она сообщает нам,
каков будет размер у результата этого выражения. Само выражение при этом не вычисля-
ется, так в
double f(){ printf( "Hi!\n"); return 12.34; }
main(){
int x = 2; long y = 4;
printf( "%u\n", sizeof(x + y + f()));
}
будет напечатано значение, совпадающее с sizeof(double), а фраза "Hi!" не будет напе-
чатана.
Когда оператор sizeof применяется к переменной (а не к имени типа), можно не
писать круглые скобки:
sizeof(char *); но sizeof x;
3.8. Напишите объединение, в котором может храниться либо указатель, либо целое,
либо действительное число. Ответ:
union all{
char *s; int i; double f;
____________________
|= "Куча" (heap, pool) - область статической памяти, увеличивающаяся по мере надоб-
ности, и предназначенная как раз для хранения динамически отведенных данных.
А. Богатырев, 1992-95 - 125 - Си в UNIX
} x;
x.i = 12 ; printf("%d\n", x.i);
x.f = 3.14; printf("%f\n", x.f);
x.s = "Hi, there"; printf("%s\n", x.s);
printf("int=%d double=%d (char *)=%d all=%d\n",
sizeof(int), sizeof(double), sizeof(char *),
sizeof x);
В данном примере вы обнаружите, что размер переменной x равен максимальному из разме-
ров типов int, double, char *.
Если вы хотите использовать одну и ту же переменную для хранения данных разных
типов, то для получения мобильной программы вы должны пользоваться только объединени-
ями и никогда не привязываться к длине слова и представлению этих типов данных на
конкретной ЗВМ! Раньше, когда программисты не думали о мобильности, они писали прог-
раммы, где в одной переменой типа int хранили в зависимости от нужды то целые значе-
ния, то указатели (это было на машинах PDP и VAX). Увы, такие программы оказались
непереносимы на машины, на которых sizeof(int) != sizeof(char *), более того, они
оказались весьма туманны для понимания их другими людьми. Не следуйте этому стилю
(такой стиль американцы называют "poor style"), более того, всеми силами избегайте
его!
Сравните два примера, использующие два стиля программирования. Первый стиль не
так плох, как только что описанный, но все же мы рекомендуем использовать только вто-
рой:
/* СТИЛЬ ПЕРВЫЙ: ЯВНЫЕ ПРЕОБРАЗОВАНИЯ ТИПОВ */
typedef void *PTR; /* универсальный указатель */
struct a { int x, y; PTR pa; } A;
struct b { double u, v; PTR pb; } B;
#define Aptr(p) ((struct a *)(p))
#define Bptr(p) ((struct b *)(p))
PTR ptr1, ptr2;
main(){
ptr1 = &A; ptr2 = &B;
Bptr(ptr2)->u = Aptr(ptr1)->x = 77;
printf("%f %d\n", B.u, A.x);
}
/* СТИЛЬ ВТОРОЙ: ОБ'ЕДИНЕНИЕ */
/* предварительное объявление: */
extern struct a; extern struct b;
/* универсальный тип данных: */
typedef union everything {
int i; double d; char *s;
struct a *ap; struct b *bp;
} ALL;
struct a { int x, y; ALL pa; } A;
struct b { double u, v; ALL pb; } B;
ALL ptr1, ptr2, zz;
main(){
ptr1.ap = &A; ptr2.bp = &B; zz.i = 77;
ptr2.bp->u = ptr1.ap->x = zz.i;
printf("%f %d\n", B.u, A.x);
}
3.9. Для выделения классов символов (например цифр), следует пользоваться макросами
из include-файла <ctype.h> Так вместо
if( '0' <= c && c <= '9' ) ...
А. Богатырев, 1992-95 - 126 - Си в UNIX
следует использовать
#include <ctype.h>
.....
if(isdigit(c)) ...
и вместо
if((c >='a' && c <= 'z') || (c >= 'A' && c <= 'Z')) ...
надо
if(isalpha(c)) ...
Дело в том, что сравнения < и > зависят от расположения букв в используемой коди-
ровке. Но например, в кодировке КОИ-8 русские буквы расположены НЕ в алфавитном
порядке. Вследствие этого, если для
char c1, c2;
c1 < c2
то это еще не значит, что буква c1 предшествует букве c2 в алфавите! Лексикографичес-
кое сравнение требует специальной перекодировки букв к "упорядоченной" кодировке.
Аналогично, сравнение
if( c >= 'а' && c <= 'я' )
скорее всего не даст ожидаемого результата. Макроопределения же в <ctype.h> исполь-
зуют массив флагов для каждой буквы кодировки, и потому не зависят от порядка букв (и
работают быстрее). Идея реализации такова:
extern unsigned char _ctype[]; /*массив флагов*/
#define US(c) (sizeof(c)==sizeof(char)?((c)&0xFF):(c))
/* подавление расширения знакового бита */
/* Ф Л А Г И */
#define _U 01 /* uppercase: большая буква */
#define _L 02 /* lowercase: малая буква */
#define _N 04 /* number: цифра */
#define _S 010 /* space: пробел */
/* ... есть и другие флаги ... */
#define isalpha(c) ((_ctype+1)[US(c)] & (_U|_L) )
#define isupper(c) ((_ctype+1)[US(c)] & _U )
#define islower(c) ((_ctype+1)[US(c)] & _L )
#define isdigit(c) ((_ctype+1)[US(c)] & _N )
#define isalnum(c) ((_ctype+1)[US(c)] & (_U|_L|_N))
#define tolower(c) ((c) + 'a' - 'A' )
#define toupper(c) ((c) + 'A' - 'a' )
где массив _ctype[] заполнен заранее (это проинициализированные статические данные) и
хранится в стандартной библиотеке Си. Вот его фрагмент:
unsigned char _ctype[256 /* размер алфавита */ + 1] = {
/* EOF код (-1) */ 0,
...
/* '1' код 061 0x31 */ _N,
...
/* 'A' код 0101 0x41 */ _U,
...
/* 'a' код 0141 0x61 */ _L,
...
};
А. Богатырев, 1992-95 - 127 - Си в UNIX
Выигрыш в скорости получается вот почему: если мы определим|-
#define isalpha(c) (((c) >= 'a' && (c) <= 'z') || \
((c) >= 'A' && (c) <= 'Z'))
то этот оператор состоит из 7 операций. Если же мы используем isalpha из <ctype.h>
(как определено выше) - мы используем только две операции: индексацию и проверку
битовой маски &. Операции _ctype+1 и _U|_L вычисляются до констант еще при компиля-
ции, и поэтому не вызывают генерации машинных команд.
Определенные выше toupper и tolower работают верно лишь в кодировке ASCII|=, в
которой все латинские буквы расположены подряд и по алфавиту. Обратите внимание, что
tolower имеет смысл применять только к большим буквам, а toupper - только к малень-
ким:
if( isupper(c) ) c = tolower(c);
Существует еще черезвычайно полезный макрос isspace(c), который можно было бы опреде-
лить как
#define isspace(c) (c==' ' ||c=='\t'||c=='\f'|| \
c=='\n'||c=='\r')
или
#define isspace(c) (strchr(" \t\f\n\r",(c)) != NULL)
На самом деле он, конечно, реализован через флаги в _ctype[]. Он используется для
определения символов-пробелов, служащих заполнителями промежутков между словами
текста.
Есть еще два нередко используемых макроса: isprint(c), проверяющий, является ли
c ПЕЧАТНЫМ символом, т.е. имеющим изображение на экране; и iscntrl(c), означающий,
что символ c является управляющим, т.е. при его выводе на терминал ничего не изобра-
зится, но терминал произведет некоторое действие, вроде очистки экрана или перемеще-
ния курсора в каком-то направлении. Они нужны, как правило, для отображения управля-
ющих ("контроловских") символов в специальном печатном виде, вроде ^A для кода '\01'.
Задание: исследуйте кодировку и <ctype.h> на вашей машине. Напишите функцию
лексикографического сравнения букв и строк.
Указание: пусть буквы имеют такие коды (это не соответствует реальности!):
буква: а б в г д е
код: 1 4 2 5 3 0
нужно: 0 1 2 3 4 5
Тогда идея функции Ctou перекодировки к упорядоченному алфавиту такова:
unsigned char UU[] = { 5, 0, 2, 4, 1, 3 };
/* в действительности - 256 элементов: UU[256] */
Ctou(c) unsigned char c; { return UU[c]; }
int strcmp(s1, s2) char *s1, *s2; {
/* Проигнорировать совпадающие начала строк */
while(*s1 && *s1 == *s2) s1++, s2++;
/* Вернуть разность [не]совпавших символов */
return Ctou(*s1) - Ctou(*s2);
____________________
|- Обратите внимание, что символ \ в конце строки макроопределения позволяет про-
должить макрос на следующей строке, поэтому макрос может состоять из многих строк.
|= ASCII - American Standard Code for Information Interchange - наиболее распрост-
раненная в мире кодировка (Американский стандарт).
А. Богатырев, 1992-95 - 128 - Си в 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;