Подтвердить что ты не робот

'strncpy' vs. 'sprintf'

Я вижу много sprintf, используемых в моих приложениях для копирования строки.

У меня есть массив символов:

char myarray[10];
const char *str = "mystring";

Теперь, если я хочу скопировать строку str в myarray, лучше использовать:

sprintf(myarray, "%s", str);

или

strncpy(myarray, str, 8);

?

4b9b3361

Ответ 1

Ничего не следует использовать.

  • sprintf является опасным, устаревшим и заменяется на snprintf. Единственный способ безопасного использования старого sprintf со строковыми вводами - либо измерить их длину перед вызовом sprintf, который является уродливым и подверженным ошибкам, либо путем добавления спецификатора точности поля (например, %.8s или %.*s с дополнительным целым аргументом для ограничения размера). Это также является уродливым и подверженным ошибкам, особенно если задействовано более одного спецификатора %s.

  • strncpy также опасен. Это не версия с ограниченным размером буфера strcpy. Это функция для копирования символов в массив с фиксированной длиной, с нулевым заполнением (в отличие от нулевого), где источником может быть строка C или массив символов фиксированной длины, по меньшей мере, размер адресата. Его предполагаемое использование предназначалось для устаревших таблиц каталогов unix, записей в базе данных и т.д., Которые работали с текстовыми полями фиксированного размера и не хотели тратить даже один байт на диск или в память для нулевого завершения. Это может быть неправильно использовано в качестве ограниченного размера буфера strcpy, но это вредно по двум причинам. Прежде всего, он не может быть отменен, если весь буфер используется для строковых данных (т.е. Если длина исходной строки не меньше, чем буфер dest). Вы можете добавить завершение назад самостоятельно, но это уродливо и подвержено ошибкам. И во-вторых, strncpy всегда заполняет полный буфер назначения нулевыми байтами, когда исходная строка короче, чем выходной буфер. Это просто пустая трата времени.

Итак, что вы должны использовать вместо этого?

Некоторые люди любят функцию BSD strlcpy. Семантически он идентичен snprintf(dest, destsize, "%s", source), за исключением того, что возвращаемое значение size_t и не накладывает искусственный предел INT_MAX на длину строки. Тем не менее, большинству популярных систем, отличных от BSD, не хватает strlcpy, и легко сделать опасные ошибки, записывая свои собственные, поэтому, если вы хотите их использовать, вы должны получить безопасную, известную рабочую версию из надежного источника.

Мое предпочтение состоит в том, чтобы просто использовать snprintf для любой нетривиальной строковой конструкции и strlen + memcpy для некоторых тривиальных случаев, которые были измерены как критические по производительности. Если вы привыкли правильно использовать эту идиому, становится практически невозможно случайно написать код со связанными с строкой уязвимостями.

Ответ 2

Различные версии printf/scanf являются невероятно медленными функциями по следующим причинам:

  • Они используют списки переменных аргументов, что делает передачу параметров более сложной. Это делается с помощью различных неясных макросов и указателей. Все аргументы должны анализироваться во время выполнения, чтобы определить их типы, что добавляет дополнительный код служебной информации. (Списки VA также довольно избыточная функция языка, а также опасная, так как она имеет более сложную типизацию, чем простая передача параметров).

  • Они должны обрабатывать много сложного форматирования и поддерживать все разные типы. Это добавляет много накладных расходов на функцию. Поскольку все оценки типов выполняются во время выполнения, компилятор не может оптимизировать часть функций, которые никогда не используются. Поэтому, если вы хотите печатать целые числа с printf(), вы получите поддержку чисел с плавающей запятой, сложной арифметики, обработки строк и т.д. И т.д., Связанных с вашей программой, в качестве полной траты пространства.

  • С другой стороны, функции, такие как strcpy() и, в частности, memcpy(), сильно оптимизированы компилятором, часто реализуемым в встроенной сборке для максимальной производительности.

Некоторые измерения, которые я когда-то сделал на barebone 16-разрядных младших микроконтроллерах, приведены ниже.

Как правило, вы никогда не должны использовать stdio.h в любом виде производственного кода. Он должен рассматриваться как библиотека для отладки/тестирования. MISRA-C: 2004 запрещает stdio.h в производственном коде.

ИЗМЕНИТЬ

Заменены субъективные числа фактами:

Измерения strcpy по сравнению с sprintf на целевом Freescale HCS12, компилятор Freescale Codewarrior 5.1. Используя C90-реализацию sprintf, C99 будет еще более неэффективным. Все оптимизации включены. Был протестирован следующий код:

  const char str[] = "Hello, world";
  char buf[100];

  strcpy(buf, str);
  sprintf(buf, "%s", str);

Время выполнения, в том числе перетаскивание вкл./выкл. вызова:

strcpy   43 instructions
sprintf  467 instructions

Распределено пространство программы/ПЗУ:

strcpy   56 bytes
sprintf  1488 bytes

ОЗУ/пространство стека выделено:

strcpy   0 bytes
sprintf  15 bytes

Количество внутренних вызовов функций:

strcpy   0
sprintf  9

Глубина стека вызовов функций:

strcpy   0 (inlined)
sprintf  3 

Ответ 3

Я бы не использовал sprintf только для копирования строки. Это излишне, и кто-то, кто читает этот код, наверняка остановится и задаст себе вопрос, почему я это сделал, и если они (или я) чего-то не хватает.

Ответ 4

Есть один способ использовать sprintf() (или, будучи параноиком, snprintf()), чтобы сделать "безопасную" копию строки, которая усекается вместо того, чтобы переполнять поле или оставлять его без NUL-завершенного.

То есть использовать символ формата "*" в качестве "точности строки" следующим образом:

Так:

char dest_buff[32];
....
sprintf(dest_buff, "%.*s", sizeof(dest_buff) - 1, unknown_string);

Это помещает содержимое unknown_string в dest_buff, оставляя место для завершающего NUL.