Чому ви повинні використовувати strncpy замість strcpy?


83

Редагувати: я додав джерело для прикладу.

Я натрапив на такий приклад :

char source[MAX] = "123456789";
char source1[MAX] = "123456789";
char destination[MAX] = "abcdefg";
char destination1[MAX] = "abcdefg";
char *return_string;
int index = 5;

/* This is how strcpy works */
printf("destination is originally = '%s'\n", destination);
return_string = strcpy(destination, source);
printf("after strcpy, dest becomes '%s'\n\n", destination);

/* This is how strncpy works */
printf( "destination1 is originally = '%s'\n", destination1 );
return_string = strncpy( destination1, source1, index );
printf( "After strncpy, destination1 becomes '%s'\n", destination1 );

Що дало цей результат:

місце призначення спочатку = 'abcdefg'
Після strcpy, пункт призначення стає '123456789'

призначення1 спочатку = 'abcdefg'
Після strncpy, destination1 стає '12345fg'

Що змушує мене дивуватися, чому хтось бажає цього ефекту. Схоже, це заплутало б. Ця програма змушує мене думати, що ви в основному можете скопіювати чиєсь ім’я (наприклад, Том Броков) за допомогою Тома Бро763.

Які переваги використання strncpy() over strcpy() ?


81
Думаю, ви мали намір запитати: "Чому б бог хтось використовував strcpyзамість цього strncpy?"
Sam Harwell

5
Коли я був TA на першому семестрі курсу програмування на C, я запевнив своїх студентів, що будь-яке використання таких методів getlineпризведе до неправильних результатів, коли я оцінюватиму їх за ретельно продуманими вхідними даними. :)
Сем Гарвелл

4
Я думаю, ви неправильно зрозуміли, що насправді робить код. Подивись уважніше.
Emil H

6
Насправді шкода, що C ніколи не отримав напівпристойної стандартної бібліотеки для рядків.
starblue

7
це не ТОЛЬКО шкода. Я маю на увазі, це мене повністю зламало та зробило мови набагато веселішими :)
Карсон Майерс,

Відповіді:


98

strncpyбореться із переповненням буфера, вимагаючи від вас вказати довжину в ньому. strcpyзалежить від кінцевого результату \0, який може відбуватися не завжди.

По-друге, чому ви вирішили копіювати лише 5 символів у 7 символьних рядків, мені не під силу, але це створює очікувану поведінку. Це лише копіювання перших nсимволів, де nє третій аргумент.

Усі nфункції використовуються як захисне кодування від переповнення буфера. Будь ласка, використовуйте їх замість старих функцій, таких як strcpy.


47
Див. Lysator.liu.se/c/rat/d11.html : strncpyспочатку було введено в бібліотеку C для роботи з полями імен фіксованої довжини в таких структурах, як записи в каталозі. Такі поля використовуються не так само, як рядки: нульовий нуль не потрібен для поля максимальної довжини, а встановлення нульових байтів для коротших імен забезпечує ефективне порівняння по полях. strncpyза своїм походженням не є "обмеженою strcpy", і Комітет волів визнати існуючу практику, а не змінювати функцію, щоб краще відповідати їй для такого використання.
Sinan Ünür

35
Я не впевнений, чому це набирає багато голосів - strncpy ніколи не задумувався як безпечніша альтернатива strcpy і насправді не є нічим безпечнішим, оскільки не нульове завершення рядка. Він також відрізняється функціональністю, завдяки чому додає довжину, що постачається, символами NUL. Як каже caf у своїй відповіді - це для перезаписування рядків у масиві фіксованого розміру.
щуп

26
Фактом залишається факт, що strncpyце не більш безпечна версія strcpy.
Sinan Ünür

7
@Sinan: Я ніколи не казав, що це безпечніше. Це оборонно. Це змушує вас докласти довжину, ерго, що змушує задуматися над тим, що ви робите. Є кращі рішення, але факт залишається фактом, який люди (і роблять) використовуватимуть strncpyзамість того, strcpyщо це набагато більш захисна функція ... що я і сказав.
Ерік

