Каких функций из стандартной библиотеки следует (следует) избегать?

90

Я читал в Stack Overflow, что некоторые функции C «устарели» или «их следует избегать». Не могли бы вы привести мне несколько примеров такой функции и причину почему?

Какие существуют альтернативы этим функциям?

Можем ли мы использовать их безопасно - какие хорошие практики?

Андрей Чобану
источник
3
Я считаю, что это очень хорошо знать. Некоторые функции из C действительно следует избегать и использовать только в образовательных целях.
INS
@Felix, в будущем будет проще отредактировать один единственный (отмеченный как правильный) ответ. Ответы на вопросы, которые, вероятно, со временем изменятся, действительно витают в воздухе. Кто знает, может быть, Джефф даст значок «дворника» тем, кто отвечает на вопросы в ближайшие несколько лет.
Тим Пост
1
Сообщение @Tim: Хорошо, я удалю свои комментарии.
Феликс Клинг
3
Я бы не стал использовать его strncpy()в качестве общей замены strcpy(), и я бы никогда не стал его использовать, strncat()потому что у него самый неинтуитивный интерфейс, который только можно вообразить - ВЫ знаете, что указывает параметр длины?
Джонатан Леффлер,
2
Использование strncpy и strncat почти всегда является ошибкой. Их, конечно же, не следует использовать вместо strcpy и strcat !! scanf и sprintf также идеально подходят для использования, если вы знаете, как их использовать ...
R .. GitHub НЕ ПОМОГАЕТ ICE

Ответы:

58

Устаревшие функции
небезопасны
Прекрасным примером такой функции является 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.

Михаил Аарон Сафян
источник
4
«Устаревший» - сильное слово, которое имеет особое значение при обсуждении стандарта C ++. В этом смысле функции gets (), strcpy () и т. Д. Не являются устаревшими.
4
Просто до тех пор, пока вы проводите различие между «не рекомендуется стандартом C», «не рекомендуется Майклом Аароном Сафьяном» и «не рекомендуется неизвестным лицом или лицами, которые, надеюсь, знают, о чем говорят [необходима цитата]». Вопрос в предпочтительном стиле кодирования, а не в стандарте C, поэтому подходят два вторых. Но, как и Нил, мне потребовалось дважды подумать, прежде чем я понял, что ваши утверждения не подразумевают первого значения.
Стив Джессоп,
11
strncpyкак правило, также следует избегать. Он не делает того, что думает большинство программистов. Он не гарантирует завершения (что приводит к переполнению буфера) и заполняет более короткие строки (возможно, в некоторых случаях снижая производительность).
Адриан Маккарти,
2
@Adrian: Я согласен с вами - ни то, strncpy()ни другое не strncat()является разумной заменой вариантам без n.
Джонатан Леффлер,
4
Этот ответ распространяет бессмысленную догму о том, что «замените strcpy на strncpy - я не знаю почему, но Microsoft говорит мне это». strncpy никогда не задумывался как безопасная версия strcpy! На первый взгляд это гораздо опаснее. См. Почему strlcpy и strlcat считаются небезопасными? .
Lundin
24

И снова люди повторяют, как мантру, нелепое утверждение, что "n" версия функций str является безопасной версией.

Если бы это было то, для чего они были предназначены, они всегда завершали бы строки нулевым символом.

«N» версий функций были написаны для использования с полями фиксированной длины (такими как записи каталогов в ранних файловых системах), где символ конца nul требуется только в том случае, если строка не заполняет поле. Это также причина того, что функции имеют странные побочные эффекты, которые бессмысленно неэффективны, если использовать их просто как замену - например, возьмем strncpy ():

Если массив, на который указывает s2, является строкой, которая короче n байтов, нулевые байты добавляются к копии в массиве, на который указывает s1, пока не будет записано n байтов всего.

Поскольку размер буферов, выделенных для обработки имен файлов, обычно составляет 4 КБ, это может привести к значительному снижению производительности.

Если вам нужны «предположительно безопасные» версии, то получите - или напишите свои собственные - подпрограммы strl (strlcpy, strlcat и т.д.), которые всегда завершают строки nul и не имеют побочных эффектов. Обратите внимание, что это не совсем безопасно, поскольку они могут тихо обрезать строку - это редко лучший способ действий в любой реальной программе. Есть случаи, когда это нормально, но есть также много обстоятельств, когда это может привести к катастрофическим результатам (например, распечатка медицинских рецептов).

