static char str[] = "До встречи в буфете";
char *pt;

pt = str; puts(pt); puts(++pt);
str[7] = '\0'; puts(str); puts(pt);
puts(++pt);
}


2.36. Что напечатает следующая программа?

main() {
static char name[] = "Константин";
char *pt;
pt = name + strlen(name);
while(--pt >= name)
puts(pt);
}


2.37. Что напечатает следующая программа?

char str1[] = "abcdef";
char str2[] = "xyz";
main(){
register char *a, *b;
a = str1; b = str2;
while( *b )
*a++ = *b++;
printf( "str=%s a=%s\n", str1, a );

a = str1; b = str2;



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

while( *b )
*++a = *b++;
printf( "str=%s a=%s\n", str1, a );
}

Ответ:

str=xyzdef a=def
str=xxyzef a=zef


2.38. Что печатает программа?

char *s;
for(s = "Ситроен"; *s; s+= 2){
putchar(s[0]); if(!s[1]) break;
}
putchar('\n');


2.39. Что напечатает программа? Рассмотрите продвижение указателя s, указателей -
элементов массива strs[]. Разберитесь с порядком выполнения операций. В каких случаях
++ изменяет указатель, а в каких - букву в строке? Нарисуйте себе картинку, изобража-
ющую состояние указателей - она поможет вам распутать эти спагетти. Уделите разбору
этого примера достаточное время!

#include <stdio.h> /* определение NULL */
/* Латинский алфавит: abcdefghijklmnopqrstuvwxyz */
char *strs[] = {
"abcd","ABCD","0fpx","159",
"hello","-gop","A1479",NULL
};
main(){
char c, **s = strs, *p;
c = *++*s; printf("#1 %d %c %s\n", s-strs, c, *s);
c = **++s; printf("#2 %d %c %s\n", s-strs, c, *s);
c = **s++; printf("#3 %d %c %s\n", s-strs, c, *s);
c = ++**s; printf("#4 %d %c %s\n", s-strs, c, *s);
c = (**s)++; printf("#5 %d %c %s\n", s-strs, c, *s);
c = ++*++*s; printf("#6 %d %c %s\n", s-strs, c, *s);
c = *++*s++; printf("#7 %d %c %s %s\n",
s-strs, c, *s, strs[2]);
c = ++*++*s++; printf("#8 %d %c %s %s\n",
s-strs, c, *s, strs[3]);
c = ++*++*++s; printf("#9 %d %c %s\n", s-strs,c,*s);
c = ++**s++; printf("#10 %d %c %s\n",s-strs,c,*s);
p = *s; c = ++*(*s)++;
printf("#11 %d %c %s %s %s\n",s-strs,c,*s,strs[6],p);
c = ++*((*s)++); printf("#12 %d %c %s %s\n",
s-strs, c, *s, strs[6]);
c = (*++(*s))++; printf("#13 %d %c %s %s\n",
s-strs, c, *s, strs[6]);
for(s=strs; *s; s++)
printf("strs[%d]=\"%s\"\n", s-strs, *s);
putchar('\n');
}

Печатается:






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

#1 0 b bcd strs[0]="bcd"
#2 1 A ABCD strs[1]="ABCD"
#3 2 A 0fpx strs[2]="px"
#4 2 1 1fpx strs[3]="69"
#5 2 1 2fpx strs[4]="hello"
#6 2 g gpx strs[5]="iop"
#7 3 p 159 px strs[6]="89"
#8 4 6 hello 69
#9 5 h hop
#10 6 i A1479
#11 6 B 1479 1479 B1479
#12 6 2 479 479
#13 6 7 89 89

Учтите, что конструкция

char *strs[1] = { "hello" };

означает, что в strs[0] содержится указатель на начальный байт безымянного массива,
содержащего строку "hello". Этот указатель можно изменять! Попробуйте составить еще
подобные примеры из *, ++, ().

2.40. Что печатает программа?

char str[25] = "Hi, ";
char *f(char **s){ int cnt;
for(cnt=0; **s != '\0'; (*s)++, ++cnt);
return("ny" + (cnt && (*s)[-1] == ' ') + (!cnt));
}
void main(void){ char *s = str;
if( *f(&s) == 'y') strcat(s, "dude");
else strcat(s, " dude");
printf("%s\n", str);
}

Что она напечатает, если задать

char str[25]="Hi,"; или char str[25]="";


