'strncpy' против 'sprintf'

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

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

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

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

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

or

strncpy(myarray, str, 8);

?


person Vijay    schedule 05.09.2012    source источник
comment
Не забудьте добавить '\0' после вызова strncpy. Само по себе это может не получиться.   -  person Thomas Padron-McCarthy    schedule 05.09.2012
comment
Лучше в каком измерении? Производительность - тогда эталон! Хотя strncpy должен быть быстрее - он делает меньше. Не забудьте добавить \0   -  person Drakosha    schedule 05.09.2012
comment
Это сообщение в блоге довольно хорошо объясняет различия. Самая большая разница для большинства людей заключается в том, что snprintf говорит вам, сколько символов было написано, и вам не нужно беспокоиться о пропущенных нулевых терминаторах, когда не хватает места.   -  person verdesmarald    schedule 05.09.2012
comment
тогда strcpy лучше, чем strncpy, поскольку он также копирует завершающий нулевой символ в исходной строке?   -  person Vijay    schedule 05.09.2012
comment
Да, лучше по производительности и удобству использования!   -  person Vijay    schedule 05.09.2012
comment
@sarathi, только если вы уверены, что переполнение буфера невозможно.   -  person obataku    schedule 05.09.2012
comment
Вы можете безопасно использовать strcpy() только в том случае, если знаете длину исходной и целевой строк. Если вы знаете длину исходного кода, вы можете использовать memmove() или memcpy() вместо strcpy(). Обратите внимание, что strncpy() может быть очень неэффективным, если длина, указанная вами в качестве третьего аргумента, значительно превышает длину строки, потому что strncpy() null дополняет вывод до полной длины. И, как указывали другие, strncpy() не гарантирует, что вывод завершается нулем.   -  person Jonathan Leffler    schedule 05.09.2012
comment
Причина, по которой strncpy может не добавлять завершающий символ '\0', заключается в том, что изначально он был создан для помещения текста в небольшие поля фиксированной длины, которые не нужно было завершать, если они были полностью заполнены.   -  person Some programmer dude    schedule 05.09.2012


Ответы (4)


Ни то, ни другое не должно использоваться вообще.

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

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

Так что же использовать вместо этого?

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

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

person R.. GitHub STOP HELPING ICE    schedule 05.09.2012
comment
+1. Я собирался возразить, что strncpy не опасен, потому что он идеально подходит для своего предполагаемого использования, но я вижу, что его так часто неправильно понимают, неправильно используют и злоупотребляют, что проще просто сказать, что он опасен. - person CB Bailey; 05.09.2012
comment
Я думаю, что предполагаемое использование strncpy - это в основном концепция, которая изжила свою полезность... - person R.. GitHub STOP HELPING ICE; 05.09.2012

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

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

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

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

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

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

ИЗМЕНИТЬ

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

