Яких функцій зі стандартної бібліотеки слід (слід) уникати?


90

Я читав на Stack Overflow, що деякі функції C є "застарілими" або "їх слід уникати". Чи можете ви дати мені кілька прикладів такого роду функцій та причину?

Які альтернативи цим функціям існують?

Чи можемо ми використовувати їх безпечно - якісь хороші практики?


3
Я вважаю, що це дуже добре знати. Справді слід уникати деяких функцій з C і використовувати їх лише в навчальних цілях.
INS

@Felix, у майбутньому було б простіше відредагувати одну-єдину (позначену як правильну) відповідь. Це справді лунає в повітрі щодо відповідей, які з часом можуть змінитися. Хто знає, можливо, Джефф надасть значок «прибиральника» людям, які в найближчі кілька років постійно підтримуватимуть відповіді.
Tim Post

1
@Tim Post: Добре, я видалю свої коментарі.
Фелікс Клінг,

3
Я б не використовував strncpy()як загальну заміну strcpy()і не використовував би strncat()ніколи, оскільки він має найбільш неінтуїтивний інтерфейс, який тільки можна собі уявити - ВИ знаєте, що вказує параметр length?
Джонатан Леффлер,

2
Використання strncpy та strncat майже завжди є помилкою. Їх, звичайно, не слід використовувати замість strcpy та strcat !! scanf та sprintf також цілком придатні для використання, якщо ви знаєте, як ними користуватися ...
R .. GitHub СТОП ДОПОМОГАЙ ЛЕД

Відповіді:


58

Застарілі функції
небезпечні
Ідеальним прикладом такої функції є get () , оскільки немає можливості сказати, наскільки великий буфер призначення. Отже, будь-яка програма, яка читає вхідні дані за допомогою get (), має вразливість переповнення буфера . З подібних причин слід використовувати 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
"Застаріле" - це сильне слово, яке має конкретне значення під час обговорення стандарту С ++. У цьому сенсі get (), strcpy () тощо не є застарілими.

4
До тих пір, поки ви розмежовуєте "застарілих за стандартом С", "застарілих Майклом Аароном Саф'яном" та "застарілих невідомих або осіб, які, сподіваємось, знають, про що вони говорять [потрібне цитування]". Питання буде про кращий стилю кодування, а нема про стандарті C, тому другі два є придатними. Але, як і Ніл, я вимагав подвійного прийому, перш ніж зрозуміти, що ваші висловлювання не мають на меті натякати на перший сенс.
Стів Джессоп,

11
strncpyяк правило, слід також уникати. Він не робить того, що припускає більшість програмістів. Це не гарантує завершення (що призводить до перевиконання буфера), і воно прокладає коротші рядки (можливо, погіршує продуктивність в деяких випадках).
Адріан Маккарті

2
@ Адріан: Я погоджуюся з вами - ні те, strncpy()ні ще гірше strncat()не є розумною заміною для варіантів, що не містять n.
Джонатан Леффлер,

4
Ця відповідь поширює безглузді догми про "замінити strcpy на strncpy - я не знаю чому, але Microsoft каже мені". strncpy ніколи не задумувався як безпечна версія strcpy! На обличчя це набагато небезпечніше. Див. Чому strlcpy та strlcat вважаються небезпечними? .
Лундін,

24

Люди знову повторюють, мантроподібно, смішне твердження про те, що "n" версія функцій str є безпечною версією.

Якби це було те, для чого вони були призначені, вони завжди мали б нульове закінчення рядків.

Версії функцій "n" були написані для використання з полями фіксованої довжини (наприклад, записами каталогів у ранніх файлових системах), де термінатор nul потрібен лише в тому випадку, якщо рядок не заповнює поле. Це також причина, чому функції мають дивні побічні ефекти, які безглуздо неефективні, якщо їх просто використовувати як заміну - візьмемо, наприклад, strncpy ():

Якщо масив, на який вказує s2, є рядком, який коротший за n байт, нульові байти додаються до копії в масиві, на який вказує s1, доки не буде записано n байтів у всіх.

Оскільки буфери, виділені для обробки імен файлів, зазвичай складають 4 кбайт, це може призвести до значного погіршення продуктивності.

Якщо ви хочете "нібито" безпечні версії, то отримайте - або напишіть власні - процедури strl (strlcpy, strlcat тощо), які завжди нуль завершують рядки і не мають побічних ефектів. Зверніть увагу, однак, що вони насправді не є безпечними, оскільки можуть мовчки скоротити рядок - це рідко найкращий спосіб дій у будь-якій реальній програмі. Бувають випадки, коли це нормально, але є також багато обставин, коли це може призвести до катастрофічних результатів (наприклад, роздрукування лікарських рецептів).


1
Ви маєте рацію strncpy(), але помиляєтесь strncat(). strncat()не був розроблений для використання з полями фіксованої довжини - він насправді був розроблений таким чином, strcat()щоб обмежувати кількість об'єднаних символів. Досить просто використовувати це як "сейф strcat()", відстежуючи простір, що залишився в буфері при виконанні кількох конкатенацій, і ще простіше використовувати його як "сейф strcpy()" (встановивши для першого символу буфера призначення значення '\0'до називаючи це). strncat() завжди закінчує рядок призначення, і він не пише зайвих '\0's.
кафе

2
@caf - так, але strncat () абсолютно марний, оскільки для параметра береться максимальна довжина для копіювання, а не довжина цільового буфера. Щоб запобігти переповненню буфера, вам потрібно знати довжину поточного цільового рядка, і якщо ви знаєте, чому б вам використовувати strncat () - який повинен знову обробити довжину dest - а не просто strlcat () вихідний рядок, щоб кінець рядка dest.
щуп

Це все одно не змінює того факту, що ви натякаєте, що strncat()не завжди закінчує призначення, і що воно було розроблене для використання з полями фіксованої довжини, обидва з яких є помилковими.
кафе

2
@chrisharris: strncat()працюватиме належним чином незалежно від довжини вихідного рядка, тоді як strcat()не буде. Проблема strlcat()тут полягає в тому, що це не стандартна функція C.
Девід Торнлі,

@caf - ви правильно вважаєте strncat (). Я насправді ніколи не використовував його, бо - як я вже зазначав вище - він абсолютно марний. Тому цього слід уникати.
щуп

19

Кілька відповідей тут пропонують використовувати strncat()over strcat(); Я пропоную, щоб strncat()strncpy()) також слід уникати. У нього є проблеми, які ускладнюють правильне використання та призводять до помилок:

  • параметр length до strncat()пов'язаний (але не зовсім точно - див. 3-й пункт) максимальною кількістю символів, які можна скопіювати в пункт призначення, а не розміром буфера призначення. Це strncat()ускладнює використання, ніж повинно бути, особливо якщо кілька об’єктів будуть об’єднані в пункт призначення.
  • може бути важко визначити, чи результат був усічений (що може бути чи не важливо)
  • легко отримати поодиноку помилку. Як зазначає стандарт C99, "Отже, максимальна кількість символів, яка може потрапити в масив, на який вказує", s1- strlen(s1)+n+1для дзвінка, який виглядає якstrncat( s1, s2, n)

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

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

Ось трохи того, що Тодд Міллер та Тео де Раадт мали сказати про проблеми strncat()та strncpy():

Існує кілька проблем, які виникають, коли strncpy()і strncat()використовуються як безпечні версії strcpy()та strcat(). Обидві функції мають справу з припиненням NUL та параметром довжини різними та неінтуїтивними способами, що бентежить навіть досвідчених програмістів. Вони також не забезпечують легкого способу виявити, коли відбувається усічення. ... З усіх цих питань плутанина, спричинена параметрами довжини, та пов'язана з цим проблема припинення NUL є найбільш важливими. Коли ми перевірили дерево джерел OpenBSD на наявність потенційних дірок у безпеці, ми виявили нестримне зловживання strncpy()та strncat(). Незважаючи на те, що не всі з них призвели до дірок, які можна використати, вони дали зрозуміти, що правила користування strncpy()та strncat()безпечні операції з рядками широко неправильно розуміються.

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

Деякі посилання з додатковою інформацією:


1
Набагато краще відстежувати довжину рядків поза самим рядком. Подібно до цього, об’єднання двох рядків (-з довжиною) безпечно стає питанням простого обчислення (для отримання необхідного розміру буфера) можливого перерозподілу та memmove(). (Ну, ви можете використовувати memcpy()в звичайному випадку, коли рядки незалежні.)
Donal Fellows

1
Ваш другий пункт абсолютно невірний - strncat() завжди закінчує рядок призначення.
кафе

1
Ваш другий пункт помилковий strncat(). Однак це правильно для strncpy(), що має деякі інші проблеми. strncat()є розумною заміною strcat(), але strncpy()не є розумною заміною strcpy().
Девід Торнлі,

2
caf і David на 100% мають рацію щодо мого твердження, strncat()яке не завжди закінчується. Я плутав поведінку strncat()та strncpy()трохи (ще одна причина, через яку їх слід уникати - вони мають імена, що передбачають подібну поведінку, але насправді по-різному поводяться у важливих напрямках ...). Я змінив свою відповідь, щоб виправити це, а також додати додаткову інформацію.
Michael Burr,