9
Усі функції n використовуються як захисне кодування від переповнення буфера. Будь ласка, використовуйте їх замість старих функцій, таких як strcpy. Це справедливо для snprintf, але не має значення для strncatі абсолютно неправдиво для strncpy. Як ця відповідь могла отримати стільки голосів? Це показує, наскільки погана ситуація щодо цієї підробленої функції. Його використання не є захисним: у більшості ситуацій програміст не розуміє його семантики і створює потенційно не нульовий кінець рядка.
chqrlie

178

strncpy()Функція була розроблена з дуже конкретною проблемою у вигляді: маніпуляціях рядків , що зберігаються у вигляді вихідних записів каталогу UNIX. Вони використовували масив фіксованого розміру, а нуль-термінатор використовувався лише в тому випадку, якщо ім'я файлу було коротше масиву.

Ось що стоїть за двома дивностями strncpy():

  • Він не ставить нуль-термінатор на пункт призначення, якщо він повністю заповнений; і
  • Він завжди повністю заповнює пункт призначення, при необхідності - нулями.

Для "безпечнішого strcpy()" вам краще використовувати strncat()такий спосіб:

if (dest_size > 0)
{
    dest[0] = '\0';
    strncat(dest, source, dest_size - 1);
}

Це завжди призведе до нульового припинення результату і не буде копіювати більше, ніж потрібно.


Але, звичайно, strncpy не завжди є тим, що ви хочете: strncpy приймає максимальну кількість символів для додавання, а не розмір буфера призначення ... Але це лише незначна річ, тому, ймовірно, це не буде проблемою, якщо ви намагаюся об'єднати один рядок в інший.
Девід Волевер,

Я не знав причини, і це дуже стосується того, над чим я працюю в банкоматі.
Matt Joiner

Функція strncpy () призначена для зберігання рядків у нульовому форматі фіксованої довжини. Такий формат був використаний для вихідних записів каталогу Unix, але використовується і в безлічі інших місць, оскільки він дозволяє зберігати рядок з 0-N байтів у N байтах пам'яті. Навіть сьогодні багато баз даних використовують рядки, заповнені нулем, у своїх полях рядків фіксованої довжини. Плутанина з strncpy () випливає з того, що він перетворює рядки у формат FLNP. Якщо для цього потрібен рядок FLNP, це чудово. Якщо комусь потрібен рядок із нульовим закінченням, він повинен забезпечити завершення самостійно.
supercat

1
чому нам потрібно писати dest[0] = '\0';перед викликом strncat? Не могли б ви пояснити, сер?
ЗСШ

4
@snr: strncat()об'єднує вихідний рядок у кінець цільового рядка. Ми просто хочемо скопіювати вихідний рядок до пункту призначення, тому спочатку встановлюємо пункт призначення до порожнього рядка - ось що dest[0] = '\0';робить.
кафе

34

Хоча я знаю намір, що стоїть позаду strncpy, це насправді не є хорошою функцією. Уникайте обох. Пояснює Реймонд Чен .

Особисто я роблю висновок - просто уникати strncpyта всіх його друзів, якщо ви маєте справу з рядками з нульовим закінченням. Незважаючи на "str" ​​у назві, ці функції не створюють рядків із нульовим закінченням. Вони перетворюють рядок із нульовим закінченням у буфер необроблених символів. Використовуючи їх там, де очікується рядок із нульовим закінченням, оскільки другий буфер є просто неправильним. Ви не тільки не можете отримати належне припинення нуля, якщо джерело занадто довге, але якщо джерело коротке, ви отримуєте непотрібне нульове заповнення.

Див. Також Чому strncpy небезпечний?


27

strncpy НЕ безпечніший за strcpy, він просто торгує одним типом помилок іншим. У C, обробляючи рядки C, ви повинні знати розмір ваших буферів, це ніяк не можна обійти. strncpy був виправданий для того, що згадували інші, але в іншому випадку, ви ніколи не повинні використовувати його:

  • якщо ви знаєте довжину вашого рядка та буфера, навіщо використовувати strncpy? Це у найкращому випадку марно обчислювальна потужність (додавання марних 0)
  • якщо ви не знаєте довжини, то ви ризикуєте мовчки скоротити свої рядки, що не набагато краще, ніж переповнення буфера

