Я читал в Stack Overflow, что некоторые функции C «устарели» или «их следует избегать». Не могли бы вы привести мне несколько примеров такой функции и причину почему?
Какие существуют альтернативы этим функциям?
Можем ли мы использовать их безопасно - какие хорошие практики?
c
standard-library
obsolete
Андрей Чобану
источник
источник
strncpy()
в качестве общей заменыstrcpy()
, и я бы никогда не стал его использовать,strncat()
потому что у него самый неинтуитивный интерфейс, который только можно вообразить - ВЫ знаете, что указывает параметр длины?Ответы:
Устаревшие функции
небезопасны
Прекрасным примером такой функции является gets () , потому что нет способа определить размер целевого буфера. Следовательно, любая программа, которая считывает ввод с помощью gets (), имеет уязвимость переполнения буфера . По аналогичным причинам следует использовать strncpy () вместо strcpy () и strncat () вместо strcat () .
Еще несколько примеров включают функции tmpfile () и mktemp () из-за потенциальных проблем безопасности с перезаписью временных файлов, которые заменяются более безопасной функцией mkstemp () .
Без повторного входа
Другие примеры включают gethostbyaddr () и gethostbyname (), которые не реентерабельны (и, следовательно, не гарантируют безопасность потоков), и были заменены реентерабельными getaddrinfo () и freeaddrinfo () .
Вы можете заметить здесь шаблон ... либо отсутствие безопасности (возможно, из-за того, что в сигнатуре не было включено достаточно информации для ее безопасной реализации), либо отсутствие повторного входа являются распространенными источниками устаревания.
Устаревшие, непереносимые
Некоторые другие функции просто устаревают, потому что они дублируют функциональные возможности и не так переносимы, как другие варианты. Например, bzero () устарел в пользу memset () .
Безопасность потоков и повторный вход В своем сообщении
вы спрашивали о безопасности потоков и повторном входе. Есть небольшая разница. Функция является реентерабельной, если она не использует какое-либо разделяемое изменяемое состояние. Так, например, если вся необходимая информация передается в функцию, и любые необходимые буферы также передаются в функцию (а не совместно используются всеми вызовами функции), то она является реентерабельной. Это означает, что разные потоки, используя независимые параметры, не рискуют случайно разделить состояние. Повторный вход - более надежная гарантия, чем безопасность потоков. Функция является потокобезопасной, если она может использоваться несколькими потоками одновременно. Функция является потокобезопасной, если:
Как правило, в Единой спецификации UNIX и 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 () нет.и т. д., пока вы сами создаете строки формата, вы никогда не передаете произвольные строки форматирования и не разрешаете использовать вводимые пользователем данные в качестве строк форматирования, а при необходимости используете макросы форматирования, определенные в <inttypes.h> . (Обратите внимание, что snprintf () следует использовать вместо sprintf () , но это связано с невозможностью указать размер целевого буфера, а не с использованием строк формата). Я также должен отметить, что в C ++ boost :: format обеспечивает форматирование, подобное printf, без varargs.
источник
strncpy
как правило, также следует избегать. Он не делает того, что думает большинство программистов. Он не гарантирует завершения (что приводит к переполнению буфера) и заполняет более короткие строки (возможно, в некоторых случаях снижая производительность).strncpy()
ни другое неstrncat()
является разумной заменой вариантам без n.И снова люди повторяют, как мантру, нелепое утверждение, что "n" версия функций str является безопасной версией.
Если бы это было то, для чего они были предназначены, они всегда завершали бы строки нулевым символом.
«N» версий функций были написаны для использования с полями фиксированной длины (такими как записи каталогов в ранних файловых системах), где символ конца nul требуется только в том случае, если строка не заполняет поле. Это также причина того, что функции имеют странные побочные эффекты, которые бессмысленно неэффективны, если использовать их просто как замену - например, возьмем strncpy ():
Поскольку размер буферов, выделенных для обработки имен файлов, обычно составляет 4 КБ, это может привести к значительному снижению производительности.
Если вам нужны «предположительно безопасные» версии, то получите - или напишите свои собственные - подпрограммы strl (strlcpy, strlcat и т.д.), которые всегда завершают строки nul и не имеют побочных эффектов. Обратите внимание, что это не совсем безопасно, поскольку они могут тихо обрезать строку - это редко лучший способ действий в любой реальной программе. Есть случаи, когда это нормально, но есть также много обстоятельств, когда это может привести к катастрофическим результатам (например, распечатка медицинских рецептов).
источник
strncpy()
, но ошибаетесьstrncat()
.strncat()
не был разработан для использования с полями фиксированной длины - на самом деле он был разработан какstrcat()
ограничивающий количество конкатенированных символов. Это довольно легко использовать как «безопасныйstrcat()
», отслеживая пространство, оставшееся в буфере при выполнении нескольких конкатенаций, и еще проще использовать его как «безопасныйstrcpy()
» (установив для первого символа целевого буфера значение'\0'
до называя это).strncat()
всегда завершает строку назначения и не записывает лишние'\0'
s.strncat()
адрес назначения не всегда завершается нулевым завершением, и что он был разработан для использования с полями фиксированной длины, что неверно.strncat()
будет работать правильно независимо от длины исходной строки, аstrcat()
не будет. Проблемаstrlcat()
здесь в том, что это не стандартная функция C.Несколько ответов здесь предлагают использовать
strncat()
overstrcat()
; Я бы посоветовал избегатьstrncat()
(иstrncpy()
). У него есть проблемы, которые затрудняют правильное использование и приводят к ошибкам:strncat()
связан с (но не совсем точно - см. 3-й пункт) максимальным количеством символов, которые можно скопировать в место назначения, а не с размером буфера назначения. Это делает использованиеstrncat()
более сложным, чем должно быть, особенно если несколько элементов будут объединены в место назначения.s1
-strlen(s1)+n+1
» для вызова, который выглядит какstrncat( s1, s2, n)
strncpy()
также есть проблема, которая может привести к ошибкам, которые вы пытаетесь использовать интуитивно понятным способом - это не гарантирует, что место назначения имеет нулевое завершение. Чтобы убедиться, что вы специально обрабатываете этот угловой случай,'\0'
самостоятельно помещая a в последнее место буфера (по крайней мере, в определенных ситуациях).Я бы посоветовал использовать что-то вроде OpenBSD
strlcat()
иstrlcpy()
(хотя я знаю, что некоторые люди не любят эти функции; я считаю, что их гораздо проще использовать безопасно, чемstrncat()
/strncpy()
).Вот немного того, что Тодд Миллер и Тео де Раадт сказали о проблемах с
strncat()
иstrncpy()
:Аудит безопасности OpenBSD обнаружил, что ошибки с этими функциями были "безудержными". В отличие от
gets()
этих функций можно безопасно использовать, но на практике возникает много проблем, потому что интерфейс запутанный, неинтуитивный и сложный для правильного использования. Я знаю, что Microsoft также провела анализ (хотя я не знаю, какую часть своих данных они могли опубликовать), и в результате запретили (или, по крайней мере, очень сильно обескуражили - «запрет» не может быть абсолютным) использованиеstrncat()
иstrncpy()
(среди других функций).Некоторые ссылки с дополнительной информацией:
источник
memmove()
. (Ну, вы можете использоватьmemcpy()
в обычном случае, когда струны независимы.)strncat()
всегда заканчивается строка назначения.strncat()
. Однако он веренstrncpy()
, и у него есть другие проблемы.strncat()
является разумной заменойstrcat()
, ноstrncpy()
не является разумной заменойstrcpy()
.strncat()
не всегда заканчивается нулём. Я немного запутал поведениеstrncat()
иstrncpy()
(еще одна причина, по которой их следует избегать - у них есть имена, подразумевающие схожее поведение, но на самом деле они ведут себя по-разному в важных аспектах ...). Я изменил свой ответ, чтобы исправить это, а также добавить дополнительную информацию.char str[N] = ""; strncat(str, "long string", sizeof(str));
это переполнение буфера, если N недостаточно велик.strncat()
Функция слишком легко неправильное использование; его не следует использовать. Если вы можете использоватьstrncat()
безопасно, вы могли бы использоватьmemmove()
илиmemcpy()
вместо него (и это было бы более эффективно).Стандартные библиотечные функции, которые никогда не следует использовать:
setjmp.h
setjmp()
. Вместе с темlongjmp()
, эти функции широко признаны невероятно опасными в использовании: они приводят к программированию спагетти, они имеют множество форм неопределенного поведения, они могут вызывать непреднамеренные побочные эффекты в программной среде, такие как влияние на значения, хранящиеся в стеке. Ссылки: MISRA-C: 2012, правило 21.4, CERT C MSC22-C .longjmp()
. Смотритеsetjmp()
.stdio.h
gets()
. Функция была удалена из языка C (согласно C11), так как она была небезопасной по замыслу. Функция уже была помечена как устаревшая в C99.fgets()
Вместо этого используйте . Ссылки: ISO 9899: 2011 K.3.5.4.1, также см. Примечание 404.stdlib.h
atoi()
семейство функций. Они не обрабатывают ошибки, но вызывают неопределенное поведение всякий раз, когда возникают ошибки. Совершенно лишние функции, которые можно заменитьstrtol()
семейством функций. Ссылки: MISRA-C: 2012, правило 21.7.string.h
strncat()
. Имеет неудобный интерфейс, которым часто злоупотребляют. В основном это лишняя функция. Также см. Примечания кstrncpy()
.strncpy()
. Эта функция никогда не предназначалась для создания более безопасной версииstrcpy()
. Его единственной целью всегда была обработка древнего строкового формата в системах Unix, и то, что он был включен в стандартную библиотеку, является известной ошибкой. Эта функция опасна, потому что она может оставить строку без завершения нулем, а программисты, как известно, часто используют ее неправильно. Ссылки: Почему strlcpy и strlcat считаются небезопасными? .Стандартные библиотечные функции, которые следует использовать с осторожностью:
assert.h
assert()
. Поставляется с накладными расходами и обычно не должен использоваться в производственном коде. Лучше использовать обработчик ошибок для конкретного приложения, который отображает ошибки, но не обязательно закрывает всю программу.signal.h
signal()
. Ссылки: MISRA-C: 2012, правило 21.5, CERT C SIG32-C .stdarg.h
va_arg()
семейство функций. Наличие функций переменной длины в программе на языке C почти всегда указывает на плохой дизайн программы. Следует избегать, если у вас нет особых требований.stdio.h
Как правило, вся эта библиотека не рекомендуется для производственного кода , так как она имеет множество случаев плохо определенного поведения и плохой безопасности типов.
fflush()
. Идеально подходит для выходных потоков. Вызывает неопределенное поведение, если используется для входных потоков.gets_s()
. Безопасная версияgets()
включена в интерфейс проверки границ C11.fgets()
Вместо этого предпочтительнее использовать согласно стандартной рекомендации C. Ссылки: ISO 9899: 2011 K.3.5.4.1.printf()
семейство функций. Ресурсоемкие функции, которые имеют много неопределенного поведения и плохую безопасность типов.sprintf()
также есть уязвимости. Эти функции следует избегать в производственном коде. Ссылки: MISRA-C: 2012, правило 21.6.scanf()
семейство функций. См. Примечания по поводуprintf()
. Кроме того, -scanf()
уязвим для переполнения буфера при неправильном использовании.fgets()
предпочтительнее использовать, когда это возможно. Ссылки: CERT C INT05-C , MISRA-C: 2012, правило 21.6.tmpfile()
семейство функций. Поставляется с различными проблемами уязвимости. Ссылки: CERT C FIO21-C .stdlib.h
malloc()
семейство функций. Идеально подходит для использования в размещенных системах, хотя помните об известных проблемах в C90 и поэтому не используйте результат .malloc()
Семейство функций никогда не должно использоваться в автономных приложениях. Ссылки: MISRA-C: 2012, правило 21.3.Также обратите внимание, что
realloc()
это опасно, если вы замените старый указатель результатомrealloc()
. В случае сбоя функции вы создаете утечку.system()
. Поставляется с большим количеством накладных расходов и, несмотря на переносимость, вместо этого часто лучше использовать системные функции API. Поставляется с различным плохо определенным поведением. Ссылки: CERT C ENV33-C .string.h
strcat()
. См. Примечания дляstrcpy()
.strcpy()
. Идеально подходит для использования, если размер копируемых данных неизвестен или больше целевого буфера. Если проверка размера входящих данных не выполняется, возможно переполнение буфера. Это не вина самого поstrcpy()
себе, а вызывающего приложения - этоstrcpy()
небезопасно, это по большей части миф, созданный Microsoft .strtok()
. Изменяет строку вызывающего и использует внутренние переменные состояния, что может сделать его небезопасным в многопоточной среде.источник
static_assert()
вместоassert()
, если условие может быть решено во время компиляции. Кроме того,sprintf()
почти всегда можно заменить,snprintf()
что немного безопаснее.strtok()
в функции A означает, что (a) функция не может вызывать любую другую функцию, которая также используется,strtok()
пока A использует ее, и (b) означает, что никакая функция, которая вызывает A, не может использоваться,strtok()
когда она вызывает A. Другими словами, использованиеstrtok()
отравляет цепочку вызовов; его нельзя безопасно использовать в коде библиотеки, потому что он должен задокументировать, что он использует,strtok()
чтобы другие пользователи не моглиstrtok()
вызывать код библиотеки.strncpy
подходящая функция для использования при записи буфера строки с нулевым заполнением с данными, взятыми либо из строки с завершающим нулем, либо из буфера с нулевым заполнением, размер которого не меньше размера места назначения. Буферы с нулевым заполнением не очень распространены, но они тоже не совсем непонятные.Некоторые люди утверждают , что
strcpy
иstrcat
следует избегать, в пользуstrncpy
иstrncat
. На мой взгляд, это несколько субъективно.Их определенно следует избегать при работе с пользовательским вводом - здесь нет сомнений.
В коде, «далеком» от пользователя, когда вы просто знаете, что буферы достаточно длинные,
strcpy
и ониstrcat
могут быть немного более эффективными, потому что вычислениеn
для передачи их кузенам может быть излишним.источник
strlcat
иstrlcpy
, поскольку версия 'n' не гарантирует завершение строки назначения NULL.strncpy()
будет записывать ровноn
байты, при необходимости используя нулевые символы. Как Дэн, использование безопасной версии - лучший выбор ИМО.strncat()
использовать правильно и безопасно (и этого следует избегать) может быть непросто, касается темы.Избегайте
strtok
для многопоточных программ, поскольку он не является поточно-ориентированным.gets
так как это могло вызвать переполнение буфераисточник
strtok()
выходит за рамки безопасности потоков - это небезопасно даже в однопоточной программе, если вы не знаете наверняка, что никакие функции, которые ваш код может вызвать при использованииstrtok()
, также не используют его (или они испортятstrtok()
состояние правильно из-под вас). Фактически, большинство компиляторов, нацеленных на многопоточные платформы,strtok()
решают потенциальные проблемы в отношении потоков, используя локальное хранилище потоков дляstrtok()
статических данных. Но это по-прежнему не решает проблему использования его другими функциями, пока вы (в том же потоке).strtok()
заключается в том, что он сохраняет ровно один буфер для работы, поэтому для большинства хороших замен требуется поддерживать значение буфера и передавать его.strcspn
делает большую часть того, что вам нужно - находит следующий разделитель токенов. Вы можете переопределить с его помощью разумный вариантstrtok
.Вероятно, стоит добавить еще раз, что
strncpy()
это не универсальная замена, оstrcpy()
которой можно было бы предположить по названию. Он разработан для полей фиксированной длины, которым не нужен нулевой символ конца (изначально он был разработан для использования с записями каталога UNIX, но может быть полезен для таких вещей, как поля ключа шифрования).Однако его легко использовать
strncat()
как заменуstrcpy()
:if (dest_size > 0) { dest[0] = '\0'; strncat(dest, source, dest_size - 1); }
(
if
Очевидно, что тест можно отбросить в общем случае, когда вы знаете, чтоdest_size
это определенно ненулевое значение).источник
Также ознакомьтесь со списком запрещенных API Microsoft . Это API-интерфейсы (включая многие из них, уже перечисленные здесь), которые запрещены для использования в коде Microsoft, поскольку они часто используются не по назначению и приводят к проблемам с безопасностью.
Вы можете не согласиться со всеми из них, но все они заслуживают внимания. Они добавляют API в список, когда его неправильное использование привело к ряду ошибок безопасности.
источник
Практически любая функция, которая имеет дело со строками, завершающимися NUL, потенциально небезопасна. Если вы получаете данные из внешнего мира и манипулируете ими с помощью функций str * (), вы настраиваете себя на катастрофу.
источник
Не забывайте о sprintf - это причина многих проблем. Это правда, потому что альтернатива snprintf иногда имеет разные реализации, которые могут сделать ваш код непереносимым.
Linux: http://linux.die.net/man/3/snprintf
окна: http://msdn.microsoft.com/en-us/library/2ts7cx93%28VS.71%29.aspx
В случае 1 (linux) возвращаемое значение - это объем данных, необходимых для хранения всего буфера (если он меньше размера данного буфера, то вывод был усечен)
В случае 2 (окна) возвращаемое значение - отрицательное число в случае усечения вывода.
Как правило, вам следует избегать функций, которые не являются:
безопасное переполнение буфера (здесь уже упоминалось множество функций)
потокобезопасный / не реентерабельный (например, strtok)
В руководстве каждой функции вы должны искать такие ключевые слова, как: safe, sync, async, thread, buffer, bugs.
источник
_sprintf()
был создан Microsoft до появления стандартаsnprintf()
IIRC. ´StringCbPrintf () ´ очень похож наsnprintf()
.sprintf
как - то благополучно в некоторых случаях:sprintf(buffer,"%10s",input);
ограничивает скопированный пь байт до 10 (еслиbuffer
естьchar buffer[11]
это безопасно , даже если эти данные могут заводиться усечен.Это очень сложно
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; } }
источник
Во всех сценариях копирования / перемещения строки - strcat (), strncat (), strcpy (), strncpy () и т. Д. - все идет намного лучше ( безопаснее ), если применяется пара простых эвристик:
1. Всегда заполнять NUL ваш буфер (ы) перед добавлением данных.
2. Объявите символьные буферы как [SIZE + 1] с макроконстантой.
Например, учитывая:
#define BUFSIZE 10 char Buffer[BUFSIZE+1] = { 0x00 }; /* The compiler NUL-fills the rest */
мы можем использовать такой код:
memset(Buffer,0x00,sizeof(Buffer)); strncpy(Buffer,BUFSIZE,"12345678901234567890");
относительно безопасно. Memset () должен появиться перед strncpy (), даже если мы инициализировали Buffer во время компиляции, потому что мы не знаем, какой мусор поместил в него другой код до вызова нашей функции. Strncpy () усекает скопированные данные до «1234567890» и не завершает их NUL. Однако, поскольку мы уже заполнили весь буфер NUL - sizeof (Buffer), а не BUFSIZE - в любом случае гарантированно будет окончательный "выход за рамки", завершающий NUL, если мы ограничиваем наши записи с помощью BUFSIZE константа, а не sizeof (Buffer).
Buffer и BUFSIZE также отлично работают для snprintf ():
memset(Buffer,0x00,sizeof(Buffer)); if(snprintf(Buffer,BUFIZE,"Data: %s","Too much data") > BUFSIZE) { /* Do some error-handling */ } /* If using MFC, you need if(... < 0), instead */
Несмотря на то, что snprintf () специально записывает только символы BUFIZE-1, за которыми следует NUL, это работает безопасно. Таким образом, мы «тратим» лишний байт NUL в конце буфера ... мы предотвращаем как переполнение буфера, так и состояние незавершенной строки за довольно небольшие затраты памяти.
Мой призыв к strcat () и strncat () более жесткий: не используйте их. Безопасно использовать strcat () сложно, а API для strncat () настолько нелогичен, что усилия, необходимые для его правильного использования, сводят на нет какие-либо преимущества. Предлагаю следующий дроп-ин:
#define strncat(target,source,bufsize) snprintf(target,source,"%s%s",target,source)
Заманчиво создать добавление strcat (), но это не очень хорошая идея:
#define strcat(target,source) snprintf(target,sizeof(target),"%s%s",target,source)
потому что target может быть указателем (таким образом, sizeof () не возвращает нужную нам информацию). У меня нет хорошего «универсального» решения для экземпляров strcat () в вашем коде.
Проблема, с которой я часто сталкиваюсь от программистов, "знающих о strFunc ()", - это попытка защиты от переполнения буфера с помощью strlen (). Это нормально, если содержимое гарантированно завершается NUL. В противном случае сама strlen () может вызвать ошибку переполнения буфера (обычно приводящую к нарушению сегментации или другой ситуации с дампом ядра), прежде чем вы когда-нибудь достигнете «проблемного» кода, который пытаетесь защитить.
источник
atoi не является потокобезопасным. Вместо этого я использую strtol по рекомендации на странице руководства.
источник
strtol()
это могло бы быть поточно-ориентированным иatoi()
не было бы.man atoi
(хотя они должны быть).