1
Зверніть увагу, що char str[N] = ""; strncat(str, "long string", sizeof(str));це переповнення буфера, якщо N недостатньо великий. strncat()Функція занадто легко неправильне використання; його не слід використовувати. Якщо ви можете strncat()безпечно використовувати , ви могли б використовувати memmove()або memcpy()замість цього (і це було б ефективніше).
Джонатан Леффлер,

9

Стандартні функції бібліотеки, які ніколи не слід використовувати:

setjmp.h

  • setjmp(). Разом з longjmp()цим ці функції широко визнані неймовірно небезпечними у використанні: вони призводять до програмування спагеті, вони мають численні форми невизначеної поведінки, вони можуть спричинити непередбачувані побічні ефекти в середовищі програми, такі як вплив на значення, що зберігаються в стеку. Посилання: MISRA-C: правило 21.4 2012 р., 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: правило 21.7 2012 р.

рядок.ч

  • strncat(). Має незручний інтерфейс, яким часто зловживають. Це здебільшого зайва функція. Також див. Зауваження до strncpy().
  • strncpy(). Метою цієї функції ніколи не було бути більш безпечною версією strcpy(). Його єдиною метою завжди було обробляти старовинний формат рядків у системах Unix, і те, що його включено до стандартної бібліотеки - це відома помилка. Ця функція небезпечна, оскільки може залишити рядок без нульового припинення, і програмісти, як відомо, часто використовують її неправильно. Посилання: Чому strlcpy та strlcat вважаються небезпечними? .

Стандартні функції бібліотеки, які слід використовувати з обережністю:

стверджувати

  • assert(). Поставляється із накладними витратами і, як правило, не повинен використовуватися у виробничому коді. Краще використовувати спеціальний обробник помилок, який відображає помилки, але не обов'язково закриває всю програму.

сигнал. ч

  • signal(). Посилання: MISRA-C: правило 21.5 2012 р., CERT C SIG32-C .

stdarg.h

  • va_arg()сімейство функцій. Наявність функцій змінної довжини в програмі на С майже завжди свідчить про поганий дизайн програми. Цього слід уникати, якщо у вас немає дуже конкретних вимог.

stdio.h
Як правило, вся ця бібліотека не рекомендується для виробничого коду , оскільки вона постачається з численними випадками погано визначеної поведінки та поганої безпеки типу.

  • fflush(). Ідеально підходить для використання для вихідних потоків. Викликає невизначену поведінку, якщо використовується для вхідних потоків.
  • gets_s(). Безпечна версія gets()включена в інтерфейс перевірки меж C11. Краще використовувати fgets()замість цього відповідно до стандартної рекомендації C. Посилання: ISO 9899: 2011 K.3.5.4.1.
  • printf()сімейство функцій. Ресурсні важкі функції, які мають багато невизначеної поведінки та погану безпеку типу. sprintf()також має вразливі місця. Цих функцій слід уникати у виробничому коді. Посилання: MISRA-C: правило 21.6 2012 р.
  • scanf()сімейство функцій. Див. Зауваження щодо printf(). Крім того, - scanf()вразливий до перевитрати буфера, якщо він використовується неправильно. fgets()переважно використовувати, коли це можливо. Посилання: CERT C INT05-C , MISRA-C: правило 21.6 2012 р.
  • tmpfile()сімейство функцій. Поставляється з різними проблемами вразливості. Список літератури: CERT C FIO21-C .

stdlib.h

  • malloc()сімейство функцій. Ідеально підходить для використання в розміщених системах, хоча майте на увазі добре відомі проблеми в C90 і, отже , не дайте результату . malloc()Сімейство функцій ніколи не повинно використовуватися в автономних програмах. Посилання: MISRA-C: правило 21.3 2012 р.

    Також зауважте, що realloc()небезпечно у випадку, якщо ви перезапишете старий покажчик із результатом realloc(). Якщо функція виходить з ладу, ви створюєте витік.

  • system(). Поставляється з великою кількістю накладних витрат, і хоча є портативним, часто краще замість цього використовувати спеціальні для системи функції API. Поставляється з різною погано визначеною поведінкою. Список літератури: CERT C ENV33-C .

рядок.ч

  • 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 припинення цільового рядка.
Dan Andreatta,

Для мене це звучить як передчасна оптимізація. Але IIRC strncpy()запише записувати точно nбайти, використовуючи нульові символи, якщо це необхідно. Як і Ден, використання безпечної версії - найкращий вибір IMO.
Бастієн Леонард,

@Dan, ці функції, на жаль, нестандартні, а отже, не належать до цієї дискусії
Елі Бендерський,