2.41. В чем состоит ошибка? (Любимая ошибка начинающих)

main(){
char *buf; /* или char buf[]; */
gets( buf );
printf( "%s\n", buf );
}

Ответ: память под строку buf не выделена, указатель buf не проинициализирован и смот-
рит неизвестно куда. Надо было писать например так:

char buf[80];
или
char mem[80], *buf = mem;

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



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

Вот программа, которая также использует неинициализированный указатель. На
машине SPARCstation 20 эта программа убивается операционной системой с диагностикой
"Segmentation fault" (SIGSEGV). Это как раз и значит обращение по указателю, указы-
вающему "пальцем в небо".

main(){
int *iptr;
int ival = *iptr;

printf("%d\n", ival);
}


2.42. Для получения строки "Life is life" написана программа:

main(){
char buf[ 60 ];
strcat( buf, "Life " );
strcat( buf, "is " );
strcat( buf, "life" );
printf( "%s\n", buf );
}

Что окажется в массиве buf?
Ответ: в начале массива окажется мусор, поскольку автоматический массив не инициали-
зируется байтами '\0', а функция strcat() приписывает строки к концу строки. Для исп-
равления можно написать

*buf = '\0';

перед первым strcat()-ом, либо вместо первого strcat()-а написать
strcpy( buf, "Life " );

2.43. Составьте макроопределение copystr(s1, s2) для копирования строки s2 в строку
s1.

2.44. Составьте макроопределение lenstr(s) для вычисления длины строки.
Многие современные компиляторы сами обращаются с подобными короткими (1-3 опера-
тора) стандартными функциями как с макросами, то есть при обращении к ним генерят не
вызов функции, а подставляют текст ее тела в место обращения. Это делает объектный
код несколько "толще", но зато быстрее. В расширенных диалектах Си и в Си++ компиля-
тору можно предложить обращаться так и с вашей функцией - для этого функцию следует
объявить как inline (такие функции называются еще "intrinsic").

2.45. Составьте рекурсивную и нерекурсивную версии программы инвертирования (зер-
кального отображения) строки:

abcdef --> fedcba.


2.46. Составьте функцию index(s, t), возвращающую номер первого вхождения символа t
в строку s; если символ t в строку не входит, функция возвращает -1.
Перепишите эту функцию с указателями, чтобы она возвращала указатель на первое
вхождение символа. Если символ в строке отсутствует - выдавать NULL. В UNIX System-V
такая функция называется strchr. Вот возможный ответ:

char *strchr(s, c) register char *s, c;
{ while(*s && *s != c) s++;
return *s == c ? s : NULL;
}




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

Заметьте, что p=strchr(s,'\0'); выдает указатель на конец строки. Вот пример исполь-
зования:

extern char *strchr();
char *s = "abcd/efgh/ijklm";
char *p = strchr(s, '/');
printf("%s\n", p==NULL ? "буквы / нет" : p);
if(p) printf("Индекс вхождения = s[%d]\n", p - s );


2.47. Напишите функцию strrchr(), указывающую на последнее вхождение символа.
Ответ:

char *strrchr(s, c) register char *s, c;
{ char *last = NULL;
do if(*s == c) last = s; while(*s++);
return last;
}

Вот пример ее использования:

extern char *strrchr();
char p[] = "wsh"; /* эталон */
main(argc, argv) char *argv[];{
char *s = argv[1]; /* проверяемое имя */
/* попробуйте вызывать
* a.out csh
* a.out /bin/csh
* a.out wsh
* a.out /usr/local/bin/wsh
*/
char *base =
(base = strrchr(s, '/')) ? base+1 : s;
if( !strcmp(p, base))
printf("Да, это %s\n" , p);
else printf("Нет, это %s\n", base);

/* еще более изощренный вариант: */
if( !strcmp(p,(base=strrchr(s,'/')) ? ++base :
(base=s))
) printf("Yes %s\n", p);
else printf("No %s\n", base);
}


2.48. Напишите макрос substr(to,from,n,len) который записывает в to кусок строки
from начиная с n-ой позиции и длиной len. Используйте стандартную функцию strncpy.
Ответ:

#define substr(to, from, n, len) strncpy(to, from+n, len)

или более корректная функция:












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

char *substr(to, from, n, len) char *to, *from;
{
int lfrom = strlen(from);
if(n < 0 ){ len += n; n = 0; }
if(n >= lfrom || len <= 0)
*to = '\0'; /* пустая строка */
else{
/* длина остатка строки: */
if(len > lfrom-n) len = lfrom - n;
strncpy(to, from+n, len);
to[len] = '\0';
}
return to;
}


2.49. Напишите функцию, проверяющую, оканчивается ли строка на ".abc", и если нет -
приписывающую ".abc" к концу. Если же строка уже имеет такое окончание - ничего не
делать. Эта функция полезна для генерации имен файлов с заданным расширением. Сде-
лайте расширение аргументом функции.
Для сравнения конца строки s со строкой p следует использовать:

int ls = strlen(s), lp = strlen(p);
if(ls >= lp && !strcmp(s+ls-lp, p)) ...совпали...;


2.50. Напишите функции вставки символа c в указанную позицию строки (с раздвижкой
строки) и удаления символа в заданной позиции (со сдвижкой строки). Строка должна
изменяться "на месте", т.е. никуда не копируясь. Ответ:

/* удаление */
char delete(s, at) register char *s;
{
char c;
s += at; if((c = *s) == '\0') return c;
while( s[0] = s[1] ) s++;
return c;
}
/* либо просто strcpy(s+at, s+at+1); */


/* вставка */
insert(s, at, c) char s[], c;
{
register char *p;
s += at; p = s;
while(*p) p++; /* на конец строки */
p[1] = '\0'; /* закрыть строку */
for( ; p != s; p-- )
p[0] = p[-1];
*s = c;
}


2.51. Составьте программу удаления символа c из строки s в каждом случае, когда он
встречается.
Ответ:







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

delc(s, c) register char *s; char c;
{
register char *p = s;
while( *s )
if( *s != c ) *p++ = *s++;
else s++;
*p = '\0'; /* не забывайте закрывать строку ! */
}


2.52. Составьте программу удаления из строки S1 каждого символа, совпадающего с
каким-либо символом строки S2.

2.53. Составьте функцию scopy(s,t), которая копирует строку s в t, при этом символы
табуляции и перевода строки должны заменяться на специальные двухсимвольные последо-
вательности "\n" и "\t". Используйте switch.

2.54. Составьте функцию, которая "укорачивает" строку, заменяя изображения спецсим-
волов (вроде "\n") на сами эти символы ('\n'). Ответ:

extern char *strchr();
void unquote(s) char *s;
{ static char from[] = "nrtfbae",
to [] = "\n\r\t\f\b\7\33";
char c, *p, *d;

for(d=s; c = *s; s++)
if( c == '\\'){
if( !(c = *++s)) break;
p = strchr(from, c);
*d++ = p ? to[p - from] : c;
}else *d++ = c;
*d = '\0';
}


2.55. Напишите программу, заменяющую в строке S все вхождения подстроки P на строку
Q, например:

P = "ура"; Q = "ой";
S = "ура-ура-ура!";
Результат: "ой-ой-ой!"


2.56. Кроме функций работы со строками (где предполагается, что массив байт заверша-
ется признаком конца '\0'), в Си предусмотрены также функции для работы с массивами
байт без ограничителя. Для таких функций необходимо явно указывать длину обрабатывае-
мого массива. Напишите функции: пересылки массива длиной n байт memcpy(dst,src,n);
заполнения массива символом c memset(s,c,n); поиска вхождения символа в массив
memchr(s,c,n); сравнения двух массивов memcmp(s1,s2,n); Ответ:

#define REG register
char *memset(s, c, n) REG char *s, c;
{ REG char *p = s;
while( --n >= 0 ) *p++ = c;
return s;
}
char *memcpy(dst, src, n)
REG char *dst, *src;
REG int n;
{ REG char *d = dst;



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

while( n-- > 0 ) *d++ = *src++;
return dst;
}
char *memchr(s, c, n) REG char *s, c;
{
while(n-- && *s++ != c);
return( n < 0 ? NULL : s-1 );
}
int memcmp(s1, s2, n)
REG char *s1, *s2; REG n;
{
while(n-- > 0 && *s1 == *s2)
s1++, s2++;
return( n < 0 ? 0 : *s1 - *s2 );
}

Есть такие стандартные функции.

