усечение swprintf вызывает неожиданный вывод - PullRequest
0 голосов
/ 06 февраля 2019

Я исправляю устаревший код, который работает на Linux и Windows, и в некоторых случаях буферы, которые должны содержать отформатированное содержимое, меньше этого содержимого.

Код использует swprintf, который согласно документация

размер - до размера - можно записать 1 символ, плюс нулевой терминатор

действительно обрежет строку, но при попытке ее при coliru Я столкнулся с неожиданными результатами:

#include <iostream> 
#include <string> 
#include <cwchar> 

int main()
{

    wchar_t wide[5];

    std::swprintf(wide, sizeof wide/sizeof *wide, L"%ls", L"111111111");

    std::wcout << wide;
}

приведет к 1111??, но

#include <iostream> 
#include <string> 
#include <cwchar> 

int main()
{

    wchar_t wide[20];

    std::swprintf(wide, sizeof wide/sizeof *wide, L"%ls", L"111111111");

    std::wcout << wide;
}

Работает просто отлично.

Что не так?

PS Я бы хотел изменить все на потоки / строки C ++, но не могу, wchar_t массивы используются везде

1 Ответ

0 голосов
/ 06 февраля 2019

tl; dr: По той или иной причине эта семантика нулевого завершения зависит от успешного вызова функции, а для swprintf она успешна только в том случае, если буфер достаточно большой.Следовательно, массив в вашей первой попытке не заканчивается нулем.


Это тонкий, но swprintf не похож на snprintf.Он не пишет «не более N-1 символов» и считает его успешным во всех случаях.

Вот что говорится в той же документации о возвращаемом значении swprintf:

Возвращаемое значение: Количество написанных широких символов (не считая завершающих нулевых широких символов), если успешно или отрицательное значение, если произошла ошибка кодирования или если число генерируемых символов было равно или больше размера (включая, когда размер равен нулю)

И, действительно, Ваша попытка возвращает -1 .

Из этого (и из примечания под этой цитатой) мы можемубедитесь, что swprintf считает операцию сбой , если в предоставленном выходном буфере недостаточно байт.Он не будет переполнять этот буфер, но он также может не завершить свою работу, и его работа включает в себя написание терминатора NULL.Без этого NULL-терминатора wchar_t*, который вы [эффективно] передаете на std::wcout, выйдет за пределы, и ваша программа будет иметь неопределенное поведение.


Я допускаю, что это при случайном чтенииможет показаться противоречащим семантике, окружающей параметр size, для которого C11 сообщает:

записывается не более n широких символов, включая завершающий нулевой широкий символ, который всегда добавляется(если n не равен нулю).

… без указания каких-либо условий того, был ли вызов функции в противном случае успешным.

Может существовать область для вызова этого редакционного дефекта встандарт, или ошибка реализации. Но , даже если ни один из них не был истинным, ваш вызов функции был признан неудачным, и я не думаю, что вы должны полагаться на результат соответствующим образом.

Мы можем по крайней мере увидеть, что libc намерение соответствует приведенному выше сокращению, начиная с этой страницы руководства по функциям форматированного вывода :

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


Вам нужно учесть вышеупомянутое примечание:

В то время как узкие строки предоставляют стандартное:: snprintf, который позволяет определить требуемый размер выходного буфера, нет эквивалента для широких строк, и для определения размера буфера программе может потребоваться вызвать std :: swprintf, проверить значение результата иперераспределить больший буфер, повторить попытку до успешного завершения.

… или вообще переключиться на другие функции.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...