Является ли 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, что не является стандартным поведением.
5 ответов
Как и другие ответы установить: он должен:
snprintf
... Записывает результаты в буфер символьных строк. (...) будет оканчиваться нулевым символом, если только buf_size не равен нулю.
Поэтому все, что вам нужно, это позаботиться о том, чтобы вы не передавали ему буфер нулевого размера, потому что (очевидно) он не может записать ноль в "никуда".
Однако следует помнить, что в библиотеке Microsoft нет функции под названием snprintf
но вместо этого исторически была только функция под названием _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 сохраняются в буфере, нулевой терминатор не добавляется, (...)
Согласно snprintf(3) manpage.
Функции
snprintf()
а такжеvsnprintf()
написать максимумsize
байты (включая завершающий нулевой байт ('\0')) вstr
,
Так что, да, нет необходимости завершать, если размер>= 1.
Согласно стандарту 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 NULL прекращает вывод в случае ошибки, в то время как стандартная версия C делает это.
Также обратите внимание на ответы : "Используете ли вы безопасные функции TR 24731? (см. MSDN для версии Microsoft vsprintf_s()
) и решение Mac для безопасных альтернатив небезопасным функциям стандартной библиотеки C?
Некоторые старые версии SunOS делали странные вещи с snprintf и могли не выводить NUL-вывод и иметь возвращаемые значения, которые не соответствовали тому, что делали все остальные, но все, что было выпущено за последние 10 лет, делало то, что C99 говорит.
Неопределенность начинается с самого стандарта 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, ...);
Описание
2snprintf
функция эквивалентнаfprintf
за исключением того, что вывод записывается в массив (определяется аргументомs
), а не в поток. Еслиn
ноль, ничего не написано, иs
может быть нулевым указателем. В противном случае, вывод символов за пределыn-1
st сбрасываются, а не записываются в массив, и нулевой символ записывается в конце символов, фактически записанных в массив. Если копирование происходит между объектами, которые перекрываются, поведение не определено.
Возвращает
3snprintf
Функция возвращает количество символов, которые были бы написаныn
было достаточно большим, не считая завершающий нулевой символ, или отрицательное значение, если произошла ошибка кодирования. Таким образом, завершенный нулем вывод был полностью записан тогда и только тогда, когда возвращаемое значение неотрицательно и меньше чемn
,
С одной стороны, предложение
В противном случае, вывод символов за пределы
n-1
st сбрасываются, а не записываются в массив, и нулевой символ записывается в конце символов, фактически записанных в массив
Говорит, что
если ( s
указывает на массив длиной 3 символа, а) n
3, тогда будут записаны 2 символа, а символы, выходящие за 2-й, отбрасываются; затем после этих 2 пишется нулевой символ (и нулевой символ будет написан как третий символ).
И это, я считаю, отвечает на первоначальный вопрос.
ОТВЕТ:
Если копирование происходит между объектами, которые перекрываются, поведение не определено.
Если n
0, то ничего не записывается на выход
в противном случае, если не обнаружены ошибки кодирования, выходные данные ВСЕГДА заканчиваются нулем (независимо от того, помещается ли выходной массив в выходной массив или нет; если нет, то некоторые символы отбрасываются так, что выходной массив никогда не переполняется),
в противном случае (если обнаружены ошибки кодирования) вывод может остаться не завершенным нулем.
С другой стороны
Последнее предложение
Таким образом, завершенный нулем вывод был полностью записан тогда и только тогда, когда возвращаемое значение неотрицательно и меньше чем
n
дает двусмысленность (или мой английский недостаточно хорош). Я могу интерпретировать это предложение по крайней мере двумя способами:
1. Вывод завершается нулем, если и только если возвращаемое значение неотрицательно и меньше чем n
(что означает, что если возвращаемое значение не меньше n
т. е. выходные данные (включая завершающий нулевой символ) не помещаются в массив, тогда выходные данные не заканчиваются нулем).
2. Вывод завершен (никакие символы не были отброшены) тогда и только тогда, когда возвращаемое значение неотрицательно и меньше чем n
,
Я полагаю, что толкование 1 выше противоречит ОТВЕТУ, вызывает недопонимание и длительные дискуссии. Вот почему последнее предложение, описывающее snprintf
Функция нуждается в изменении, чтобы устранить любую двусмысленность (которая дает основания для написания Предложения к Стандарту языка Си).
Пример не двусмысленной формулировки, я полагаю, может быть взят из http://en.cppreference.com/w/c/io/fprintf (см. 4)
), спасибо @"Martin Ba" за ссылку.
См. Также вопрос " snprintf: Существуют ли какие-либо Стандартные предложения / планы C по изменению описания этой функции? ".