Я думаю, що це хороший опис для strncpy, тому я проголосував за нього. strncpy має власний набір проблем. Я думаю, це причина, що, наприклад, glib має власні розширення. І так прикро, що ви як програміст повинні знати про розмір усіх масивів. Рішення, що має 0 символів, що закінчують масив char як рядок, коштувало нам усім дорого ....
Фрідріх

1
Рядки з нульовим заповненням досить поширені при зберіганні даних у файлах фіксованого формату. Безумовно, популярність таких речей, як двигуни баз даних та XML, разом із розвиваються сподіваннями користувачів призвели до того, що файли у фіксованому форматі стали менш поширеними, ніж 20 років тому. Тим не менше, такі файли часто є найбільш економічним способом збереження даних. За винятком випадків, коли існує велика різниця між очікуваною та максимальною довжиною даних у записі, набагато швидше читати запис як один фрагмент, що містить деякі невикористані дані, ніж читати запис, розділений на кілька фрагментів.
supercat

Щойно взяв на себе підтримку застарілого коду, який використовував g_strlcpy (), тому не страждає від неефективності заповнення, але, звичайно, кількість переданих байтів НЕ підтримувалась, тому код мовчки скорочував результат.
user2548100

21

Ви шукаєте функцію, strlcpy()яка завжди закінчує рядок 0 і ініціалізує буфер. Він також здатний виявляти переливи. Єдина проблема - це не (насправді) портативність і присутня лише в деяких системах (BSD, Solaris). Проблема цієї функції полягає в тому, що вона відкриває чергову консервовану балочку, що можна побачити в дискусіях на http://en.wikipedia.org/wiki/Strlcpy

Моя особиста думка полягає в тому, що це набагато корисніше, ніж strncpy()і strcpy(). Він має кращу продуктивність і є хорошим супутником для snprintf(). Для платформ, які цього не мають, це відносно легко реалізувати. (для фази розробки додатка я замінюю ці дві функції ( snprintf()і strlcpy()) версією з перехопленням, яка жорстоко перериває програму щодо переповнення або скорочення буфера. Це дозволяє швидко вловлювати найгірших порушників. Особливо, якщо ви працюєте над кодовою базою від когось іншого) .

РЕДАГУВАТИ: strlcpy()можна легко реалізувати:

size_t strlcpy(char *dst, const char *src, size_t dstsize)
{
  size_t len = strlen(src);
  if(dstsize) {
    size_t bl = (len < dstsize-1 ? len : dstsize-1);
    ((char*)memcpy(dst, src, bl))[bl] = 0;
  }
  return len;
}

3
Ви можете написати, що strlcpy доступний майже у всьому, крім Linux та Windows! Однак він має ліцензію BSD, тому ви можете просто залишити його в одній зі своїх бібліотек і використовувати звідти.
Michael van der Westhuizen

Можливо, ви захочете додати тест dstsize > 0і нічого не робити, якщо це не так.
chqrlie

Так, ви праві. Я додаю перевірку, оскільки без неї a dstsizeбуде запускати memcpyдовжину lenв буфері призначення і переповнювати її.
Патрік Шлютер

Плюс один для просування хороших рішень. Більше людей повинні знати про strlcpy, тому що всі продовжують його погано винаходити.
rsp

@MichaelvanderWesthuizen Він доступний у Linux, тільки не у glibc. Перегляньте мої відповіді для отримання додаткової інформації (1) (2) (3)
rsp

3

strncpy()Функція є більш безпечним один: ви повинні пройти максимальну довжину буфера призначення може прийняти. В іншому випадку може статися так, що вихідний рядок неправильно завершено 0, і в цьому випадку strcpy()функція може записати більше символів у пункт призначення, пошкоджуючи все, що є в пам'яті після буфера призначення. Це проблема переповнення буфера, яка використовується у багатьох експлойтах

Крім того, для функцій API POSIX, таких як read()кінцевий 0, який не закінчує буфер, а повертає кількість прочитаних байтів, ви або вручну ставите 0, або копіюєте його за допомогою strncpy().

У вашому прикладі коду, indexнасправді не показник, але count- він каже , скільки символів в найбільш скопіювати з джерела в пункт призначення. Якщо серед перших n байтів джерела немає нульового байта, рядок, розміщений у пункті призначення, не буде припинено нулем