2.57. Почему лучше пользоваться стандартными функциями работы со строками и памятью
(strcpy, strlen, strchr, memcpy, ...)?
Ответ: потому, что они обычно реализованы поставщиками системы ЭФФЕКТИВНО, то
есть написаны не на Си, а на ассемблере с использованием специализированных машинных
команд и регистров. Это делает их более быстрыми. Написанный Вами эквивалент на Си
может использоваться для повышения мобильности программы, либо для внесения поправок
в стандартные функции.

2.58. Рассмотрим программу, копирующую строку саму в себя:

#include <stdio.h>
#include <string.h>

char string[] = "abcdefghijklmn";
void main(void){
memcpy(string+2, string, 5);
printf("%s\n", string);
exit(0);

Она печатает abababahijklmn. Мы могли бы ожидать, что кусок длины 5 символов "abcde"
будет скопирован как есть: ab[abcde]hijklmn, а получили ab[ababa]hijklmn - цикличес-
кое повторение первых двух символов строки... В чем дело? Дело в том, что когда
области источника (src) и получателя (dst) перекрываются, то в некий момент *src
берется из УЖЕ перезаписанной ранее области, то есть испорченной! Вот программа,
иллюстрирующая эту проблему:





















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

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

char string[] = "abcdefghijklmn";
char *src = &string[0];
char *dst = &string[2];
int n = 5;

void show(int niter, char *msg){
register length, i;

printf("#%02d %s\n", niter, msg);
length = src-string;
putchar('\t');
for(i=0; i < length+3; i++) putchar(' ');
putchar('S'); putchar('\n');

printf("\t...%s...\n", string);

length = dst-string;
putchar('\t');
for(i=0; i < length+3; i++) putchar(' ');
putchar('D'); putchar('\n');
}


void main(void){
int iter = 0;

while(n-- > 0){
show(iter, "перед");
*dst++ = toupper(*src++);
show(iter++, "после");
}
exit(0);
}

Она печатает:

























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

#00 перед
S
...abcdefghijklmn...
D
#00 после
S
...abAdefghijklmn...
D
#01 перед
S
...abAdefghijklmn...
D
#01 после
S
...abABefghijklmn...
D
#02 перед
S
...abABefghijklmn...
D
#02 после
S
...abABAfghijklmn...
D
#03 перед
S
...abABAfghijklmn...
D
#03 после
S
...abABABghijklmn...
D
#04 перед
S
...abABABghijklmn...
D
#04 после
S
...abABABAhijklmn...
D

Отрезки НЕ перекрываются, если один из них лежит либо целиком левее, либо целиком
правее другого (n - длина обоих отрезков).

dst src src dst
######## @@@@@@@@ @@@@@@@@ ########

dst+n <= src или src+n <= dst
dst <= src-n или dst >= src+n

Отрезки перекрываются в случае

! (dst <= src - n || dst >= src + n) =
(dst > src - n && dst < src + n)

При этом опасен только случай dst > src. Таким образом опасная ситуация описывается
условием

src < dst && dst < src + n

(если dst==src, то вообще ничего не надо делать). Решением является копирование "от



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

хвоста к голове":

void bcopy(register char *src, register char *dst,
register int n){

if(dst >= src){
dst += n-1;
src += n-1;
while(--n >= 0)
*dst-- = *src--;
}else{
while(n-- > 0)
*dst++ = *src++;
}
}

Или, ограничиваясь только опасным случаем:

void bcopy(register char *src, register char *dst,
register int n){

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);
}

Программа

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

char string[] = "abcdefghijklmn";
char *src = &string[0];
char *dst = &string[2];
int n = 5;

void show(int niter, char *msg){
register length, i;

printf("#%02d %s\n", niter, msg);
length = src-string;
putchar('\t');
for(i=0; i < length+3; i++) putchar(' ');
putchar('S'); putchar('\n');

printf("\t...%s...\n", string);

length = dst-string;
putchar('\t');
for(i=0; i < length+3; i++) putchar(' ');
putchar('D'); putchar('\n');
}







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

void main(void){
int iter = 0;

if(dst==src || n <= 0){
printf("Ничего не надо делать\n");
return;
}

if(src < dst && dst < src + n) {
dst += n-1;
src += n-1;
while(--n >= 0){
show(iter, "перед");
*dst-- = toupper(*src--);
show(iter++, "после");
}
}else
while(n-- > 0){
show(iter, "перед");
*dst++ = toupper(*src++);
show(iter++, "после");
}
exit(0);
}

Печатает






































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

#00 перед
S
...abcdefghijklmn...
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,