Щуп
источник
1
Вы правы strncpy(), но ошибаетесь strncat(). strncat()не был разработан для использования с полями фиксированной длины - на самом деле он был разработан как strcat()ограничивающий количество конкатенированных символов. Это довольно легко использовать как «безопасный strcat()», отслеживая пространство, оставшееся в буфере при выполнении нескольких конкатенаций, и еще проще использовать его как «безопасный strcpy()» (установив для первого символа целевого буфера значение '\0'до называя это). strncat() всегда завершает строку назначения и не записывает лишние '\0's.
caf
2
@caf - да, но strncat () совершенно бесполезен, поскольку он принимает в качестве параметра максимальную длину для копирования, а не длину целевого буфера. Чтобы предотвратить переполнение буфера, вам нужно знать длину текущей целевой строки, и если вы знаете, зачем вам использовать strncat (), который должен снова вычислить длину целевой строки, а не просто strlcat () исходную строку для конец целевой строки.
Dipstick
Это по-прежнему не меняет того факта, что вы подразумеваете, что strncat()адрес назначения не всегда завершается нулевым завершением, и что он был разработан для использования с полями фиксированной длины, что неверно.
caf
2
@chrisharris: strncat()будет работать правильно независимо от длины исходной строки, а strcat()не будет. Проблема strlcat()здесь в том, что это не стандартная функция C.
Дэвид Торнли
@caf - вы правы насчет strncat (). Я никогда не использовал его, потому что, как я уже отмечал выше, он совершенно бесполезен. Поэтому этого все же следует избегать.
Dipstick
19

Несколько ответов здесь предлагают использовать strncat()over strcat(); Я бы посоветовал избегать strncat()strncpy()). У него есть проблемы, которые затрудняют правильное использование и приводят к ошибкам:

  • параметр длины strncat()связан с (но не совсем точно - см. 3-й пункт) максимальным количеством символов, которые можно скопировать в место назначения, а не с размером буфера назначения. Это делает использование strncat()более сложным, чем должно быть, особенно если несколько элементов будут объединены в место назначения.
  • может быть сложно определить, был ли результат усечен (что может иметь значение, а может и нет)
  • легко получить единичную ошибку. Как отмечается в стандарте C99, «Таким образом, максимальное количество символов, которое может оказаться в массиве, на который указывает, s1- strlen(s1)+n+1» для вызова, который выглядит какstrncat( s1, s2, n)

strncpy()также есть проблема, которая может привести к ошибкам, которые вы пытаетесь использовать интуитивно понятным способом - это не гарантирует, что место назначения имеет нулевое завершение. Чтобы убедиться, что вы специально обрабатываете этот угловой случай, '\0'самостоятельно помещая a в последнее место буфера (по крайней мере, в определенных ситуациях).

Я бы посоветовал использовать что-то вроде OpenBSD strlcat()и strlcpy()(хотя я знаю, что некоторые люди не любят эти функции; я считаю, что их гораздо проще использовать безопасно, чем strncat()/ strncpy()).

Вот немного того, что Тодд Миллер и Тео де Раадт сказали о проблемах с strncat()и strncpy():

Есть несколько проблем , с которыми сталкиваются , когда strncpy()и strncat()используются в качестве безопасных версий strcpy()и strcat(). Обе функции имеют дело с NUL-завершением и параметром длины по-разному и не интуитивно понятно, что сбивает с толку даже опытных программистов. Они также не предоставляют простого способа определить, когда происходит усечение. ... Из всех этих проблем наиболее важными являются путаница, вызванная параметрами длины и связанная с этим проблема NUL-termination. Когда мы проверили дерево исходного кода OpenBSD на предмет потенциальных дыр в безопасности, мы обнаружили безудержное злоупотребление strncpy()и strncat(). Хотя не все это привело к появлению уязвимых мест в системе безопасности, они ясно дали понять, что правила использования strncpy()и strncat()безопасных операций со строками широко неправильно понимаются.

Аудит безопасности OpenBSD обнаружил, что ошибки с этими функциями были "безудержными". В отличие от gets()этих функций можно безопасно использовать, но на практике возникает много проблем, потому что интерфейс запутанный, неинтуитивный и сложный для правильного использования. Я знаю, что Microsoft также провела анализ (хотя я не знаю, какую часть своих данных они могли опубликовать), и в результате запретили (или, по крайней мере, очень сильно обескуражили - «запрет» не может быть абсолютным) использование strncat()и strncpy()(среди других функций).

Некоторые ссылки с дополнительной информацией:

