Является ли snprintf() ВСЕГДА нулевым завершением?
Является ли 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, который не является стандартным поведением.
Ответы
Ответ 1
Как другие ответы устанавливают: :
snprintf
... Записывает результаты в буфер символьной строки. (...) будет завершен нулевым символом, если buf_size не равен нулю.
Итак, все, что вам нужно позаботиться, это то, что вы не передаете ему нулевой буфер, потому что (очевидно) он не может записать нуль в "никуда".
Однако остерегай, что в Microsoft library нет функции с именем snprintf
, но вместо этого исторически только была функция с именем _snprintf
(обратите внимание на подчеркивание), что делает не добавлять завершающий нуль. Здесь документы (VS 2012, ~~ VS 2013):
http://msdn.microsoft.com/en-us/library/2ts7cx93%28v=vs.110%29.aspx
Возвращаемое значение
Пусть len - длина форматированной строки данных (не включая завершающий нуль). len и count в байтах для _snprintf, wide символов для _snwprintf.
-
Если len < count, тогда len-символы хранятся в буфере, а null-terminator добавляется, и возвращается len.
-
Если len = count, тогда len-символы хранятся в буфере, нет null-terminator добавляется, и возвращается len.
-
Если len > count, то счетчики хранятся в буфере, нет null-terminator добавляется, и возвращается отрицательное значение.
(...)
Visual Studio 2015 (VC14), по-видимому, представил совместимую функцию snprintf
, но прежняя с ведущим подчеркиванием и не нулевым завершающим поведением все еще существует
Функция snprintf
усекает вывод, когда len больше чем или равный счету, помещая нуль-терминатор в buffer[count-1]
. (...)
Для всех функций, отличных от snprintf
, если len = count, len символы хранятся в буфере, не добавляется нуль-терминатор, (...)
Ответ 2
Согласно snprintf (3) manpage.
Функции snprintf()
и vsnprintf()
пишутся не более size
байтов (включая конечный нулевой байт ('\ 0')) до str
.
Итак, да, не нужно прекращать, если size >= 1.
Ответ 3
В соответствии со стандартом C, если размер буфера не равен 0, vsnprintf()
и snprintf()
null завершает свой вывод.
Функция snprintf()
должна быть эквивалентна sprintf()
, с добавлением n аргумента, который определяет размер буфера, на который ссылается s. Если n равно нулю, ничего не должно быть записано и s может быть нулевым указателем. В противном случае выходные байты, выходящие за пределы n-1, должны быть отброшены вместо того, чтобы записываться в массив, а нулевой байт записывается в конце байтов, фактически записанных в массив.
Итак, если вам нужно знать, как большой буфер выделяется, используйте нулевой размер, и затем вы можете использовать нулевой указатель в качестве адресата. Обратите внимание, что я связан с страницами 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
Некоторые более старые версии SunOS сделали странные вещи с snprintf и, возможно, не NUL-завершили вывод и имели возвращаемые значения, которые не соответствовали тем, что делали все остальные, но все, что было выпущено за последние 10 лет, было что делает C99.
Ответ 5
Неоднозначность начинается с самого стандарта 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
й отбрасываются, а не записываются в массив, а нулевой символ записывается в конце символов, фактически записанных в массив
Говорит, что
if (s
указывает на массив длиной 3 символа и) n
равно 3, тогда будут записаны 2 символа, а символы, выходящие за пределы второго, будут отброшены; то нулевой символ записывается после этих 2 (и нулевой символ будет написан третьим символом).
И это, на мой взгляд, отвечает на исходный вопрос.
ОТВЕТ:
Если копирование происходит между перекрывающимися объектами, поведение не определено.
Если n
равно 0, то на вывод ничего не записывается
в противном случае, если ошибки кодирования не встречаются, выход ALWAYS завершает нуль (независимо от того, соответствует ли выход в выходном массиве или нет, а если нет, то некоторые символы отбрасываются, так что выходной массив никогда не переполняется)
в противном случае (если ошибки кодирования встречаются), выход может оставаться не нулевым.
С другой стороны
Последнее предложение
Таким образом, вывод с нулевым завершением был полностью записан тогда и только тогда, когда возвращаемое значение неотрицательно и меньше n
дает двусмысленность (или мой английский недостаточно хорош). Я могу интерпретировать это предложение по крайней мере двумя способами:
1. Выход завершен с нулевым значением тогда и только тогда, когда возвращаемое значение неотрицательно и меньше n
(это означает, что если возвращаемое значение не меньше n
, то есть выход (включая завершающий нулевой символ) не помещается в массив, то вывод не заканчивается нулем).
2. Выход завершен (ни один символ не был отброшен) тогда и только тогда, когда возвращаемое значение неотрицательно и меньше n
.
Я считаю, что интерпретация 1 выше противоречит ОТВЕТ, вызывает непонимание и длительные дискуссии. Вот почему последнее предложение, описывающее функцию snprintf
нуждается в изменении, чтобы устранить любую двусмысленность (что дает основания для написания предложения на стандарт языка C).
Пример недвусмысленной формулировки, я считаю, может быть взят из http://en.cppreference.com/w/c/io/fprintf (см. 4)
), благодаря @"Martin Ba" для ссылки.
См. Также вопрос " snprintf: Существуют ли какие-либо стандартные предложения/планы C, чтобы изменить описание этой функции? ".