2009-10-01 2 views
9

Следующий код вызывает ошибку и убивает мое приложение. Это имеет смысл, поскольку буфер составляет всего 10 байтов, а текст - 22 байта (переполнение буфера).sprintf_s с слишком маленьким буфером

char buffer[10];  
int length = sprintf_s(buffer, 10, "1234567890.1234567890."); 

Как я могу уловить эту ошибку, чтобы я мог сообщить об этом, вместо того, чтобы разбивать мое приложение?

Edit:

После прочтения комментариев ниже я пошел с _snprintf_s. Если он возвращает значение -1, буфер не обновлялся.

length = _snprintf_s(buffer, 10, 9, "123456789"); 
printf("1) Length=%d\n", length); // Length == 9 

length = _snprintf_s(buffer, 10, 9, "1234567890.1234567890."); 
printf("2) Length=%d\n", length); // Length == -1 

length = _snprintf_s(buffer, 10, 10, "1234567890.1234567890."); 
printf("3) Length=%d\n", length); // Crash, it needs room for the NULL char 
+0

Передача размера буфера и размера буфера минус одна, является тупой и подверженной ошибкам. Вы должны предпочесть вариант, который я описываю ниже: length = _snprintf_s (buffer, _TRUNCATE, "1234567890.1234567890."); Поскольку параметр первого размера не указан, компилятор использует перегрузку шаблона, которая задает размер. _TRUNCATE - это особое значение, которое делает то, что он говорит. Никаких магических чисел, и теперь ваш код безопасен, поддерживается и является хорошим примером. Если вам понравился этот комментарий и _snprintf_s, тогда вы должны выбрать мой ответ, а не опасный ответ snprintf/_snprintf. –

ответ

5

Вместо sprintf_s, вы можете использовать snprintf (a.k.a _snprintf на окнах).

#ifdef WIN32 
#define snprintf _snprintf 
#endif 

char buffer[10];  
int length = snprintf(buffer, 10, "1234567890.1234567890."); 
// unix snprintf returns length output would actually require; 
// windows _snprintf returns actual output length if output fits, else negative 
if (length >= sizeof(buffer) || length<0) 
{ 
    /* error handling */ 
} 
+4

Существует также snprintf_s. – Joe

+2

Примечание: в случае безопасности, если места недостаточно, содержимое буфера не может быть завершено нулем. – Managu

+2

@Managu: если MS заявила о соответствии C99, чего нет, это утверждение было бы фиктивным; для стандартного C99 требуется, чтобы snprintf() нулевое завершало строку, если длина строки не равна 0. Раздел 7.19.6.5: Если n равно нулю, ничего не записывается ... В противном случае выходные символы за пределами n-1 равны , чем записывается в массив, а нулевой символ записывается в конце символов, фактически записанных в массив. Если копирование происходит между объектами , которые перекрываются, поведение не определено. –

0

Из MSDN:

Другой Основное различие между sprintf_s и Sprintf является то, что sprintf_s принимает параметр длины, указывающий размер выходного буфера в символах. Если буфер слишком мал для печатаемого текста, тогда буфер устанавливается в пустую строку и вызывается недопустимый обработчик параметров. В отличие от snprintf, sprintf_s гарантирует, что буфер будет заканчиваться на нуль (если размер буфера не равен нулю).

В идеале, то, что вы написали, должно работать правильно.

+4

По умолчанию «недопустимый обработчик параметров» завершает процесс. –

+0

true, но легко установить тот, который этого не делает, что приводит к тому, что sprintf_s возвращает -1, если буфер слишком мал – stijn

0

Похоже, вы пишете на MSVC?

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

Как предложил Л.Бушкин, вам гораздо лучше использовать классы, управляющие строками.

16

Это по дизайну. Вся точка sprintf_s и другие функции семейства *_s - это перехватывание ошибок переполнения буфера и их обработка как нарушения предварительных условий. Это означает, что они не предназначены для восстановления. Это предназначено только для обнаружения ошибок - вы никогда не должны звонить sprintf_s, если знаете, что строка может быть слишком большой для целевого буфера. В этом случае сначала используйте strlen, чтобы проверить и решить, нужно ли вам обрезать.

+0

Я не согласен. Весьма разумно вызвать sprintf_s с слишком маленьким буфером назначения, если вы используете флаг _TRUNCATE, чтобы указать это. Хорошо, технически _TRUNCATE требует использования snprintf_s вместо sprintf_s, но моя точка в основном стоит. Использование strlen часто неприменимо или неудобно, но использование _TRUNCATE часто тривиально и уместно. –

+0

Я думаю, что использование 'snprintf_s' является решающим отличием, а не просто техническим. –

+0

Это решающее различие, безусловно. Но я думаю, что ваш ответ заставляет казаться, что семейство функций не может усекать, что может ввести в заблуждение. –

0

См. Раздел 6.6.1 от TR24731, который является версией функциональности, реализованной Microsoft, ISO C Committee. Он предоставляет функции set_constraint_handler(), abort_constraint_handler() и ignore_constraint_handler() функции.

Есть комментарии Павла Минаева о том, что реализация Microsoft не соответствует предложению TR24731 (который является «Техническим отчетом по типу 2»), поэтому вы не можете вмешиваться, или вам, возможно, придется что-то сделать отличное от того, что указывает TR. Для этого внимательно изучите MSDN.

+1

К сожалению, MSVC не реализует TR24731 полностью - в частности, он не выполняет специально функции, которые вы ссылаетесь (также их имена также заканчиваются на '_s' - то есть' set_constraint_handler_s'). –

+1

Но в соответствии с http://msdn.microsoft.com/en-us/library/ksazx244%28VS.80%29.aspx существует функция _set_invalid_parameter_handler(), которая может использоваться для изменения поведения по умолчанию прерывания программы , –

5

Это работает с VC++ и даже безопаснее, чем при использовании snprintf (и, конечно, безопаснее, чем _snprintf):

флаг
void TestString(const char* pEvil) 
{ 
    char buffer[100]; 
    _snprintf_s(buffer, _TRUNCATE, "Some data: %s\n", pEvil); 
} 

_TRUNCATE указывает на то, что строка должна быть усечен. В этой форме размер буфера фактически не передается, что (как ни парадоксально!) является тем, что делает его настолько безопасным. Компилятор использует магию шаблонов для вывода размера буфера, что означает, что он не может быть задан неправильно (неожиданно распространенная ошибка). Этот метод может быть применен для создания других безопасных оболочек строк, как описано в моем сообщении в блоге: https://randomascii.wordpress.com/2013/04/03/stop-using-strncpy-already/

+0

, смотря на документацию MSDN от _snprintf_s, похоже, что вы забыли аргумент в вызове _snprintf_s. Этот аргумент должен появиться между буфером и _TRUNCATE и называется sizeOfBuffer – user1741137

+1

Я не забыл аргумент - этот код компилируется и совершенно безопасен. Вам нужно перечитать документацию. Я использую переопределение шаблона в _snprintf_s, которое сообщает компилятору вывести размер буфера. Я видел сотни мест, где программисты явно передавали размер буфера и передавали * неправильный * размер. Только благодаря тому, что компилятор выводит размер буфера, эту серьезную ошибку можно избежать. Я упомянул об этой технике в статье, с которой я связан в своем решении. Настоятельно рекомендуется. –

Смежные вопросы