Майкл Берр
источник
1
Гораздо лучше отслеживать длину строк за пределами самой строки. Таким образом, безопасное объединение двух строк (-with-length) становится вопросом простого вычисления (чтобы получить требуемый размер буфера) возможного перераспределения и a memmove(). (Ну, вы можете использовать memcpy()в обычном случае, когда струны независимы.)
Donal Fellows
1
Ваша вторая точка совершенно неверна - strncat() всегда заканчивается строка назначения.
caf
1
Ваш второй пункт неверен strncat(). Однако он верен strncpy(), и у него есть другие проблемы. strncat()является разумной заменой strcat(), но strncpy()не является разумной заменой strcpy().
Дэвид Торнли
2
caf и Дэвид на 100% правы в моем заявлении, которое strncat()не всегда заканчивается нулём. Я немного запутал поведение strncat()и strncpy()(еще одна причина, по которой их следует избегать - у них есть имена, подразумевающие схожее поведение, но на самом деле они ведут себя по-разному в важных аспектах ...). Я изменил свой ответ, чтобы исправить это, а также добавить дополнительную информацию.
Майкл Берр
1
Обратите внимание, что char str[N] = ""; strncat(str, "long string", sizeof(str));это переполнение буфера, если N недостаточно велик. strncat()Функция слишком легко неправильное использование; его не следует использовать. Если вы можете использовать strncat()безопасно, вы могли бы использовать memmove()или memcpy()вместо него (и это было бы более эффективно).
Джонатан Леффлер
9

Стандартные библиотечные функции, которые никогда не следует использовать:

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()что немного безопаснее.
user694733
1
Использование strtok()в функции A означает, что (a) функция не может вызывать любую другую функцию, которая также используется, strtok()пока A использует ее, и (b) означает, что никакая функция, которая вызывает A, не может использоваться, strtok()когда она вызывает A. Другими словами, использование strtok()отравляет цепочку вызовов; его нельзя безопасно использовать в коде библиотеки, потому что он должен задокументировать, что он использует, strtok()чтобы другие пользователи не могли strtok()вызывать код библиотеки.
Джонатан Леффлер
Это strncpyподходящая функция для использования при записи буфера строки с нулевым заполнением с данными, взятыми либо из строки с завершающим нулем, либо из буфера с нулевым заполнением, размер которого не меньше размера места назначения. Буферы с нулевым заполнением не очень распространены, но они тоже не совсем непонятные.
supercat
7

Некоторые люди утверждают , что strcpyи strcatследует избегать, в пользу strncpyи strncat. На мой взгляд, это несколько субъективно.

Их определенно следует избегать при работе с пользовательским вводом - здесь нет сомнений.

В коде, «далеком» от пользователя, когда вы просто знаете, что буферы достаточно длинные, strcpyи они strcatмогут быть немного более эффективными, потому что вычисление nдля передачи их кузенам может быть излишним.

Эли Бендерский
источник
4
И если доступно, еще лучше strlcatи strlcpy, поскольку версия 'n' не гарантирует завершение строки назначения NULL.
Дэн Андреатта
Для меня это звучит как преждевременная оптимизация. Но IIRC strncpy()будет записывать ровно nбайты, при необходимости используя нулевые символы. Как Дэн, использование безопасной версии - лучший выбор ИМО.
Bastien Léonard
@Dan, эти функции, к сожалению, нестандартны и, следовательно, не относятся к этому обсуждению
Эли Бендерский
@Eli: но тот факт, что strncat()использовать правильно и безопасно (и этого следует избегать) может быть непросто, касается темы.
Майкл Берр
@Michael: Я бы не сказал, что это сложно правильно и безопасно использовать. С осторожностью все работает отлично
Эли Бендерский
6

Избегайте

  • strtok для многопоточных программ, поскольку он не является поточно-ориентированным.
  • gets так как это могло вызвать переполнение буфера
