Какие функции управления строкой следует использовать?

В моей среде 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 не удалось).