C strcpy() - зло?

Некоторые люди, кажется, думают, что функция strcpy() в C плохая или злая. Хотя я признаю, что обычно лучше использовать strncpy(), чтобы избежать переполнения буфера, следующее (реализация функции strdup() для тех, кому не повезло с ней) безопасно использует strcpy() и никогда не должно переполняться:

char *strdup(const char *s1)
{
  char *s2 = malloc(strlen(s1)+1);
  if(s2 == NULL)
  {
    return NULL;
  }
  strcpy(s2, s1);
  return s2;
}

*s2 гарантированно имеет достаточно места для хранения *s1, а использование strcpy() избавляет нас от необходимости сохранять результат strlen() в другой функции для последующего использования в качестве ненужного (в данном случае) параметра длины для strncpy(). Тем не менее, некоторые люди пишут эту функцию с strncpy() или даже memcpy(), которые оба требуют параметра длины. Я хотел бы знать, что люди думают об этом. Если вы считаете, что strcpy() в определенных ситуациях безопасен, так и скажите. Если у вас есть веская причина не использовать strcpy() в этой ситуации, укажите ее — мне хотелось бы знать, почему в подобных ситуациях лучше использовать strncpy() или memcpy(). Если вы думаете, что strcpy() подходит, но не здесь, объясните.

По сути, я просто хочу знать, почему некоторые люди используют memcpy(), когда другие используют strcpy(), а третьи используют простое strncpy(). Есть ли какая-то логика в том, чтобы предпочесть один из трех (без учета проверок буфера первых двух)?


person Chris Lutz    schedule 04.03.2009    source источник
comment
В многопоточной среде редко бывает разумно, чтобы каждая библиотечная функция сама обрабатывала блокировку.   -  person    schedule 04.03.2009
comment
Поскольку strlen может давать сбой или возвращать неоправданно большое значение при неправильном завершении s1, ваш strdup небезопасен.   -  person kmarsh    schedule 11.06.2009
comment
Если «malloc» заменяет «s1», то нет гарантии, что буфер будет достаточно большим, когда вы позже скопируете в него с помощью «strcpy». Предположим, что 's1' является указателем на строку, поддерживаемую внутри системы управления памятью, возможно, последний раз, когда вызывалась 'malloc'.   -  person David Schwartz    schedule 28.08.2011


Ответы (16)


memcpy может быть быстрее, чем strcpy и strncpy, потому что ему не нужно сравнивать каждый скопированный байт с '\0', и потому что он уже знает длину копируемого объекта. Его можно реализовать аналогичным образом с устройством Даффа или использовать инструкции ассемблера, копирующие несколько байтов за раз, например movsw и movsd

person dmityugov    schedule 04.03.2009
comment
Это, безусловно, причина использования memcpy здесь, это не имеет ничего общего с безопасностью. Было бы интересно проанализировать производительность, чтобы определить, действительно ли memcpy работает быстрее или нет. Как и вы, я предполагаю, что это будет для большинства случаев, но, возможно, для очень маленьких строк strcpy может оказаться быстрее. - person benno; 05.01.2013

Я соблюдаю правила, описанные здесь. Позвольте мне процитировать его

strncpy изначально был введен в библиотеку C для работы с полями имен фиксированной длины в таких структурах, как записи каталогов. Такие поля не используются так же, как строки: завершающий нуль не нужен для поля максимальной длины, а установка нулевого значения в конце байтов для более коротких имен обеспечивает эффективное сравнение полей. strncpy по происхождению не является "ограниченным strcpy", и Комитет предпочел признать существующую практику, а не изменять функцию, чтобы она лучше подходила для такого использования.

По этой причине вы не получите конечный '\0' в строке, если вы нажмете n, не найдя пока '\0' в исходной строке. Его легко использовать неправильно (конечно, если вы знаете об этой ловушке, вы можете ее избежать). Как говорится в цитате, он не был разработан как ограниченный strcpy. И я бы предпочел не использовать его без необходимости. В вашем случае явно его использование не нужно и вы это доказали. Зачем тогда его использовать?

