Я читал на Переполнении стека, что некоторые функции C являются "устаревшими", или "должен избежаться". Можно ли дать мне некоторые примеры этого вида функции и причины почему?
Какие альтернативы тем функциям существуют?
Мы можем использовать их безопасно - какие-либо хорошие методы?
Устаревшие функции
Небезопасные
Прекрасным примером такой функции является gets ( ) , поскольку невозможно определить размер целевого буфера. Следовательно, любая программа, считывающая ввод с помощью gets (), имеет уязвимость переполнения буфера . По аналогичным причинам следует использовать strncpy () вместо strcpy () и strncat () вместо strcat () .
Еще несколько примеры включают tmpfile () и функция mktemp () из-за потенциальных проблем безопасности при перезаписи временных файлов , которые заменяются более безопасной функцией mkstemp () .
Не реентерабельность
К другим примерам относятся gethostbyaddr () и gethostbyname () , которые не реентерабельны (и, следовательно, не гарантируют безопасность потоков) и были заменено повторно используемыми getaddrinfo () и freeaddrinfo () .
Возможно, вы заметили здесь закономерность ...либо отсутствие безопасности (возможно, из-за того, что в подпись не включено достаточно информации для ее безопасной реализации), либо отсутствие повторного входа являются распространенными источниками устаревания.
Устаревшие, непереносимые
Некоторые другие функции просто устаревают, потому что они дублируют функциональные возможности и не так переносимы, как другие варианты. Например, bzero () устарел в пользу memset () .
Безопасность потоков и повторный вход
В своем сообщении вы спрашивали о безопасности потоков и повторном входе. Есть небольшая разница. Функция является реентерабельной, если она не использует какое-либо разделяемое изменяемое состояние. Так, например, если вся необходимая информация передается в функцию, и любые необходимые буферы также передаются в функцию (а не совместно используются всеми вызовами функции), то она является реентерабельной. Это означает, что разные потоки, используя независимые параметры, не рискуют случайно разделить состояние. Повторный вход - более надежная гарантия, чем безопасность потоков. Функция является потокобезопасной, если она может использоваться несколькими потоками одновременно. Функция является потокобезопасной, если:
В общем, в Single UNIX Specification и IEEE 1003.1 (то есть «POSIX»), любая функция, которая не гарантирована для повторного входа, не гарантированно будет потокобезопасной.Другими словами, в многопоточных приложениях можно переносимо использовать только функции, которые гарантированно реентерабельны (без внешней блокировки). Однако это не означает, что реализации этих стандартов не могут сделать нереентерабельную функцию потокобезопасной. Например, Linux часто добавляет синхронизацию к функциям без повторного входа, чтобы добавить гарантию (помимо единой спецификации UNIX) безопасности потоков.
Строки (и буферы памяти в целом)
Вы также спросили, есть ли какой-то фундаментальный недостаток со строками / массивами. Кто-то может возразить, что это так, но я бы сказал, что нет, в языке нет фундаментального недостатка. C и C ++ требуют, чтобы вы передавали длину / емкость массива отдельно (это не свойство ".length", как в некоторых других языках). Само по себе это не недостаток. Любой разработчик C и C ++ может написать правильный код, просто передав длину в качестве параметра там, где это необходимо. Проблема в том, что несколько API-интерфейсов, которым требовалась эта информация, не смогли указать ее в качестве параметра. Или предположил, что будет использоваться некоторая константа MAX_BUFFER_SIZE. Такие API-интерфейсы устарели и заменены альтернативными API-интерфейсами, которые позволяют указывать размеры массива / буфера / строки.
Scanf (в ответ на ваш последний вопрос)
Лично я использую библиотеку iostreams C ++ (std :: cin, std :: cout, операторы << и >>, std :: getline, std :: istringstream, std :: ostringstream и т. д.), поэтому я обычно не занимаюсь этим.Если бы мне пришлось использовать чистый C, я бы лично просто использовал fgetc () или getchar () в сочетании с strtol () , strtoul () и т. д. и разбираю вещи вручную, так как я не большой поклонник varargs или форматных строк. Тем не менее, насколько мне известно, нет проблем с [f] scanf () , [f] printf () и т. Д., Если вы создаете формат струны сами,вы никогда не передаете произвольные строки формата и не разрешаете использовать вводимые пользователем данные в качестве строк форматирования, и вы используете макросы форматирования, определенные в
Вероятно, стоит еще раз добавить, что strncpy ()
не является универсальной заменой для strcpy ()
, о которой можно судить по названию. Он разработан для полей фиксированной длины, которым не нужен нулевой терминатор (изначально он был разработан для использования с записями каталога UNIX, но может быть полезен для таких вещей, как поля ключа шифрования).
Однако легко использовать strncat ()
в качестве замены для strcpy ()
:
if (dest_size > 0)
{
dest[0] = '\0';
strncat(dest, source, dest_size - 1);
}
(Тест if
, очевидно, может быть в общем случае, когда вы знаете, что dest_size
определенно ненулевой).
Не забывайте о sprintf - это причина многих проблем. Это правда, потому что альтернатива snprintf иногда имеет разные реализации, которые могут сделать ваш код непереносимым.
В случае 1 (linux) возвращаемое значение - это объем данных, необходимый для хранения всего буфера (если он меньше размера данного буфера, вывод был обрезан)
В случае 2 (окна) возвращаемое значение - отрицательное число, если вывод усечен.
Как правило, вам следует избегать функций, которые не:
безопасны при переполнении буфера (многие функции уже упомянуты здесь)
потокобезопасны / не реентерабельны (например, strtok)
В руководстве каждого функции, вы должны искать такие ключевые слова, как: safe, sync, async, thread, buffer, bugs
И снова люди повторяют, как мантру, смехотворное утверждение о том, что "n" версия функций str - безопасные версии.
Если бы они были предназначены для этого, они всегда завершали бы строки нулевым символом.
"n" версий функций были написаны для использования с полями фиксированной длины (такими как записи каталогов в ранних файловых системах), где нулевой терминатор требуется только в том случае, если строка не заполняет поле. Это также причина того, почему функции имеют странные побочные эффекты, которые бессмысленно неэффективны, если использовать их просто как замену - например, возьмите strncpy ():
Если массив, на который указывает s2, является строкой , которая короче чем n байтов, нулевых байтов добавляются к копии в массиве, на который указывает s1, пока не будет записано n байтов.
Поскольку размер буферов, выделяемых для обработки имен файлов, обычно составляет 4 Кбайта, это может привести к значительному снижению производительности.
Если вы хотите «предположительно» безопасные версии, получите - или напишите свои собственные - процедуры strl (strlcpy, strlcat и т. Д.), Которые всегда завершают строки nul и не имеют побочных эффектов.Однако обратите внимание, что это не совсем безопасно, поскольку они могут тихо обрезать строку - это редко лучший способ действий в любой реальной программе. Бывают случаи, когда это нормально, но есть также много обстоятельств, когда это может привести к катастрофическим результатам (например, распечатка медицинских рецептов).
Избегайте
strtok
для многопоточных программ, поскольку это не многопоточные программы. получает
, поскольку это может вызвать переполнение буфера atoi не является потокобезопасным. Вместо этого я использую strtol по рекомендации на странице руководства.
Некоторые люди утверждают, что следует избегать strcpy
и strcat
в пользу strncpy
и strncat
. На мой взгляд, это несколько субъективно.
Их определенно следует избегать при работе с пользовательским вводом - здесь нет сомнений.
В коде «далеко» от пользователя, когда вы просто знаете , что буферы достаточно длинные, strcpy
и strcat
могут быть немного более эффективными, потому что вычисление n
для передачи их кузенам может быть излишним.
Практически любая функция, которая имеет дело со строками, завершающимися NUL, потенциально небезопасна. Если вы получаете данные из внешнего мира и манипулируете ими с помощью функций str * (), вы настраиваетесь на катастрофу
Также ознакомьтесь со списком Microsoft запрещенных API . Это API-интерфейсы (в том числе многие из них, уже перечисленные здесь), которые запрещены для использования в коде Microsoft, поскольку они часто используются не по назначению и приводят к проблемам с безопасностью.
Вы можете не согласиться со всеми из них, но все они заслуживают внимания. Они добавляют API в список, когда его неправильное использование привело к ряду ошибок безопасности.
Очень трудно безопасно использовать scanf
. Правильное использование scanf
позволяет избежать переполнения буфера, но вы все еще уязвимы к неопределенному поведению при чтении чисел, которые не вписываются в запрашиваемый тип. В большинстве случаев fgets
с последующим самоанализом (используя sscanf
, strchr
и т.д.) является лучшим вариантом.
Но я бы не сказал "избегайте scanf
все время". scanf
имеет свое применение. В качестве примера, допустим, вы хотите прочитать пользовательский ввод в массиве char
длиной 10 байт. Вы хотите удалить идущую следом новую строку, если она есть. Если пользователь вводит более 9 символов до новой строки, вы хотите сохранить первые 9 символов в буфере и отбросить все до следующей новой строки. Вы можете сделать:
char buf[10];
scanf("%9[^\n]%*[^\n]", buf));
getchar();
Когда вы привыкнете к этой идиоме, она будет короче и в некотором смысле чище, чем:
char buf[10];
if (fgets(buf, sizeof buf, stdin) != NULL) {
char *nl;
if ((nl = strrchr(buf, '\n')) == NULL) {
int c;
while ((c = getchar()) != EOF && c != '\n') {
;
}
} else {
*nl = 0;
}
}
Несколько ответов здесь предлагают использовать strncat()
вместо strcat()
; я бы предложил избегать strncat()
(и strncpy()
). У него есть проблемы, которые затрудняют его правильное использование и приводят к ошибкам:
strncat()
связан (но не совсем точно - см. 3-й пункт) с максимальным количеством символов, которые могут быть скопированы в место назначения, а не с размером буфера назначения. Это делает strncat()
более сложной в использовании, чем следовало бы, особенно если несколько элементов будут конкатенированы в место назначения. s1
, равно strlen(s1)+n+1
" для вызова, который выглядит как strncat( s1, s2, n)
strncpy()
также имеет проблему, которая может привести к ошибкам, если вы попытаетесь использовать его интуитивно понятным способом - он не гарантирует, что место назначения имеет нулевое завершение. Чтобы гарантировать это, вы должны убедиться, что вы специально обрабатываете этот угловой случай, бросая '\0'
в последнее место буфера самостоятельно (по крайней мере, в некоторых ситуациях).
Я бы предложил использовать что-то вроде strlcat()
и strlcpy()
OpenBSD (хотя я знаю, что некоторым людям не нравятся эти функции; я считаю, что их гораздо проще безопасно использовать, чем strncat()
/strncpy()
).
Вот немного из того, что Тодд Миллер и Тео де Раадт могли сказать о проблемах с strncat()
и strncpy()
:
Существует несколько проблем, возникающих при использовании
strncpy()
иstrncat()
в качестве безопасных версийstrcpy()
иstrcat()
. Обе функции работают с NUL-терминацией и параметром длины разными и неинтуитивными способами, которые сбивают с толку даже опытных программистов. Они также не дают простого способа определить, когда происходит усечение. ... Из всех этих проблем путаница, вызванная параметрами длины, и связанная с ними проблема NUL-терминации являются наиболее важными. Когда мы проверяли дерево исходников OpenBSD на наличие потенциальных дыр в безопасности, мы обнаружили массовое злоупотреблениеstrncpy()
иstrncat()
. Хотя не все из них привели к появлению эксплуатируемых дыр в безопасности, они ясно показали, что правила использованияstrncpy()
иstrncat()
в безопасных строковых операциях понимаются очень плохо.
Аудит безопасности OpenBSD обнаружил, что ошибки с этими функциями были "безудержными". В отличие от gets()
, эти функции можно использовать безопасно, но на практике возникает множество проблем из-за запутанного, неинтуитивного и сложного для правильного использования интерфейса. Я знаю, что компания Microsoft также провела анализ (хотя я не знаю, сколько данных они опубликовали), и в результате запретила (или, по крайней мере, очень настоятельно не рекомендовала - "запрет" может не быть абсолютным) использование strncat()
и strncpy()
(среди прочих функций).
Некоторые ссылки с дополнительной информацией: