Чи snprintf () ЗАВЖДИ нульовим завершенням?


85

Чи завжди snprintf закінчує нуль завершенням буфера призначення?

Іншими словами, чи достатньо цього:

або ти повинен робити так, якщо деякий строк достатньо довгий?

Мене цікавить і те, що говорить стандарт, і те, що може зробити якийсь популярний libc, який не є стандартною поведінкою.


Ви маєте на увазі нуль закінчувати somestr або dst у другому прикладі?
Хадсон,

@chux, Мартін Ба висвітлив це у прийнятій відповіді. :)
Проф. Фалькен,

@chux Я думаю, це було добре, твій коментар просто чітко дав зрозуміти, що якщо dest i 0 довго, нічого не написано. Я сприймаю кожен коментар як потенційний привід поспілкуватися з іншими колегами. :)
Проф. Фалькен,

@Prof. Фалькен Погодьтеся, що коментар був нормальним і явним, але він був зайвим із відповідями - просто пропустив це в моєму огляді.
chux

stackoverflow.com/a/8712996/193892 Visual Studio тепер підтримує snprintf ()
Проф. Фалькен

Відповіді:


73

Як зазначають інші відповіді, слід :

snprintf... Записує результати в буфер рядків символів. (...) буде закінчено нульовим символом, якщо buf_size не дорівнює нулю.

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


Тим НЕ менше, будьте обережні , що бібліотека Microsoft, не має функції з ім'ям , snprintfале замість того, щоб історично тільки мала функцію з ім'ям _snprintf(примітка ведучого підкресленням) , який Не додає завершальний нуль. Ось документи (VS 2012, ~~ VS 2013):

http://msdn.microsoft.com/en-us/library/2ts7cx93%28v=vs.110%29.aspx

Повернене значення

Нехай len - довжина відформатованого рядка даних (не враховуючи закінчувальне null). len і count - у байтах для _snprintf, широкі символи для _snwprintf.

  • Якщо len <count, тоді символи len зберігаються в буфері, додається нульовий термінатор і повертається len.

  • Якщо len = count, тоді символи len зберігаються в буфері, не додається нульовий термінатор і len повертається.

  • Якщо len> count, тоді символи count зберігаються в буфері, не додається нульовий термінатор і повертається негативне значення.

(...)

Visual Studio 2015 (VC14) , мабуть , ввели відповідну snprintfфункцію, але спадщина один з провідним підкресленням і , НЕ нульовий кінцевим поведінкою по - , як і раніше існує:

snprintfФункція обрізає вихідний сигнал , коли Len більше або дорівнює розраховувати, помістивши нуль-термінатор в buffer[count-1]. (...)

Для всіх функцій інших , ніж snprintf, якщо розраховувати LEN =, довжина символів , що зберігаються в буфері, НЕ нуль-термінатор не додають , (...)


24
Про що, від імені Аслана, думали інженери Microsoft, коли вони представили, _snprintfщо тихо видаляє ключову функцію безпекиsnprintf та дозволяє рядку не припинятися з нуля ?!
Колін Д Беннетт,

2
@ColinDBennett - це дивно і надзвичайно дратує, і я не маю уявлення, чи хтось взагалі думав :-)
Мартін Ба

2
@MartinBa так, вибачте, те, що я протестував, було, template <size_t size> int _snprintf_s(char (&buffer)[size], size_t count, const char *format [, argument] ...);і я повинен також зазначити, що це відбувається лише з / GS (перевірка безпеки) прапором компіляції. Ця функція знає розмір, кількість і довжину.
sekmet64

3
Пам'ятайте, що mingw64 використовував (використовує?) Реалізацію Microsoft _snprintf як "звичайний" snprintf, якщо не вказано інше nvd.nist.gov/vuln/detail/CVE-2018-1000101
domenukk