И вообще говоря, код программирования также предназначен для уменьшения избыточности. Если вы знаете, что у вас есть строка, содержащая 'n' символов, зачем указывать функции копирования копировать максимум n символов? Вы выполняете избыточную проверку. Это немного о производительности, но гораздо больше о согласованности кода. Читатели спросят себя, что может сделать strcpy, что может пересечь n символы, и что делает необходимым ограничение копирования, просто чтобы прочитать в руководствах, что это не может произойти в этом случае. И тут начинается путаница среди читателей кода.

Для рационального использования mem-, str- или strn- я выбрал среди них, как в приведенном выше связанном документе:

mem- когда я хочу скопировать необработанные байты, например байты структуры.

str- при копировании строки с завершающим нулем - только при 100% переполнении не может произойти.

strn- при копировании строки с завершающим нулем до некоторой длины, заполняя оставшиеся байты нулями. Вероятно, это не то, что я хочу в большинстве случаев. Легко забыть об этом факте с завершающим нулевым заполнением, но это сделано по замыслу, как поясняется в приведенной выше цитате. Итак, я бы просто написал свой собственный небольшой цикл, который копирует символы, добавляя завершающий '\0':

char * sstrcpy(char *dst, char const *src, size_t n) {
    char *ret = dst;
    while(n-- > 0) {
        if((*dst++ = *src++) == '\0')
            return ret;
    }
    *dst++ = '\0';
    return ret;
}

Всего несколько строк, которые делают именно то, что я хочу. Если бы мне нужна была «чистая скорость», я все равно мог бы поискать переносимую и оптимизированную реализацию, которая выполняет именно эту работу bounded strcpy. Как всегда, сначала профилируйте, а потом уже заморачивайтесь.

Позже в C появились функции для работы с широкими символами, называемые wcs- и wcsn- (для C99). Я бы использовал их так же.

person Johannes Schaub - litb    schedule 04.03.2009
comment
Для strn* функций нет общего шаблона. Функция strncpy предназначена для использования в тех случаях, когда строка, которая может быть либо дополнена нулями, либо завершаться нулями, должна быть скопирована с добавлением нуля в буфер того же размера, что и максимальная длина строки. Никакая другая функция с именем strn* не имеет подобной семантики. Функция strncat предназначена для использования в тех случаях, когда неизвестна длина текущей строки в целевом буфере, но известно, что в ней осталось по крайней мере определенное количество места. Совершенно другая (и ИМХО гораздо менее вероятная) ситуация. - person supercat; 01.05.2017

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

С помощью strcpy это может произойти, с strncpy этого никогда не произойдет. Именно поэтому strcpy считается небезопасным. Зло может быть немного сильным.

person Salgo    schedule 04.03.2009
comment
strncpy также опасен, потому что он НЕ гарантирует, что целевая строка завершается 0! Лучше используйте strncpy_s, хотя я не уверен, что эти функции специфичны для MS. - person newgre; 04.03.2009
comment
strncpy защищает вас только в том случае, если вы передаете правильную длину. Если вы выполняете strncpy для dst, который не является началом буфера, вам все равно придется вычислять доступное пространство. Это принципиально не отличается от использования strlen для проверки соответствия вашего strcpy: это все еще вычитание. Но меньше кода. - person Steve Jessop; 04.03.2009
comment
@jn - strncpy_s - это предлагаемое дополнение к ISO/ANSI C (ISO/IEC TR 24731-1), за исключением того, что MS не реализует его таким образом. - person D.Shawley; 04.03.2009
comment
строки не всегда заканчиваются нулем, является ложным утверждением. Строка по определению представляет собой последовательность символов, заканчивающуюся нулем. Если у вас есть символьный буфер без ограничителя null, он по определению не является строкой. - person Chris Young; 04.03.2009
comment
За исключением того, что в примере мы знаем, что строка завершается нулем, и что у нас достаточно места. Использование strncpy в качестве общего правила допустимо, но в данном случае strncpy безопасен. - person David Thornley; 05.03.2009

Откровенно говоря, если вы много работаете со строками в C, вам не следует спрашивать себя, следует ли вам использовать strcpy, strncpy или memcpy. Вы должны найти или написать строковую библиотеку, обеспечивающую абстракцию более высокого уровня. Например, тот, который отслеживает длину каждой строки, выделяет для вас память и предоставляет все необходимые операции над строками.

Это почти наверняка гарантирует, что вы совершите очень мало ошибок, обычно связанных с обработкой строк C, таких как переполнение буфера, забывание завершить строку байтом NUL и т. д.

В библиотеке могут быть такие функции:

typedef struct MyString MyString;
MyString *mystring_new(const char *c_str);
MyString *mystring_new_from_buffer(const void *p, size_t len);
void mystring_free(MyString *s);
size_t mystring_len(MyString *s);
int mystring_char_at(MyString *s, size_t offset);
MyString *mystring_cat(MyString *s1, ...); /* NULL terminated list */
MyString *mystring_copy_substring(MyString *s, size_t start, size_t max_chars);
MyString *mystring_find(MyString *s, MyString *pattern);
size_t mystring_find_char(MyString *s, int c);
void mystring_copy_out(void *output, MyString *s, size_t max_chars);
int mystring_write_to_fd(int fd, MyString *s);
int mystring_write_to_file(FILE *f, MyString *s);

Я написал один для проекта Kannel, см. файл gwlib/octstr.h. Это значительно упростило нам жизнь. С другой стороны, такую ​​библиотеку довольно просто написать, поэтому вы можете написать ее для себя, хотя бы в качестве упражнения.

person Community    schedule 04.03.2009
comment
В моем текущем проекте строки не используются широко, но это очень хорошее предложение. Я буду иметь это в виду. И, если только для получения опыта, я, вероятно, пойду своим собственным путем. - person Chris Lutz; 05.03.2009
comment
-1 Библиотека не всегда подходит и не относится к вопросу. - person Tomas; 11.05.2010
comment
+1. Программирование основано на абстракциях, чтобы справляться со сложностью, но многие программисты на C, кажется, думают, что, поскольку это C, вы должны оставаться как можно ближе к голому железу, даже сопротивляясь вызову какой-либо функции с повторяющимся шаблонным кодом. в небольшую функцию-оболочку. Это может работать для некоторых небольших игрушечных программ или для некоторых узких частей, где действительно важна скорость (независимо от того). Любой проект достаточного размера быстро закончится адом управления памятью. То, что это C, не означает, что вы не можете использовать принципы современной разработки программного обеспечения. - person Secure; 09.09.2011
comment
@Tomas: библиотека - это то, что нужно использовать для приложений C, которые обрабатывают строки. Примеры: qmail, постфикс. - person ninjalj; 01.12.2013
comment
@ninjalj: полностью согласен - person Tomas; 19.02.2014

Никто не упомянул strlcpy, разработано Тоддом С. Миллером и Тео де Раадтом. Как они пишут в своей газете:

Наиболее распространенное заблуждение состоит в том, что strncpy() NUL-завершает строку назначения. Однако это верно только в том случае, если длина исходной строки меньше параметра размера. Это может быть проблематично при копировании пользовательского ввода произвольной длины в буфер фиксированного размера. Самый безопасный способ использовать strncpy() в этой ситуации — передать его на единицу меньше, чем размер строки назначения, а затем завершить строку вручную. Таким образом, вы всегда будете иметь строку назначения, заканчивающуюся NUL.

Существуют контраргументы в пользу использования strlcpy; страница Википедии отмечает, что

Дреппер утверждает, что strlcpy и strlcat облегчают программисту игнорирование ошибок усечения и, таким образом, могут создавать больше ошибок, чем устранять.

Однако я считаю, что это просто вынуждает людей, которые знают, что они делают, добавлять ручное завершение NULL в дополнение к ручной настройке аргумента strncpy. Использование strlcpy значительно упрощает предотвращение переполнения буфера, поскольку вы не смогли завершить буфер NULL.

Также обратите внимание, что отсутствие strlcpy в glibc или библиотеках Microsoft не должно быть препятствием для использования; вы можете найти исходный код для strlcpy и друзей в любом дистрибутиве BSD, и лицензия, скорее всего, подходит для вашего коммерческого/некоммерческого проекта. См. комментарий вверху strlcpy.c.

