Какие функции управления строкой следует использовать?
В моей среде Windows/Visual C существует множество альтернатив для выполнения одних и тех же основных задач манипулирования строкой.
Например, для выполнения строковой копии я мог бы использовать:
-
strcpy
, стандартная библиотечная функция ANSI C (CRT)
-
lstrcpy
, версия, включенная в kernel32.dll
-
strcpy
, из библиотеки утилиты Shell Lightweight Utility
-
StringCchCopy
/StringCbCopy
, из библиотеки "безопасной строки"
-
strcpy_s
, улучшенная версия CRT с улучшенной безопасностью
Хотя я понимаю, что все эти альтернативы имеют историческую причину, могу ли я просто выбрать согласованный набор функций для нового кода? И какой? Или я должен выбрать наиболее подходящую функцию в каждом случае?
Ответы
Ответ 1
Прежде всего, рассмотрите плюсы и минусы каждого набора функций:
Стандартная библиотечная функция ANSI C (CRT)
Функции, такие как strcpy
, являются единственным выбором, если вы разрабатываете портативный C-код. Даже в проекте только для Windows, может быть, разумно иметь разделение портативного и зависимого от ОС кода.
Эти функции часто выполняют оптимизацию уровня сборки и, следовательно, очень быстрые.
Есть некоторые недостатки:
- у них много ограничений, и поэтому часто вам приходится вызывать функции из других библиотек или предоставлять свои собственные версии.
- есть некоторые архаизмы, такие как печально известный
strncpy
Строковые функции Kernel32
Функции типа lstrcpy
экспортируются ядром32 и должны использоваться только при попытке избежать любой зависимости от ЭЛТ. Возможно, вы захотите сделать это по двум причинам:
- избежать использования CRT для сверхлегкого исполняемого файла (необычно в эти дни, но не 10 лет назад!)
- избежать проблем с инициализацией (если вы запускаете поток с
CreateThread
вместо _beginthread
).
Кроме того, функция kernel32 может быть более оптимизирована, чтобы версия CRT: когда ваш исполняемый файл будет работать в Windows 9, оптимизированный для Core i13, kernel32 может использовать версию, оптимизированную для сборки.
Полезные функции командной консоли Shell
Здесь справедливы те же соображения, что и для функций kernel32, с добавленным значением некоторых более сложных функций. Однако я сомневаюсь, что они активно поддерживаются, и я просто пропустил их.
Функция StrSafe
Функции StringCchCopy
/StringCbCopy
обычно являются моим личным выбором: они очень хорошо разработаны, мощны и удивительно быстры (я также помню технический документ, сравнивающий эффективность этих функций с эквивалентами CRT).
Функции, улучшающие безопасность CRT
Эти функции имеют несомненную выгоду от того, что они очень похожи на эквиваленты ANSI C, поэтому перенос устаревшего кода - это кусок пирога. Мне особенно нравится версия на основе шаблонов (конечно, доступна только при компиляции как С++). Я очень надеюсь, что они в конечном итоге будут стандартизированы. К сожалению, у них есть ряд недостатков:
- хотя предлагаемый стандарт, они были в основном отвергнуты сообществом не-Windows (возможно, только потому, что они пришли из Microsoft)
- при сбое они не просто возвращают код ошибки, но выполняют недопустимый обработчик параметров
Выводы
Хотя мой личный фаворит для разработки Windows - это библиотека StrSafe, я советую использовать функции ANSI C, когда это возможно, поскольку переносимый код всегда хорош.
В реальной жизни я разработал персонализированную переносимую библиотеку с прототипами, подобными функциям расширенной безопасности CRT (включая мощную технологию на основе шаблонов), которая основывается на библиотеке StrSafe в Windows и на функциях ANSI C на других платформы.
Ответ 2
Мои личные предпочтения как для новых, так и для существующих проектов - это версии StringCchCopy/StringCbCopy
из безопасной библиотеки строк. Я считаю, что эти функции будут в целом очень последовательными и гибкими. И они были разработаны из группы с учетом безопасности/безопасности.
Ответ 3
Я бы ответил на этот вопрос несколько иначе. Вы хотите иметь переносимый код или нет? Если вы хотите быть переносным, вы не можете полагаться на что-либо еще, кроме strcpy
, strncpy
или стандартных функций обработки символов "строка".
Затем, если ваш код просто должен запускаться под Windows, вы можете использовать варианты "безопасной строки".
Если вы хотите быть портативным и по-прежнему хотите иметь дополнительную безопасность, вы должны проверить кросс-платформенные библиотеки, например, например,
glib или
libapr
или другие "безопасные библиотеки строк", например, например:
SafeStrLibrary
Ответ 4
Я бы предложил использовать функции из стандартной библиотеки или функции из межплатформенных библиотек.
Ответ 5
Я бы придерживался одного, я бы выбрал тот, который находится в самой полезной библиотеке, если вам нужно будет использовать его больше, и я бы держался подальше от kernel32.dll, поскольку это только окна.
Но это только советы, это субъективный вопрос.
Ответ 6
Среди этих вариантов я просто использовал бы strcpy
. По крайней мере, strcpy_s
и lstrcpy
являются крутыми, которые никогда не должны использоваться. Возможно, стоит исследовать эти автономно написанные библиотечные функции, но я бы не решался бросить нестандартный библиотечный код в качестве панацеи для безопасности строк.
Если вы используете strcpy
, вы должны быть уверены, что ваша строка подходит в целевом буфере. Если вы просто выделили его размером не менее strlen(source)+1
, вы в порядке, пока исходная строка не будет одновременно изменяться другим потоком. В противном случае вам нужно проверить, подходит ли он в буфере. Вы можете использовать такие интерфейсы, как snprintf
или strlcpy
(нестандартная функция BSD, но легко копировать реализацию), которая усекает строки, которые не подходят в вашем целевом буфере, но тогда вам действительно нужно оценить, может ли усечение строк привести к уязвимости в себе. Я думаю, что гораздо лучший подход при проверке того, подходит ли исходная строка, - это сделать новое распределение или вернуть статус ошибки, а не выполнять слепое усечение.
Если вы будете делать много конкатенации/сборки строк, вы действительно должны написать весь свой код, чтобы управлять длиной и текущей позицией по мере того, как вы идете. Вместо:
strcpy(out, str1);
strcat(out, str2);
strcat(out, str3);
...
Вы должны делать что-то вроде:
size_t l, n = outsize;
char *s = out;
l = strlen(str1);
if (l>=outsize) goto error;
strcpy(s, str1);
s += l;
n -= l;
l = strlen(str2);
if (l>=outsize) goto error;
strcpy(s, str2);
s += l;
n -= l;
...
В качестве альтернативы вы могли бы избежать изменения указателя, сохранив текущий индекс i
типа size_t
и используя out+i
, или вы могли бы избежать использования переменных размера, указав указатель на конец буфера и сделав такие вещи, как if (l>=end-s) goto error;
.
Обратите внимание, что какой бы подход вы ни выбрали, избыточность может быть сокращена путем написания собственных (простых) функций, которые принимают указатели на переменную position/size и вызывают стандартную библиотеку, например, что-то вроде:
if (!my_strcpy(&s, &n, str1)) goto error;
Избегание strcat
также имеет преимущества в производительности; см. алгоритм Schlemiel the Painter.
Наконец, вы должны заметить, что хорошие 75% людей, выполняющих операции копирования и сборки строк на C, совершенно бесполезны. Моя теория заключается в том, что люди, которые делают это, исходят из фонов в языках script, где объединение строк - это то, что вы делаете все время, но в C это не так часто полезно. Во многих случаях вы можете обойтись без копирования копий вообще, используя вместо этого оригинальные копии, и получите гораздо лучшую производительность и более простой код одновременно. Мне вспоминается недавний вопрос SO, в котором OP использовал regexec
для соответствия регулярному выражению, а затем копировал результат только для его печати, например:
char *tmp = malloc(match.end-match.start+1);
memcpy(tmp, src+match.start, match.end-match.start);
tmp[match.end-match.start] = 0;
printf("%s\n", tmp);
free(tmp);
То же самое можно сделать с помощью:
printf("%.*s\m", match.end-match.start, src+match.start);
Без распределения, без очистки, без ошибок (исходный код разбился, если malloc
не удалось).