2
@Sajjon Це, безумовно, безглуздий (і, можливо, абсолютно оригінальний) вигук роздратування ( idioms.thefreedictionary.com/in+the+name+of+God ), можливо, трохи як рубана клятва ( en.wikipedia.org/wiki/Minced_oath ). Іншим прикладом може бути "Що в ім'я Зевса ...?!" ( forum.wordreference.com/threads/in-the-name-of-zeus.2132965 )
Колін Д Беннетт

19

Відповідно до snprintf (3) manpage.

Функції snprintf()та vsnprintf()запис не більше ніж sizeбайтів (включаючи кінцевий нульовий байт ('\ 0')) в str.

Так, так, не потрібно припиняти, якщо розмір> = 1.


3
І слава Богу за це; це єдиний розумний дизайн. Весь сенс перевірених версій цих функцій полягає в безпеці , і було б жахливо, якби вам довелося робити всі ключі припинення вручну.
Kerrek SB

1
Я б порекомендував протестувати його на платформі, яку ви використовуєте, перш ніж покладатися на це. Навіть якщо він повинен писати нульовий байт, я знаю, що я стикався з реалізаціями, які цього не робили (можливо, це було з MinGW, який використовував старіший час виконання MS).
Дмитро

10

Відповідно до стандарту C, якщо розмір буфера не дорівнює 0, vsnprintf()і значення snprintf()null не припиняє вихід.

snprintf()Функція повинна бути еквівалентна sprintf(), з додаванням п аргумент , який свідчить , розмір буфера посилається с. Якщо n дорівнює нулю, нічого не записується, і s може бути нульовим покажчиком. В іншому випадку вихідні байти, що перевищують n-1-й, будуть відкинуті, замість того, щоб записати їх у масив, а нульовий байт записується в кінці байтів, фактично записаних у масив.

Отже, якщо вам потрібно знати, наскільки великий буфер для виділення, використовуйте розмір нуль, і тоді ви можете використовувати нульовий покажчик як призначення. Зверніть увагу, що я посилався на сторінки POSIX, але там прямо сказано, що не існує жодних розбіжностей між стандартом C та POSIX, коли вони охоплюють одну і ту ж основу:

Функціональність, описана на цій довідковій сторінці, узгоджується зі стандартом ISO C. Будь-який конфлікт між описаними тут вимогами та стандартом ISO C є ненавмисним. Цей обсяг POSIX.1-2008 відповідає стандарту ISO C.

Будьте обережні щодо версії Microsoft vsnprintf(). Безумовно, він поводиться інакше, ніж стандартна версія С, коли в буфері недостатньо місця (він повертає -1, де стандартна функція повертає необхідну довжину). Не зовсім зрозуміло, що версія Microsoft null припиняє свою роботу за умови помилок, тоді як стандартна версія C.

Зверніть увагу також на відповіді на питання Чи використовуєте ви безпечні функції TR 24731? (див. MSDN для версії для Microsoft vsprintf_s()) та рішення для Mac щодо безпечних альтернатив небезпечним стандартним функціям бібліотеки C?


о, злий, ніколи про це не думав. З іншого боку ... :)
Проф. Фалькен,

ах, я думаю, MS vsprintf () вкусив мене, і я зрозумів це - 1 звичка
професор Фалькен

4

Деякі старіші версії SunOS робили дивні речі з snprintf і, можливо, не припиняли вихідні дані NUL і мали значення повернення, які не відповідали тому, що робили всі інші, але все, що було випущено за останні 10 років, робило те, що C99 каже.


Я помічаю, що XP був випущений трохи більше 10 років тому. :-)
Проф. Фалькен,

І цього року він застарів. :)
Проф. Фалькен,

4

Неоднозначність починається з самого стандарту C. І C99, і C11 мають однаковий опис snprintfфункції. Ось опис від C99:

7.19.6.5 snprintf
Синопсис функції
1 #include <stdio.h> int snprintf(char * restrict s, size_t n, const char * restrict format, ...);
Опис
2 snprintfФункція еквівалентна fprintf, за винятком того, що вихідні дані записуються в масив (зазначений аргументом s), а не в потік. Якщо nдорівнює нулю, нічого не записується і sможе бути нульовим покажчиком. В іншому випадку вихідні символи за межами n-1st відкидаються, а не записуються в масив, а нульовий символ записується в кінці символів, фактично записаних в масив. Якщо копіювання відбувається між об'єктами, що перекриваються, поведінка не визначена.
Повертає
3 snprintfФункція повертає кількість символів, яку було б записаноnбуло достатньо великим, не враховуючи закінчувального нульового символу, або негативного значення, якщо сталася помилка кодування. Таким чином, вихідне значення, що закінчується нулем, було повністю записано тоді і тільки тоді, коли повернене значення невід'ємне і менше ніж n.

З одного боку речення

В іншому випадку вихідні символи після n-1st відкидаються, а не записуються в масив, а нульовий символ записується в кінці символів, фактично записаних в масив

говорить, що
якщо ( sвказує на масив довжиною 3 символи, і) nдорівнює 3, тоді будуть записані 2 символи, а символи, що перебувають поза 2-м, відкидаються ; тоді нульовий символ пишеться після цих 2 (а нульовим символом буде 3-й символ) .

І це, я вважаю, відповідає на вихідне питання.
ВІДПОВІДЬ:
Якщо копіювання відбувається між об’єктами, що перекриваються, поведінка не визначена.
Якщо nдорівнює 0, тоді у вихідні дані нічого не записується
, якщо помилок кодування не виявлено, вихідні дані ЗАВЖДИ закінчуються нулем ( незалежно від того, чи вміщується вихідний масив у вихідному масиві чи ні ; якщо ні, то деякі символи відкидаються таким чином, що вихідні дані масив ніколи не переповнюється),
інакше (якщо трапляються помилки кодування) вихідні дані можуть не закінчуватись із нулем .

З іншого боку
Останнє речення

Таким чином, вихідне значення, що закінчується нулем, було повністю записано тоді і тільки тоді, коли повернене значення невід'ємне і менше ніж n

дає неоднозначність (або моя англійська мова недостатньо хороша). Я можу інтерпретувати це речення принаймні двома способами:
1. Вихідне значення закінчується нулем тоді і лише тоді, коли повернене значення є невід'ємним і меншеn (що означає, що якщо повернене значення не менше n, тобто вихід (включаючи закінчується нульовим символом) не вміщується в масив, тоді вихід не закінчується нулем ).
2. Вивід завершено (жоден символ не відкинуто) тоді і лише тоді, коли повернене значення невід’ємне і менше ніжn .


Я вважаю, що інтерпретація 1 вище суперечить ВІДПОВІДІ, викликає нерозуміння та тривалі дискусії. Ось чому останнє речення, що описує snprintfфункцію, потребує змін, щоб усунути будь-яку неясність (що дає підстави для написання Пропозиції до мовного стандарту С).
Приклад недвозначного формулювання, на мою думку, можна взяти з http://en.cppreference.com/w/c/io/fprintf (див. 4)), Завдяки @ "Martin Ba" за посилання.

Дивіться також запитання " snprintf: Чи є якісь стандартні пропозиції C / плани змінити опис цієї функції? ".


4
Ваша інтерпретація 1 мені здається зовсім не правдоподібною. Я аналізую це речення як "Висновок (який, до речі, закінчується нулем) був повністю написаний, якщо ...", що я можу зрозуміти лише як №2.
zwol

1
Заперечення пропозиції "нульовий термін виводу був повністю написаний" є "нульовий термін виведення не був повністю записаний". Нічого більше. Сам по собі заперечений речення не означає, що що- небудь було написано (сюди входять неповні вихідні дані з нульовим закінченням, неповні вихідні дані, що не закінчуються з нульовим значенням, або безбарвні зелені ідеї). Деяке інше місце в стандарті говорить, що саме пишеться, коли вивід є неповним, і в цьому місці зазначено, що вихідний результат закінчується нулем, якщо він не порожній (n == 0).
п. 'займенники' m.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.