person Jared Oberhaus    schedule 05.03.2009
comment
Как разработчик OS X, у меня есть все эти забавные функции, такие как strdup() и strcpy(), но мне кажется довольно легко написать себя в крайнем случае. - person Chris Lutz; 05.03.2009

Лично я придерживаюсь мнения, что если можно доказать, что код действителен — и сделать это так быстро, — то он вполне приемлем. То есть, если код простой и при этом явно правильный, то все в порядке.

Однако вы предполагаете, что пока ваша функция выполняется, ни один другой поток не изменит строку, на которую указывает s1. Что произойдет, если эта функция будет прервана после успешного выделения памяти (и, следовательно, вызова strlen), строка увеличится, и бам у вас возникнет состояние переполнения буфера, так как strcpy копируется в байт NULL.

Следующее может быть лучше:

char *
strdup(const char *s1) {
  int s1_len = strlen(s1);
  char *s2 = malloc(s1_len+1);
  if(s2 == NULL) {
    return NULL;
  }

  strncpy(s2, s1, s1_len);
  return s2;
}

Теперь нить может вырасти не по вашей вине, и вы в безопасности. В результате не будет дупа, но и не будет сумасшедших переполнений.

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

ETA: вот немного лучшая реализация:

char *
strdup(const char *s1, int *retnum) {
  int s1_len = strlen(s1);
  char *s2 = malloc(s1_len+1);
  if(s2 == NULL) {
    return NULL;
  }

  strncpy(s2, s1, s1_len);
  retnum = s1_len;
  return s2;
}

Там количество символов возвращается. Вы также можете:

char *
strdup(const char *s1) {
  int s1_len = strlen(s1);
  char *s2 = malloc(s1_len+1);
  if(s2 == NULL) {
    return NULL;
  }

  strncpy(s2, s1, s1_len);
  s2[s1_len+1] = '\0';
  return s2;
}

Что завершит его байтом NUL. В любом случае лучше, чем тот, который я быстро собрал изначально.