@Eli: але той факт, що strncat()може бути важко використовувати правильно та безпечно (і цього слід уникати), є темою.
Michael Burr,

@Michael: Я б не сказав, що важко правильно і безпечно використовувати. З обережністю це працює чудово
Елі Бендерскі

6

Уникайте

  • strtok для багатопотокових програм, оскільки вона не є безпечною для потоків.
  • gets оскільки це може спричинити переповнення буфера

2
Це трохи різні випадки. Ви можете безпечно використовувати strtok, якщо знаєте, що ваша програма не є багатопотоковою або ви якось заблокували доступ до неї, але ви не можете використовувати безпечно, тому ніколи не повинні її використовувати.
jcoder

9
Проблема strtok()виходить трохи за межі безпеки потоків - це не безпечно навіть в одній потоковій програмі, якщо ви точно не знаєте, що жодні функції, які не може викликати ваш код під час використання strtok(), також не використовують її (або вони зіпсують strtok()право держави з-під вас). Насправді, більшість компіляторів, націлених на багатопотокові платформи, опікуються strtok()потенційними проблемами, що стосуються потоків, використовуючи локальне сховище потоків для strtok()статичних даних. Але це все одно не вирішує проблему інших функцій, які використовують його, поки ви (у тій самій темі).
Michael Burr,

Дійсно, і я зараз замовчу, оскільки не хочу заохочувати нікого використовувати strtok, оскільки він страждає від багатьох проблем. Я просто хотів би відзначити, що вона відрізняється від отримує хоча , як це можливо , щоб використовувати його безпечно в той час як це неможливо використання безпечно отримує.
jcoder

@Jimmy: Часто бувають нестандартні розширення бібліотек, або ти можеш написати власні. Основна проблема 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

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

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


2

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


2

Не забувайте про sprintf - він є причиною багатьох проблем. Це правда, оскільки альтернатива, snprintf, іноді має різні реалізації, які можуть зробити ваш код непереносимим.

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

  2. windows: http://msdn.microsoft.com/en-us/library/2ts7cx93%28VS.71%29.aspx

У випадку 1 (linux) повернене значення - це кількість даних, необхідних для зберігання всього буфера (якщо воно менше розміру даного буфера, тоді вихідний результат був усічений)

У випадку 2 (вікна) повернене значення є від'ємним числом, якщо висновок усічений.

Як правило, слід уникати функцій, які не є:

  1. безпечний переповнення буфера (багато функцій вже згадано тут)

  2. потік безпечний / не повертається (наприклад, strtok)

У посібнику з кожної функції слід шукати такі ключові слова, як: безпечний, синхронізація, асинхронізація, потік, буфер, помилки


2
_sprintf()був створений Microsoft до появи стандарту snprintf()IIRC. "StringCbPrintf ()" досить схожий на snprintf().
Бастієн Леонард,

Ви можете використовувати 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 (), хоча ми ініціалізували буфер під час компіляції, оскільки ми не знаємо, яке сміття розміщував інший код до того, як була викликана наша функція. Strncpy () скоротить скопійовані дані до "1234567890" і не припинить їх NUL. Однак, оскільки ми вже заповнили NUL весь буфер - sizeof (Buffer), а не BUFSIZE - все одно гарантовано буде остаточне "поза обсягом", що припиняє NUL, поки ми обмежуємо наші записи, використовуючи BUFSIZE константа, замість sizeof (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)

оскільки ціль може бути покажчиком (таким чином sizeof () не повертає потрібну нам інформацію). У мене немає хорошого "універсального" рішення для екземплярів strcat () у вашому коді.

Проблема, з якою я часто стикаюся у програмістів, що знають "strFunc ()", - це спроба захистити від переповнення буфера за допомогою strlen (). Це чудово, якщо вміст гарантовано припиняє NUL. В іншому випадку strlen () сам може спричинити помилку переповнення буфера (зазвичай це призводить до порушення сегментації або іншої ситуації дампа ядра), перш ніж ви коли-небудь дійдете до "проблемного" коду, який ви намагаєтеся захистити.


-2

atoi не є потокобезпечним. Натомість я використовую strtol, відповідно до рекомендацій, що містяться на сторінці довідки.


5
Здається, це стосується однієї конкретної реалізації. Немає жодної причини, чому це strtol()було б безпечно та atoi()не було.
Девід Торнлі,

2
Рекомендація використовувати strtol не має нічого спільного з безпекою потоків, а з обробкою помилок. Не впевнений, з якої сторінки користувача ви отримали таку інформацію - я не можу знайти жодної рекомендації нижче man atoi(хоча вона повинна бути).
Лундін,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.