Измерения strcpy и sprintf на целевом Freescale HCS12, компилятор Freescale Codewarrior 5.1. Используя реализацию sprintf C90, 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 
person Lundin    schedule 05.09.2012
comment
Этот ответ полон плохих советов и дезинформации. Нет абсолютно никакого оправдания тому, почему stdio не следует использовать в производственном коде, и на самом деле я бы сказал, что snprintf — это единственная строковая функция из стандартной библиотеки, которую вы когда-либо должны использовать в безопасном коде, который обрабатывает струны. Затраты на производительность были сильно преувеличены (помимо постоянных накладных расходов, это не должно занимать больше времени, чем strlen/memcpy и, возможно, даже использует их внутренне), как и опасности (любой современный компилятор будет помечать несоответствия строки формата для вас с предупреждениями) . - person R.. GitHub STOP HELPING ICE; 05.09.2012
comment
@R.. Что касается безопасности и производственного кода, я процитировал очень известный и широко признанный авторитет для критически важных приложений, разделяющий это убеждение, а именно правило 20.9 MISRA-C: 2004. Если вы считаете иначе, ваше субъективное мнение как случайного интернет-человека не имеет большого веса, процитируйте другой авторитет, подтверждающий ваше мнение. - person Lundin; 05.09.2012
comment
Это стандарт для написания кода встроенных систем для использования в автомобильных ЭБУ и т.п. Это не имеет отношения к программированию на C общего назначения, и я бы даже сказал, что системы, к которым это относится, вероятно, вообще не имеют отношения к работе со строками... - person R.. GitHub STOP HELPING ICE; 05.09.2012
comment
@R.. Он используется для критически важного программирования любого характера, он расширился далеко за пределы автомобильной отрасли. Он применяется ко всем программам, в которых нежелательны ошибки, и на данный момент он, безусловно, является наиболее широко признанным стандартом программирования на C. Хотя не стесняйтесь ссылаться на другие авторитеты. Что такое стандарт кодирования, который вы лично используете, скажем, о stdio? - person Lundin; 05.09.2012
comment
@R.. Что касается производительности, мой ответ был отредактирован с учетом измерений, сделанных на платформе C90 с достаточно эффективным кодом. - person Lundin; 05.09.2012
comment
И ваши измерения показывают минимальную стоимость, как и ожидалось. Единственная причина, по которой стоимость наивно выглядит нетривиальной, заключается в том, что ваши строки такие короткие. Если вы попробуете строку 1k или 20k, вы обнаружите, что производительность почти одинакова. - person R.. GitHub STOP HELPING ICE; 05.09.2012
comment
Что касается MISRA-C, все, что я могу сказать, это то, что если вы не применяете его к задачам, где обработка строк, числовое базовое преобразование/форматирование в виде строк и т. д. не требуется, правило против stdio просто в корне ошибочно. Воспроизведение этой функции без внесения опасных ошибок является весьма нетривиальной задачей, и все, что может сделать запрет stdio, когда эта функциональность необходима, — это побудить программистов написать свой собственный ошибочный код для использования вместо stdio... - person R.. GitHub STOP HELPING ICE; 05.09.2012
comment
@R.. Это изменит только количество инструкций. Все остальные накладные расходы, такие как размер программы, остаются неизменными независимо от длины строки. - person Lundin; 05.09.2012
comment
Что касается написания собственного глючного кода... если программист не может написать такую ​​тривиальную вещь, как собственную функцию strcpy/memcpy, ему, вероятно, следует искать другую профессию. - person Lundin; 05.09.2012
comment
За исключением встроенных систем, почти никто в наши дни не использует статические двоичные файлы. Даже если они есть, 1488 байт — не значительная цена; вам было бы трудно дублировать функциональность в том же размере. В любом случае, я думаю, что это заниженная оценка, вызванная неполной реализацией; для правильной реализации printf требуется не менее 3-4 КБ, если вы хотите правильно выполнять операции с плавающей запятой. Но даже при такой стоимости это довольно тривиально. - person R.. GitHub STOP HELPING ICE; 05.09.2012
comment
@Lundin: я не говорю о написании собственного strcpy. Я говорю о сборке строк путем объединения нескольких строк или числовых входных данных вместе со строками. Конечно, вы можете сделать это самостоятельно с помощью strlen и memcpy, но каждый раз, когда вы это делаете, существует нетривиальная вероятность совершить ошибку (ошибки за единицу, опечатки и т. д.), которых не было бы, если бы вы использовали стандарт, безопасная идиома. Кроме того, вызывающий код обычно становится меньше при использовании snprintf по сравнению со строкой вызовов strlen и memcpy. - person R.. GitHub STOP HELPING ICE; 05.09.2012
comment
+1, по крайней мере, за хорошую попытку объяснить факты и потратить много времени на комментарии. - person Vijay; 05.09.2012

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

person Thomas Padron-McCarthy    schedule 05.09.2012

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

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

So:

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

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

person MikeW    schedule 10.04.2019