person Michael Trausch    schedule 04.03.2009
comment
Не работая с потоками, я не задумывался об этом, но рад узнать, что для этого есть рациональная причина. Как часто функции в одном потоке изменяют переменные, в то время как другие потоки работают с ними в многопоточных программах? Или это глупый вопрос? - person Chris Lutz; 04.03.2009
comment
Вы не должны обращаться к любому буферу, который изменяют другие потоки, не блокируя его сначала. Использование strncpy не делает вашу функцию потокобезопасной. - person Sedat Kapanoglu; 04.03.2009
comment
«Что произойдет, если эта функция будет прервана после успешного выделения памяти (и, следовательно, вызова strlen), строка увеличится, и у вас возникнет состояние переполнения буфера, поскольку strcpy копирует в нулевой байт». хорошо... - person Johannes Schaub - litb; 04.03.2009
comment
Я не могу сказать, как часто это происходит, хотя у меня были проблемы с ошибками, связанными с многопоточностью и перемещением фрагментов памяти таким образом. Это просто оборонительный маневр. Положительным моментом является то, что этого не произойдет (никогда), если ваша функция использует только локальное хранилище потока. Но можете ли вы гарантировать это? Возможно нет. - person Michael Trausch; 04.03.2009
comment
что произойдет, если другой поток вызовет free в блоке памяти, на который указывает s1? ваш код будет одинаково сломан. я думаю, что это не хороший аргумент. он писал свой код не с расчетом на несколько потоков, и вам всегда приходится делать какие-то предположения, основанные на имеющихся у вас гарантиях. - person Johannes Schaub - litb; 04.03.2009
comment
@ssg: Это правда, хотя это очень часто не происходит, особенно в старом коде C. - person Michael Trausch; 04.03.2009
comment
Имейте в виду, что все, что начинается как очень маловероятное состояние гонки, может легко закончиться агрессивным эксплойтом. Вероятность того, что это станет ошибкой в ​​хорошо написанной программе, очень мала, но это все еще проблема безопасности. - person Joachim Sauer; 04.03.2009
comment
@litb Определенно. Конечно, он не совсем пуленепробиваемый. Однако на самом деле это не так: всегда есть угловой случай, который сломает код. - person Michael Trausch; 04.03.2009
comment
Пожалуйста, не думайте, что однопоточный код застрахован от этой потенциальной проблемы. Обработчик сигнала (или другое прерывание, зависящее от платформы) может так же легко модифицировать содержимое s1. - person Steve Jessop; 04.03.2009
comment
Если s1 изменен вне функции, все равно все готово. Использование strncpy ничего не изменит. В частности, s2 может не завершаться нулем, если изменяется длина s1, что только усугубляет проблему (сложнее отлаживать). Практически нет причин использовать strncpy в вашем коде. - person David Cournapeau; 04.03.2009
comment
Не говоря уже о том, что этот код показывает классическую ошибку с strncpy(): забывание явно установить NUL в последнем байте. - person kdgregory; 04.03.2009
comment
Что касается другого кода, изменяющего строку, освобождающего память и т. д.: хотя это приведет к недопустимым результатам, они не обязательно приведут к сбою программы (если только освобожденная память не будет освобождена из карты виртуальной памяти) - person kdgregory; 04.03.2009
comment
@kdgregory: согласен. Наличие строки с неопределенным значением не так плохо, как перезапись буфера с неопределенными результатами. Дэвид прав в том, что вы все равно закончили, но у вас больше шансов выполнить только DoS, а не выполнение произвольного кода. - person Steve Jessop; 04.03.2009
comment
У вас все еще может быть переполнение буфера с помощью strncpy: код выше, использующий strncpy, может не завершаться NULL (на самом деле он никогда не завершается NULL - бывает, что при компиляции вышеприведенного на моей платформе буфер из malloc заполняется '\ 0' - если бы это было не так, s2 не был бы завершен NULL) - person David Cournapeau; 05.03.2009
comment
Имейте в виду, что только последний из трех ваших примеров strncmp безопасен, если предположить, что s1 можно изменить во время работы функции. Это единственный, который будет завершать s2 нулем, если s1 растет. Если требуется три попытки, чтобы сделать что-то правильно, и первые две выглядят хорошо, есть проблема. - person David Thornley; 05.03.2009
comment
@David: В C много раз вы не завершаете массив NULL, а вместо этого передаете его длину обратно. Иногда можно сделать и то, и другое. Они оба безопасны, это просто зависит от соглашения, которому вы следуете в вызывающих функциях. - person Michael Trausch; 05.03.2009
comment
Массив, да, строка, я не уверен: в конце концов, это определение строки C (чтобы быть завершенным NULL). В данном примере, поскольку строка возвращается, я думаю, справедливо ожидать, что она будет завершена нулем. - person David Cournapeau; 07.03.2009

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

Рассмотрим такой код:

char buf[128];
strncpy(buf, "foo", sizeof buf);

Это не будет записывать ожидаемые четыре символа в buf, а вместо этого запишет «foo», за которым следуют 125 нулевых символов. Если вы, например, собираете много коротких строк, это будет означать, что ваша фактическая производительность намного хуже, чем ожидалось.

Если возможно, я предпочитаю использовать snprintf(), написав вышеприведенное примерно так:

snprintf(buf, sizeof buf, "foo");

Если вместо этого копировать непостоянную строку, это делается так:

snprintf(buf, sizeof buf, "%s", input);

Это важно, так как если input содержит % символов, snprintf() интерпретирует их, открывая целые полки банок с червями.

person unwind    schedule 04.03.2009
comment
strncpy был разработан для заполнения полей имени файла в записях каталогов в Really Ancient Unix (вспомните 1970-е), которые содержали до 14 символов и дополнялись нулями, если они были короче. Заполнение было важно для предотвращения утечки информации в конце буфера. Это оправдывает дизайн strncpy. - person ; 04.03.2009
comment
Это также полезно при отладке. Если у вас есть защитные страницы после буферов, то 0-заполнение strncpy гарантирует, что даже если вы передадите неправильную длину (ну, с учетом округления выравнивания), вы перехватываете немедленно, а не только перехватываете, если строка src достаточно длинна. - person Steve Jessop; 04.03.2009
comment
Сколько накладных расходов добавляет синтаксический анализ строки формата snprintf? - person bdonlan; 09.05.2009