1

strncpy заповнює пункт призначення \ \ 0 для розміру джерела, хоча розмір пункту призначення менше ....

manpage:

Якщо довжина src менше n, strncpy () заливає залишок dest нульовими байтами.

і не тільки залишок ... також після цього, поки не буде досягнуто n символів. І таким чином ви отримуєте переповнення ... (див. Реалізацію сторінки користувача)


3
strncpy заповнює пункт призначення \ \ 0 для розміру джерела, незважаючи на те, що розмір пункту призначення менший .... Боюся, це твердження помилкове і заплутане: strncpyзаповнює пункт призначення \ \ 0 для аргумент size, якщо довжина джерела менше. Аргумент size - це не розмір джерела, не максимальна кількість символів для копіювання з джерела, оскільки він є strncat, це розмір пункту призначення.
chqrlie

@chqrlie: Саме так. Перевага strncpyперед іншими операціями копіювання полягає в тому, що це гарантує, що буде записано весь пункт призначення. Оскільки компілятори можуть намагатися проявити "творчість" при копіюванні структур, що містять деякі невизначені значення, гарантування повного запису будь-яких масивів символів у структурах може бути найпростішим способом запобігти "сюрпризам".
supercat

@supercat: дуже мала перевага для цього конкретного випадку ... але пункт призначення слід виправити після дзвінка, strncpyщоб забезпечити нульове завершення: strncpy(dest, src, dest_size)[dest_size - 1] = '\0';
chqrlie

@chqrlie: Чи буде потрібен кінцевий нульовий байт, залежить від того, що дані повинні представляти. Використання в структурі даних, заповнених нулем, а не нулем, не настільки поширене, як раніше, але якщо, наприклад, формат об’єктного файлу використовує 8-байтові імена розділів, можливість мати char[8]внутрішню структуру для обробки речей до 8 символів може бути приємніше, ніж використання, char[8]але лише можливість обробляти 7 символів або копіювати рядок у char[9]буфер, а потім - memcpyдо місця призначення.
supercat

@chqrlie: Більшість кодів, які роблять речі зі рядками, повинні знати, скільки вони можуть бути, і не повинні сліпо запускати charпокажчики, поки не досягнуть нуля. В тільки речі нуля байт дійсно гарні як для строкових літералів, і навіть там змінна довжиною кодованого префікса, ймовірно , буде краще. Майже для всього іншого було б краще мати рядки або з префіксом довжини, або спеціальний префікс, який вказував би, що char*це насправді щось подібне struct stringInfo {char header[4]; char *realData; size_t length; size_t size;}.
supercat

-1

Це може бути використано в багатьох інших сценаріях, коли вам потрібно скопіювати лише частину оригінального рядка до місця призначення. За допомогою strncpy () ви можете скопіювати обмежену частину вихідного рядка на відміну від strcpy (). Я бачу, що ваш код вийшов з publib.boulder.ibm.com .


-1

Це залежить від наших вимог. Для користувачів Windows

Ми використовуємо strncpy всякий раз, коли не хочемо копіювати весь рядок або хочемо скопіювати лише n кількість символів. Але strcpy копіює весь рядок, включаючи закінчуючий нульовий символ.

Ці посилання допоможуть вам більше знати про strcpy та strncpy та де ми можемо використовувати.

про strcpy

про strncpy


-8

strncpy - це більш безпечна версія strcpy, адже ви ніколи не повинні використовувати strcpy, оскільки його потенційна вразливість до переповнення буфера, що робить вашу систему вразливою до всіх видів атак


6
Див. Lysator.liu.se/c/rat/d11.html : Функція strncpy strncpy спочатку була введена в бібліотеку C для роботи з полями імен фіксованої довжини в таких структурах, як записи в каталозі. Такі поля використовуються не так само, як рядки: нульовий нуль не потрібен для поля максимальної довжини, а встановлення нульових байтів для коротших імен забезпечує ефективне порівняння по полях. strncpy за своїм походженням не є `` обмеженим strcpy '', і Комітет вважає за краще визнати існуючу практику, а не змінювати функцію, щоб краще відповідати їй для такого використання.
Sinan Ünür
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.