-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcstr.html
More file actions
619 lines (542 loc) · 38.6 KB
/
cstr.html
File metadata and controls
619 lines (542 loc) · 38.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
<head>
<META http-equiv="Content-Type" content="text/html;
charset=utf-8">
<title>Сишные строки, проблемы и подходы</title>
</head>
<body>
<p><center><h3>Преамбула</h3></center></p>
<p>Здесь описываются методы со стандартными (ограниченными нулём)
строками в Си для изучающих Си. Мы предполагаем что тот кто будет
использовать этот механизм уже предупреждён в общих чертах о
проблемах, которые ему предстоит преодолевать как в связи с
использованием языка Си вместо высокоуровневых средств, так и в
связи с использованием ограниченных нулём строк и что у него нет
иных средств; если есть иные средства решающие поставленную
задачу, лучше использовать их, вынося на уровень Си только то, что
реализует интерфейс с системными средствами или требует
высокоскоростной обработки.</p>
<p>Описываемые строки носят название "ограниченные нулём" (более
точно следовало бы сказать "ограниченные нулевым символом"), в
английском принят термин "NUL-terminated", иногда не совсем
корректно "null-terminated". NUL - это ASCII обозначение символа с
кодом 0, не имеющего никакого графического или управляющего смысла
в ASCII (хотя имеющего графический вид, например, в cp437 -
кодировке текстовой консоли PC). В дальнейшем изложении будем
говорить "nt-строки" или просто "строки".</p>
<p>Мы предполагаем, что читающий это описание имеет первичное
представление о том, что такое указатель в языке Си; при
отсутствии такого представления освоение понятия строк становится
проблематичным.</p>
<p><center><h3>Основные понятия</h3></center></p>
<p>В мире nt-строк существуют следующие основные понятия:</p>
<dl>
<dt>Символ</dt>
<dd>Символ в Си - элемент строки, занимающий фиксированный размер
в памяти; в большинстве случаев это char для ASCII и совместимого
с ASCII транспорта (например, utf-8) и wchar_t для UTF-16 и других
представлений Unicode, во многих случаях (например в Microsoft
Windows) называемых просто Unicode. Это понятие символа может не
совпадать с понятием символа в целевой кодировке для
"многобайтных" кодировок (которое может быть названо, например,
"code point"); за более детальной расшифровкой проблем см. <a
href="http://www.unicode.org">стандарт Unicode</a>.</dd>
<dt>Константная строка</dt>
<dd>Константная строка представляет собой последовательность
<b>символов</b> (см. выше) ограниченная символом NUL (с кодом 0);
получивший такую строку не имеет права производить запись в неё, а
должен ограничиться только чтением её содержимого начиная с
указанной указателем позиции и до символа NUL (с кодом 0), но не
далее.</dd>
<dt>Строковый буфер</dt>
<dd>Строковый буфер представляет собой массив символов, для
которого определены адрес начала и длина (или как варианты, адрес
начала и конца, или адрес начала и адрес-ограничитель - адрес
следующего за концом символа). Функция имеет право работать со
строковым буфером, получив или аллоцировав его как буфер и зная
пределы в памяти, в которых она имеет право работать. Буфер может
содержать в себе как полные строки, так и произвольные данные, не
соответствующие определению строки.</dd>
</dl>
<p>Основные проблемы начинающих работать со строками возникают из
непонимания разницы между константной строкой и строковым буфером,
поэтому мы будем акцентировать различие между ними.</p>
<p><center><h3>Константная строка</h3></center></p>
<p>Константная строка служит для передачи строки как
неизменяемой последовательности символов. Тип передаваемых данных
- указатель на константный символ; это может быть <code>const
char*</code>, в наиболее типичном случае, <code>const
TCHAR*</code>, для программирования независимого от типа символа
под Microsoft Windows, <code>const wchar_t*</code>, для широких
символов таких как Unicode; возможны непринципиальные добавки
signed или unsigned.</p>
<p>Разрешённые действия с константной строкой - чтение содержимого
строки посимвольно с помощью указателя; продвижение указателя
вперёд, но не дальше позиции следующей за финальным NUL;
продвижение указателя назад, но не дальше позиции начала этой
строки. Если функция не знает где начало строки, началом строки
считается позиция по переданному указателю.</p>
<p>Любая часть константной строки (задаваемая указателем на
позицию внутри строки, включая финальный NUL) также является
константной строкой - от данной позиции до финального NUL.</p>
<p>Константная строка возникает в следующих случаях:</p>
<ol>
<li>Запись текстовой константы в кавычках во всех случаях кроме
присвоения начального значения массиву;</li>
<li>Явная конверсия указателя в строковый буфер (на элемент
строкового буфера) к типу указателя на константный символ.</li>
</ol>
<p>Например, в следующем случае происходит присвоение указателю
адреса константной строки:</p>
<pre><code>
const char* message = "Hello world!";
</code></pre>
<p>В следующем случае происходит <u>не</u> присвоение адреса
константной строки, а инициализация буфера:</p>
<pre><code>
const char message[] = "Hello world!";
</code></pre>
<p>В следующем случае константная строка передаётся в функцию:</p>
<pre><code>
printf("Hello world!\n");
</code></pre>
<p>Если формальный параметр функции - указатель на константный
символ, она принимает константную строку. Фактическим параметром
может быть как константная строка, так и указатель в строковый
буфер (далее об этом детальнее). Наоборот, формальный параметр -
указатель на неконстантный символ (например, <code>char*</code>)
означает передачу указателя в строковый буфер; конверсия в такой
указатель из указателя на константную строку должна вызвать
предупреждение компилятора или даже ошибку компиляции, потому что
потеря квалификатора константности указателя является ошибкой.</p>
<p><center><h3>Строковый буфер</h3></center></p>
<p>Строковый буфер является местом для формирования произвольной
строки. Он является массивом символов выбранного типа; определены
(должны быть известны коду, который работает с ним) границы
допустимой области. Методы задания границ допустимой области будут
обсуждаться далее. Функция работающая с буфером может делегировать
другой функции работу со всем буфером или его частью, сужая
границы буфера для других функций, но не расширять эти
границы.</p>
<p>Содержимое строкового буфера может быть произвольным по
усмотрению работающего с ним кода, но если какая-то часть буфера
передаётся куда-то как константная строка, то строка должна
завершиться символом NUL в пределах данного буфера (а не где-то
после него).</p>
<p>Передача константной строки туда где предполагается строковый
буфер является грубейшим нарушением. К сожалению, такие
неправильные шаблоны использования часты среди начинающих,
например:</p>
<pre><code>
char* p = "A";
strcat(p, "B");
</code></pre>
<p>В данном примере strcat() предполагает использование заведомо
достаточного строкового буфера (о проблемах такого предположения
см. ниже), в то время как указатель получил значение константной
строки. Правильный компилятор в правильном режиме использования
должен выдать предупреждение или даже ошибку на первом из этих
операторов.</p>
<p><center><h3>Использование константных строк</h3></center></p>
<p>Константные строки могут читаться целиком, по частям или
отдельными символами, например:</p>
<pre><code>
const char* s = "Do you see me?";
size_t len;
len = strlen(s);
</code></pre>
<p>Стандартная функция strlen() возвращает количество символов в
строке, не считая завершающего NUL.</p>
<pre><code>
const char* s = "Do you see me?";
const char* sp;
size_t len;
for (len = 0, sp = s; *sp != '\0'; ++sp)
++len;
</code></pre>
<p>В данном примере мы повторяем "вручную" (и, вероятно, менее
оптимально чем это делает стандартная библиотека) функцию
strlen().</p>
<pre><code>
const char*
basename(const char* path)
{
const char* rslash = strrchr(path, '/');
if (!rslash)
return path;
return rslash + 1;
}
</code></pre>
<p>В этом примере упрощённая реализация стандартной функции basename()
воспринимает входную строку как путь к файлу, в котором компоненты
разделены символами '/' ("слэш", "косая черта"), и возвращает
последний из них. Стандартная функция strrchr() возвращает адрес
самого правого вхождения символа (2-й аргумент) в константную
строку (1-й аргумент), или NULL, если нет такого символа в строке.
Это упрощённая версия, потому что от полной версии требуется
исключить из рассмотрения все финальные слэши. Полная версия
требует или сканирования по строке назад, или замены слэшей на
NUL; приведём одну из возможных реализаций полной версии
basename (но ещё без модификации строки), не оптимизированную, но
максимально понятную алгоритмически:</p>
<pre><code>
const char*
basename(const char* path)
{
const char* p = path + strlen(path);
// Пропуск финальных слэшей
while (p > path && p[-1] == '/')
--p;
// Если p[-1] допустим, то там не слэш. Если p == path, в строке
// были только слэши и вернуть мы ничего осмысленного не можем.
if (p == path)
return NULL;
// Отсчитывая от p[-1], сканируем до следующего слэша,
// если он есть. Если доходим до начала строки - путь из одного
// компонента и его надо вернуть неизменным.
--p;
while (p > path && p[-1] != '/')
--p;
return p;
}
</code></pre>
<p>Более сложные примеры будут в следующей главе, когда нам
разрешат использовать строковые буфера и тем самым модифицировать
содержимое строк.</p>
<p><center><h3>Работа со строковыми буферами</h3></center></p>
<p>Со строковыми буферами можно работать точно так же как с
константными строками, не меняя данные, или же меняя данные, тогда
спектр возможных действий резко расширяется. Модифицирующие
действия могут быть посимвольными, областями с началом и концом
(функции группы mem*, в каком-то смысле функции группы strl*) и
построчными (функции группы str*). Это деление не абсолютное;
функции некоторых из них перекрываются.</p>
<p>Мы рассмотрим действия с одной строкой и как их возможно
реализовать на nt-строках. Более-менее усреднённый базис действий
над строкой, допустимый в пределах одного буфера, следующий:</p>
<ul>
<li>Присвоение строке значения другой строки.</li>
<li>Присвоение строке значения из одного символа.</li>
<li>Дописывание в конец строки другой строки.</li>
<li>Замена символа в строке.</li>
<li>Усечение строки.</li>
<li>Дописывание в начало строки другой строки.</li>
<li>Замена части строки</li>
</ul>
<p>Мы будем приводить примеры на трёх комплектах функций
реализации: стандартному, но практически непригодному к
применению; функциям strlcpy, strlcat, нестандартным, но
обеспечивающим невыход за границы буфера; функциям ISO/IEC TR 24731 -
предложению ISO по защите от переполнений буферов.</p>
<p><h4>Присвоение строке значения другой строки (т.е. копирование строки):
</h4></p>
<p>Вариант strcpy:</p>
<pre><code>
char buffer[BUFSIZE];
strcpy(buffer, line);
</code></pre>
<p>Вариант strlcpy:</p>
<pre><code>
char buffer[BUFSIZE];
if (strlcpy(buffer, line, sizeof(buffer)) >= sizeof(buffer))
обработать_переполнение;
</code></pre>
<p>Вариант TR 24731:</p>
<pre><code>
char buffer[BUFSIZE];
strcpy_s(buffer, sizeof(buffer), line);
</code></pre>
<p>(Здесь не упоминалась strncpy(); ниже будет рассказано, почему.)</p>
<p>Рассмотрим эти примеры последовательно. strcpy() - самая старая
и самая диверсионная функция из рассмотренных. Чем диверсионная?
По сути она предполагает <b>неограниченный</b> целевой буфер,
который гарантированно будет достаточен для размещения любой
поданной строки. Что в этом плохо? Плохо то, что контроль
переполнения (который безусловно нужен, если Вы не хотите
превратить свою программу в полигон для хакеров или просто в
кривое глюкало) или просто отсутствует, или выносится отдельно от
кода работы с буфером куда-то далеко.</p>
<p>Часто кажется, что можно избавляться от переполнений следующей
логикой: "мы знаем, что модуль A вернёт строку не более 30
символов; B - не более 50; отведя таким образом 81 байт на буфер,
у нас не будет переполнения". Сколько проживёт такая логика до
первого изменения семантики, при котором длины увеличатся, и кто
сделает своевременную и полную реакцию в одном модуле на изменения
другого? Даже в пределах одного файла не всегда получается
уследить за изменениями, а если они расположены в глубоких
подкаталогах разных каталогов и модифицируются разными людьми или
даже разными отделами? Давно известно что этот подход
неработоспособен: работоспособен в конечном итоге только подход
когда модуль (неважно насколько широко он понимается) проверяет
корректность входных данных, включая - в существенной степени - и
данные из используемых им функций других модулей. Качественные
характеристики работы используемых средств могут быть постоянными
длительное время, но количественные значительно легче меняются, а
максимальная возможная длина строки - один из самых изменчивых
количественных признаков. Поэтому мы категорически возражаем
против любых вариантов контроля когда размер буфера не находится в
пределах доступности (как sizeof или как отдельный переданный
параметр) и когда он не применяется напрямую к операциям работы с
буфером.</p>
<p>Оставшиеся две функции различаются возвращаемым значением и
методом реакции на опознание переполнения. Исходная strcpy()
возвращает указатель на исходный буфер для добавления; strcpy_s()
повторяет это, в то время как strlcpy() возвращает пригодное для
немедленной проверки количество байт которое она хотела поместить
в буфер. Размер буфера передаётся в обе функции, но strlcpy()
требует проверочной оболочки вокруг, а strcpy_s() вызывает
constraint handler в случае переполнения. Таким образом strcpy_s()
больше подходит для быстрой модификации приложения с целью
исключения переполнений в нём и грубых действий в случае нарушения
целостности данных, в то время как strlcpy() с надлежащей
проверкой даёт возможность отреагировать на переполнение,
например, увеличением буфера или отказом работы на период до
восстановления.</p>
<p>В примерах выше не использовалась strncpy(). Эта функция не
совсем относится к строковым из-за специфики семантики: она
предназначается для заполнения строковых буферов фиксированного
размера, для которых не обязательно завершение нулём; такие буфера
активно используются в интерфейсах Unix (например, имя файла
классической файловой системы, имя пользователя или хоста в
структуре utmp). Кроме возможности заполнения без завершения
нулём, strncpy() согласно Posix и C99 обязана заполнить всё
оставшееся после строки место нулевыми символами (это делается для
того, чтобы содержимое буферов потом можно было сравнивать через
memcmp() или аналог с фиксированным размером); таким образом,
strncpy(dest,src,size) заполняет size байт независимо от реальной
длины строки src. Но, если использовать комбинацию из strncpy() с
последующей проверкой последнего байта буфера (если он не NUL, то
произошло переполнение) - это будет хоть и более затратным чем
strcpy_s() или strlcpy(), но достаточным средством копирования с
защитой от переполнения:</p>
<pre><code>
char buffer[BUFSIZE];
strncpy(buffer, line, sizeof(buffer));
if (buffer[sizeof(buffer)-1] != '\0')
обработать_переполнение;
</code></pre>
<p><h4>Присвоение строке значения из одного символа</h4> делается без
использования строковых функций:</p>
<pre><code>
buf[0] = c;
buf[1] = '\0';
</code></pre>
<p>Этот метод может быть расширен на любое количество символов;
проверка допустимости записи в буфер по указанному смещению
остаётся на программисте.</p>
<p><h4>Дописывание к концу строки другой строки
(конкатенация):</h4></p>
<p>Средствами TR 24731:</p>
<pre><code>
char buf[BUFSIZE];
if (strcat_s(buf, sizeof(buf), line) != 0)
обработать_переполнение;
</code></pre>
<p>Средствами strlcat:</p>
<pre><code>
char buf[BUFSIZE];
if (strlcat(buf, line, sizeof(buf)) >= sizeof(buf))
обработать_переполнение;
</code></pre>
<p>В обоих случаях отсутствием переполнения считается результат
когда вторая строка дописалась в буфер и осталось места на
завершение буфера нулевым символом. strlcat гарантирует
корректность nt-строки (наличие нулевого символа в буфере)
независимо от факта переполнения, а также отдачу желаемого объёма
буфера (но из второй строки считается не больше символов чем объём
буфера). strcat_s проверяет б<b>о</b>льшее количество условий
(например, если указатель второй строки NULL, то strlcat приведёт
к ошибке выполнения (SIGSEGV в Unix-системах, GPF в Windows), а
strcat_s отработает эту ситуацию выдачей ненулевого кода возврата
без нарушения выполнения программы.</p>
<p>Следует сделать <b>принципиальное</b> замечание об эффективности
добавления строк в
большой буфер. Так как все функции этой группы вынуждены вначале
проверять строку от начала буфера, при большом буфере и
последовательном добавлении строк зависимость времени работы от
объёма буфера становится квадратичной. Джоэл Спольский <a
href="http://russian.joelonsoftware.com/Articles/BacktoBasics.html">назвал
</a> этот подход "алгоритмом маляра Шлемиэля". Но если мы уже
добавили строку в буфер, то в процессе этого добавления знаем, где
она кончается, и можно добавлять с этого места? Исходя из этих
соображений появились функции stpcpy, stpncpy; как таковые функции
strcat, strncat, strlcat, strcat_s при наличии stpcpy и stpncpy
имеют смысл только при однократных добавлениях.</p>
<p>stpcpy() мы исключим из рассмотрения по причине отсутствия у
неё ограничений на размер буфера; stpncpy() в версии GNU libc
выглядит так:</p>
<pre><code>char *stpncpy(char *dest, const char *src, size_t n);</code></pre>
<p>К сожалению, она не терминирует буфер нулевым символом если
строка src имеет не менее n символов; можно воспользоваться
собственной функцией следующего вида:
<pre><code>
char *stpnlcpy(char *dest, const char *src, size_t n)
{
size_t r;
assert(n > 0);
r = strlcpy(dest, src, n);
if (r >= n) {
dest[n-1] = '\0';
return &dest[n-1];
}
return dest + r;
}
</code></pre>
<p>С такой функцией мы получаем буфер гарантированно содержащий
хотя бы один NUL (обратите на это внимание) и возвращаемое
значение - указатель на конец строки, то есть на символ NUL
ограничивающий её. Пример заполнения буфера конкатенацией
нескольких строк с помощью этой функции:</p>
<pre><code>
char buf[BUFSIZE];
char *buflim = buf + sizeof(buf);
char *pos = buf;
pos = stpnlcpy(pos, part1, buflim - pos);
pos = stpnlcpy(pos, part2, buflim - pos);
pos = stpnlcpy(pos, part3, buflim - pos);
if (pos >= buflim - 1)
обработать_переполнение;
</code></pre>
<p>(Оборотная сторона отсутствия прямой проверки: полностью
заполненный буфер, в последнем байте которого терминирующий NUL,
для нас эквивалентен переполнению - потому что сам факт
переполнения нигде не сообщается. Это спорное решение; можно было
бы заполнять до последней позиции, и переполнением считать
ситуацию когда pos == buflim, а NUL в буфере нет, потому что ему
не хватило места. Этот вариант тоже допустим, но его расписывать
не будем.)</p>
<p>Обратите внимание на приём с buflim. Чтобы вычислить сколько
ещё есть места в буфере, зная начало буфера и его размер, надо
выполнить арифметические действия: вычесть из размера буфера
разность между текущей позицией и началом; если bufsize-(pos-buf)
переписать в виде (buf+bufsize)-pos, buf+bufsize может быть
вычислено только один раз, так как оно не меняется всё время
работы с буфером. Кроме того, психологически проще представить
себе фиксированный ограничитель, за который нельзя выходить, чем
пересчитывать каждый раз сколько можно максимально заполнить.</p>
<p>Продвигая эту идею дальше, можно улучшить интерфейс функции
двумя модификациями. Во-первых, перенести диагностику переполнения
буфера в код возврата, а указатель на текущую позицию сам
передавать по указателю чтобы его модифицировать в функции.
Во-вторых, передавать не вычисляемое по ограничителю количество
свободных байт в буфере, а сам ограничитель. В результате
получится следующее:</p>
<pre><code>
int lbcat(char **curpos, char *buflim, const char *src)
{
size_t r, a;
if (curpos >= buflim - 1)
return 1; // даже не проверяем входную строку
a = buflim - *curpos;
r = strlcpy(*curpos, src, a);
if (r >= a) {
*curpos = buflim - 1;
**curpos = '\0';
return 1;
}
*curpos += r;
return 0;
}
</code></pre>
<p>Здесь мы снова получаем явный признак переполнения;
конкатенация строк может быть написана так:</p>
<pre><code>
char buf[BUFSIZE];
char *buflim = buf + sizeof(buf);
char *p = pos;
if (lbcat(&pos, buflim, part1) ||
lbcat(&pos, buflim, part2) ||
lbcat(&pos, buflim, part3))
{
обработать_переполнение;
}
</code></pre>
<p>Здесь мы намеренно ушли от "простых" strcat() и strncat().
Причина этому - их диверсионность, причём если strcat() тут ничем
не отличается от strcpy(), то strncat() даёт <b>ложную</b>
защищённость ограничением количества добавляемых символов. Почему
ложную? Потому что если мы знаем только размер буфера, нам это
ничего не даст. А если мы знаем сколько осталось в буфере, то мы
тем самым знаем текущую позицию NUL (ограничителя строки) и нам
никакие функции конкатенации не нужны в принципе, нужно только
копирование. Подробнее см. выше про подход с stpcpy() и
аналогами.</p>
<p>Наконец, если программисту доступно stdio (да, это бывает не во
всех случаях - например, под Windows может вводиться
принципиальное ограничение использовать только WinAPI; может быть
вариант freestanding environment с урезанным или отсутствующим
stdio для встроенных приложений, для ядер ОС, загрузчиков и т.д.)
- конкатенации подобного рода замечательно упрощаются через
snprintf():</p>
<pre><code>
char buf[BUFSIZE];
if (snprintf(buf, sizeof(buf), "%s%s%s", part1, part2, part3) >= sizeof(buf))
обработать_переполнение;
</code></pre>
<p>И опять-таки - snprintf(), а не sprintf()! Потому что бороться
с переполнениями надо не учётом того что происходит в коде за
тридевять земель, а с тем, что известно здесь и сейчас.</p>
<p><h4>Замена символа в строке</h4></p>
<p>Это делается тривиальным присвоением символа по указателю или
как элементу массива:</p>
<pre><code>
buf[4] = 'x';
*(buf+16) = 'y';
</code></pre>
<p>Почти к этому же классу тривиальности:) относится <b>замена
части строки на другую строку такой же длины</b>. Главное здесь -
не записать лишний NUL после добавленного. Поэтому такая замена
будет выглядеть (считая, что мы заменой не пересекаем границу
буфера):</p>
<pre><code>
memcpy(где_заменять, чем_заменять, длина_заменяемого);
</code></pre>
<p>(я даже не расписываю определения переменных - они должны быть
понятны и так). Почему memcpy? Потому что длина данных уже
известна, подсчёта длины которой занимаются функции группы str* не
требуется; и не требуется добавления NUL в конце, он и так есть
где надо. Более общий случай произвольной замены будет рассмотрен
далее.</p>
<p><h4>Усечение строки</h4></p>
<p>Для усечения строки надо записать NUL в позицию, следующую за
концом оставляемых данных. Код не приводим за тривиальностью:)</p>
<p>Замена части строки</p>
<p>Самый общий случай замены части строки (аналогичный функциям
substr() в perl или ряде версий Basic) задаётся входной строкой
(хранящейся в буфере который мы и будем изменять), указанием
заменяемой части (начало, длина) и заменяющей строкой. В общем
случае это включает в себя все возможные основные типы модификации
строки, включая присвоение, конкатенацию, усечение и не только.
Можно использовать несколько разных стилей реализации, например с
жёстким контролем (длина больше чем доступно считается ошибкой) и
с мягким контролем. ****</p>
<pre><code>
int lbreplace(char *buf, char *buflim, int roff, int rlen, const char *snew)
{
char *p;
// Исключить случай переполненного буфера
p = memchr(buf, 0, buflim - buf);
if (p == NULL)
return 1;
curlen = p - buf;
if (roff > curlen)
roff = curlen;
if (roff < 0) {
roff += curlen;
if (roff < curlen)
roff = 0;
}
}
</code></pre>
<hr>
<p>(C) 2005 Valentin Nechayev. All rights reserved.</p>
<br>
Разрешается полное или частичное копирование, цитирование.
При полном или частичном копировании ссылка на оригинал
обязательна.</p>
<p>$Id: cstr.html,v 1.2 2006/04/05 20:39:42 netch Exp $</p>
</body>
</html>
<!-- vim:tw=66:
--!>