Я думаю, что strncpy тоже зло.

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

Это означает, что вам нужна настоящая строковая абстракция, которая непрозрачно хранит буфер и емкость, навсегда связывает их вместе и проверяет границы. В противном случае вы в конечном итоге будете передавать строки и их емкости по всему магазину. Как только вы доберетесь до реальных строковых операций, таких как изменение середины строки, передать неправильную длину в strncpy (и особенно strncat) будет почти так же просто, как и вызвать strcpy со слишком маленьким адресом назначения.

Конечно, вы все еще можете спросить, использовать ли strncpy или strcpy для реализации этой абстракции: strncpy безопаснее, если вы полностью понимаете, что он делает. Но в коде приложения, обрабатывающем строки, полагаться на strncpy для предотвращения переполнения буфера — все равно, что надевать половину презерватива.

Итак, ваша замена strdup может выглядеть примерно так (порядок определений изменен, чтобы держать вас в напряжении):

string *string_dup(const string *s1) {
    string *s2 = string_alloc(string_len(s1));
    if (s2 != NULL) {
        string_set(s2,s1);
    }
    return s2;
}

static inline size_t string_len(const string *s) {
    return strlen(s->data);
}

static inline void string_set(string *dest, const string *src) {
    // potential (but unlikely) performance issue: strncpy 0-fills dest,
    // even if the src is very short. We may wish to optimise
    // by switching to memcpy later. But strncpy is better here than
    // strcpy, because it means we can use string_set even when
    // the length of src is unknown.
    strncpy(dest->data, src->data, dest->capacity);
}

string *string_alloc(size_t maxlen) {
    if (maxlen > SIZE_MAX - sizeof(string) - 1) return NULL;
    string *self = malloc(sizeof(string) + maxlen + 1);
    if (self != NULL) {
        // empty string
        self->data[0] = '\0';
        // strncpy doesn't NUL-terminate if it prevents overflow, 
        // so exclude the NUL-terminator from the capacity, set it now,
        // and it can never be overwritten.
        self->capacity = maxlen;
        self->data[maxlen] = '\0';
    }
    return self;
}

typedef struct string {
    size_t capacity;
    char data[0];
} string;

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

person Steve Jessop    schedule 04.03.2009

Я бы предпочел использовать memcpy, если я уже вычислил длину, хотя strcpy обычно оптимизируется для работы с машинными словами, кажется, что вы должны предоставить библиотеке как можно больше информации, чтобы она могла использовать наиболее оптимальное копирование. механизм.

Но для примера, который вы приводите, это не имеет значения — если произойдет сбой, то это будет в начальном strlen, так что strncpy ничего вам не купит с точки зрения безопасности (и, предположительно, strncpy медленнее, так как он должен проверьте границы и наличие nul), и любая разница между memcpy и strcpy не стоит спекулятивного изменения кода.

person Pete Kirkham    schedule 04.03.2009

Зло приходит, когда люди используют его вот так (хотя приведенное ниже очень упрощено):

void BadFunction(char *input)
{
    char buffer[1024]; //surely this will **always** be enough

    strcpy(buffer, input);

    ...
}

Это ситуация, которая происходит удивительно часто.

Но да, strcpy так же хорош, как и strncpy в любой ситуации, когда вы выделяете память для целевого буфера и уже использовали strlen для определения длины.

person Andrew Barrett    schedule 04.03.2009

strlen находит до последнего нулевого конечного места.

Но на самом деле буферы не заканчиваются нулем.

вот почему люди используют разные функции.

person lakshmanaraj    schedule 04.03.2009
comment
strlen() вычисляет длину строки, которая всегда имеет завершающий нулевой символ. Использование strlen() в чем-то еще, например, в массиве char или strlen(3.14159), не завершающемся нулевым значением, является простым плохим кодом. Конечно, хороший компилятор пометит второй. - person chux - Reinstate Monica; 21.04.2014

Что ж, strcpy() не так вредна, как strdup() — по крайней мере, strcpy() является частью стандарта C.

person Community    schedule 04.03.2009
comment
и это тоже strdupa() :-) - person dmityugov; 05.03.2009

