Является ли 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, ...);
Описание
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. Вывод завершен (никакие символы не были отброшены) тогда и только тогда, когда возвращаемое значение неотрицательно и меньше чем n,


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

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

Другие вопросы по тегам