Является ли snprintf () ВСЕГДА нулевым завершением? - PullRequest
68 голосов
/ 10 октября 2011

Всегда ли snprintf завершает нулевой буфер назначения?

Другими словами, достаточно ли этого:

char dst[10];

snprintf(dst, sizeof (dst), "blah %s", somestr);

или вам нужно сделать это, если somestr достаточно длинный?

char dst[10];

somestr[sizeof (dst) - 1] = '\0';
snprintf(dst, sizeof (dst) - 1, "blah %s", somestr);

Меня интересует, что говорит стандарт, и что может делать какой-то популярный libc, что не является стандартным поведением.

Ответы [ 5 ]

62 голосов
/ 25 октября 2012

Как показывают другие ответы: должен :

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

Поэтому все, что вам нужно, - это не передавать ему буфер нулевого размера, потому что(очевидно) он не может записать ноль в «никуда».


Однако остерегается , что в библиотеке Microsoft нет функции, называемой snprintf, но вместо этого исторически * только 1022 * имеет функцию с именем _snprintf (примечание с начальным подчеркиванием), которая не добавляет завершающий ноль.Вот документы (VS 2012, ~~ VS 2013):

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

Возвращаемое значение

Пусть len будет длиной строки форматированных данных (не включая завершающий ноль).len и count в байтах для _snprintf, широкие символы для _snwprintf.

  • Если len

  • Если len = count, то символы len сохраняются в буфере, никакой нулевой терминатор не добавляется, и возвращается len.

  • Если len> count, тогда символы счета сохраняются в буфере, никакой нулевой терминатор не добавляется, и возвращается отрицательное значение.

(...)

Visual Studio 2015 (VC14), по-видимому, ввел соответствующую функцию snprintf, но унаследованную с начальным подчеркиванием и не завершающее нуль поведение все еще там:

Функция snprintf обрезает вывод, когда len больше или равно count, помещая нулевой терминаторна buffer[count-1].(...)

Для всех функций другие , чем snprintf, если len = count, символы len сохраняются в буфере, никакой нулевой терминатор не добавляется , (...)

17 голосов
/ 10 октября 2011

Согласно руководству snprintf (3).

Функции snprintf() и vsnprintf() записывают не более size байтов (включая завершающий нулевой байт ('\ 0')) в str.

Так что, да, нет необходимости завершать, если размер> = 1.

10 голосов
/ 10 октября 2011

Согласно стандарту C, если размер буфера не равен 0, vsnprintf() и snprintf() null прекращает вывод.

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

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

Функциональность, описанная на этой справочной страницесоответствует стандарту ISO C.Любой конфликт между требованиями, описанными здесь, и стандартом ISO C является непреднамеренным.Этот том POSIX.1-2008 относится к стандарту ISO C.

С осторожностью относитесь к версии Microsoft vsnprintf().Он определенно ведет себя иначе, чем стандартная версия C, когда в буфере недостаточно места (он возвращает -1, когда стандартная функция возвращает требуемую длину).Не совсем ясно, что нулевая версия Microsoft прекращает свой вывод в условиях ошибки, тогда как стандартная версия C делает это.

Обратите внимание также на ответы на Используете ли вы безопасные функции TR 24731? (см. MSDN для версии Microsoft vsprintf_s()) и Mac для безопасных альтернатив небезопасным функциям стандартной библиотеки C?

4 голосов
/ 24 мая 2018

Неопределенность начинается с самого стандарта 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-1 st: отбрасывается, а не записывается в массив, и нулевой символ записывается в конце символов, фактически записанных в массив. Если копирование происходит между объектами, которые перекрываются, поведение не определено.
Возвращает
3 Функция snprintf возвращает количество символов, которое было бы записано, если бы n было достаточно большим, не считая завершающий нулевой символ, или отрицательное значение, если произошла ошибка кодирования. Таким образом, вывод с нулевым символом в конце был полностью записан тогда и только тогда, когда возвращаемое значение неотрицательно и меньше n.

С одной стороны предложение

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

говорит, что
если (s указывает на массив из 3 символов, а) n равно 3, то будет записано 2 символа, а символы за пределами 2 отбрасываются ; затем после этих 2 записывается нулевой символ (и третьим символом будет записан нулевой символ) .

И это, я полагаю, отвечает на первоначальный вопрос.
ОТВЕТ:
Если копирование происходит между объектами, которые перекрываются, поведение не определено.
Если n равно 0, то на выход ничего не записывается
в противном случае, если ошибок кодирования не обнаружено, вывод ВСЕГДА заканчивается нулем ( независимо от того, помещается ли вывод в выходной массив или нет ; если нет, то некоторые символы отбрасываются так, что выходной массив никогда не переполняется),
в противном случае (если обнаружены ошибки кодирования), вывод может остаться не завершенным нулем .

С другой стороны
Последнее предложение

Таким образом, завершенный нулем вывод был полностью записан тогда и только тогда, когда возвращаемое значение неотрицательно и меньше n

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


Я полагаю, что приведенное выше толкование 1 противоречит ОТВЕТУ, вызывает недопонимание и длительные дискуссии. Вот почему последнее предложение, описывающее функцию snprintf, нуждается в изменении, чтобы устранить любую двусмысленность (что дает основание для написания предложения к стандарту языка Си).
Я полагаю, что пример недвусмысленной формулировки можно взять из http://en.cppreference.com/w/c/io/fprintf (см. 4)), спасибо @ "Martin Ba" за ссылку.

См. Также вопрос « snprintf: Существуют ли какие-либо Стандартные предложения / планы C по изменению описания этой функции? ».

4 голосов
/ 10 октября 2011

Некоторые старые версии SunOS делали странные вещи с snprintf и могли не выводить NUL-вывод и иметь возвращаемые значения, которые не соответствовали тому, что делали все остальные, но все, что было выпущено за последние 10 лет, былоделать то, что говорит C99.

...