codaddict
источник
2
Это несколько разные случаи. Вы можете безопасно использовать strtok, если знаете, что ваша программа не многопоточная, или вы каким-то образом заблокировали доступ к ней, но вы не можете безопасно использовать get, поэтому никогда не используйте ее.
jcoder 02
9
Проблема strtok()выходит за рамки безопасности потоков - это небезопасно даже в однопоточной программе, если вы не знаете наверняка, что никакие функции, которые ваш код может вызвать при использовании strtok(), также не используют его (или они испортят strtok()состояние правильно из-под вас). Фактически, большинство компиляторов, нацеленных на многопоточные платформы, strtok()решают потенциальные проблемы в отношении потоков, используя локальное хранилище потоков для strtok()статических данных. Но это по-прежнему не решает проблему использования его другими функциями, пока вы (в том же потоке).
Майкл Берр,
В самом деле, и я сейчас заткнусь, так как я не хочу никого поощрять использовать strtok, поскольку он страдает от многих проблем. Я просто хотел бы отметить, что она отличается от получает хотя , как это возможно , чтобы использовать его безопасно в то время как это невозможно использование безопасно получает.
jcoder 02
@ Джимми: Часто существуют нестандартные расширения библиотеки, или вы можете написать свои собственные. Большая проблема strtok()заключается в том, что он сохраняет ровно один буфер для работы, поэтому для большинства хороших замен требуется поддерживать значение буфера и передавать его.
Дэвид Торнли
strcspnделает большую часть того, что вам нужно - находит следующий разделитель токенов. Вы можете переопределить с его помощью разумный вариант strtok.
bluss
5

Вероятно, стоит добавить еще раз, что strncpy()это не универсальная замена, о strcpy()которой можно было бы предположить по названию. Он разработан для полей фиксированной длины, которым не нужен нулевой символ конца (изначально он был разработан для использования с записями каталога UNIX, но может быть полезен для таких вещей, как поля ключа шифрования).

Однако его легко использовать strncat()как замену strcpy():

if (dest_size > 0)
{
    dest[0] = '\0';
    strncat(dest, source, dest_size - 1);
}

( ifОчевидно, что тест можно отбросить в общем случае, когда вы знаете, что dest_sizeэто определенно ненулевое значение).

кафе
источник
5

Также ознакомьтесь со списком запрещенных API Microsoft . Это API-интерфейсы (включая многие из них, уже перечисленные здесь), которые запрещены для использования в коде Microsoft, поскольку они часто используются не по назначению и приводят к проблемам с безопасностью.

Вы можете не согласиться со всеми из них, но все они заслуживают внимания. Они добавляют API в список, когда его неправильное использование привело к ряду ошибок безопасности.

Адриан Маккарти
источник
2

Практически любая функция, которая имеет дело со строками, завершающимися NUL, потенциально небезопасна. Если вы получаете данные из внешнего мира и манипулируете ими с помощью функций str * (), вы настраиваете себя на катастрофу.

rep_movsd
источник
2

Не забывайте о sprintf - это причина многих проблем. Это правда, потому что альтернатива snprintf иногда имеет разные реализации, которые могут сделать ваш код непереносимым.

  1. Linux: http://linux.die.net/man/3/snprintf

  2. окна: http://msdn.microsoft.com/en-us/library/2ts7cx93%28VS.71%29.aspx

В случае 1 (linux) возвращаемое значение - это объем данных, необходимых для хранения всего буфера (если он меньше размера данного буфера, то вывод был усечен)

В случае 2 (окна) возвращаемое значение - отрицательное число в случае усечения вывода.

Как правило, вам следует избегать функций, которые не являются:

  1. безопасное переполнение буфера (здесь уже упоминалось множество функций)

  2. потокобезопасный / не реентерабельный (например, strtok)

В руководстве каждой функции вы должны искать такие ключевые слова, как: safe, sync, async, thread, buffer, bugs.

INS
источник
2
_sprintf()был создан Microsoft до появления стандарта snprintf()IIRC. ´StringCbPrintf () ´ очень похож на snprintf().
Bastien Léonard
Вы можете использовать sprintfкак - то благополучно в некоторых случаях: sprintf(buffer,"%10s",input);ограничивает скопированный пь байт до 10 (если bufferесть char buffer[11]это безопасно , даже если эти данные могут заводиться усечен.
Жан-Франсуа Фабр
2

Это очень сложно 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;
    }
}
Алок Сингхал
источник
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 () может вызвать ошибку переполнения буфера (обычно приводящую к нарушению сегментации или другой ситуации с дампом ядра), прежде чем вы когда-нибудь достигнете «проблемного» кода, который пытаетесь защитить.

TLR
источник
-2

atoi не является потокобезопасным. Вместо этого я использую strtol по рекомендации на странице руководства.

Фред
источник
5
Похоже, это применимо к одной конкретной реализации. Нет никаких причин, по которым strtol()это могло бы быть поточно-ориентированным и atoi()не было бы.
Дэвид Торнли
2
Рекомендация использовать strtol не имеет ничего общего с безопасностью потоков, а связана с обработкой ошибок. Не уверен, с какой страницы руководства вы получили эту информацию - я не могу найти никаких рекомендаций ниже man atoi(хотя они должны быть).
Lundin