В описанной вами ситуации strcpy - хороший выбор. У этого strdup будут проблемы только в том случае, если s1 не заканчивается символом «\ 0».

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

strncpy часто кажется безопасным, но может привести к проблемам. Если исходная «строка» короче, чем count, она дополняет цель «\0», пока не достигнет count. Это может плохо сказаться на производительности. Если исходная строка длиннее, чем count, strncpy не добавляет '\0' к цели. Это обязательно вызовет у вас проблемы позже, когда вы ожидаете, что «строка» завершается «\ 0». Так что strncpy также следует использовать с осторожностью!

Я бы использовал memcpy только в том случае, если бы я не работал со строками в конце '\ 0', но это, похоже, дело вкуса.

person Renze de Waal    schedule 04.03.2009

char *strdup(const char *s1)
{
  char *s2 = malloc(strlen(s1)+1);
  if(s2 == NULL)
  {
    return NULL;
  }
  strcpy(s2, s1);
  return s2;
}

Проблемы:

  1. s1 не завершается, strlen вызывает доступ к нераспределенной памяти, программа вылетает.
  2. s1 не завершается, strlen при этом не вызывает доступ к нераспределенной памяти доступа к памяти из другой части вашего приложения. Он возвращается пользователю (проблема безопасности) или анализируется другой частью вашей программы (появляется гейзенбаг).
  3. s1 не завершен, strlen приводит к malloc, который система не может удовлетворить, возвращает NULL. strcpy передается NULL, программа вылетает.
  4. s1 не завершается, strlen приводит к очень большому malloc, система выделяет слишком много памяти для выполнения поставленной задачи, становится нестабильной.
  5. В лучшем случае код неэффективен, strlen требует доступа к каждому элементу строки.

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

Для написания кода общего назначения, например. бизнес-логика имеет смысл? Нет.

person new299    schedule 27.09.2011
comment
Ваш ответ не имеет смысла. Либо вы предполагаете, что в вашем приложении строки заканчиваются нулем, либо вы должны использовать библиотеку строк (даже если это просто быстро собранный struct { size_t len; char str[]; } и несколько функций для работы с ними). Почему бизнес-логика вообще должна быть связана с тем, как ваш код обрабатывает строки? Если нулевое завершение представляет опасность, то это опасность для каждой str* стандартной библиотечной функции. - person Chris Lutz; 28.09.2011

В этом ответе используются size_t и memcpy() для быстрого и простого strdup().

Лучше всего использовать тип size_t, так как это тип, возвращаемый из strlen() и используемый malloc() и memcpy(). int не подходит для этих операций.

memcpy() редко бывает медленнее, чем strcpy() или strncpy(), а часто значительно быстрее.

// Assumption: `s1` points to a C string.
char *strdup(const char *s1) {
  size_t size = strlen(s1) + 1;
  char *s2 = malloc(size);
  if(s2 != NULL) {
    memcpy(s2, s1, size);
  }
  return s2;
} 

§7.1.1 1 "строка — это непрерывная последовательность символов, заканчивающаяся первым нулевым символом и включающая его...."

person chux - Reinstate Monica    schedule 21.04.2014

Ваш код ужасно неэффективен, потому что он дважды проходит через строку, чтобы скопировать ее.

Однажды в strlen().

Затем снова в strcpy().

И вы не проверяете s1 на NULL.

Сохранение длины в какой-то дополнительной переменной вам ничего не стоит, а перебирать каждую строку дважды для ее копирования — смертный грех.

person Thorsten79    schedule 04.03.2009
comment
Поскольку функции не сообщается длина строки, как избежать двойного обхода? AFAICS, нет способа, поэтому ужасно неэффективно неточно. - person Jonathan Leffler; 05.03.2009
comment
согласен, неэффективно. Если вы передаете уже известную длину в memcpy(), вы удаляете второе сканирование строки для '\0' - person dmityugov; 05.03.2009
comment
В C, если есть возможность сделать это, вы должны кэшировать однажды определенную длину строки (если строка не была изменена за это время) - person Thorsten79; 08.03.2009
comment
И именно поэтому вы должны использовать строки в стиле паскаля - struct { size_t len; char str[]; } - person Miles Rout; 30.08.2013