Чи гарантовано безпечно виконувати memcpy (0,0,0)?


80

Я не настільки добре обізнаний із стандартом С, тож будьте ласкаві.

Я хотів би знати, чи гарантовано це, за стандартом, memcpy(0,0,0)безпечно.

Єдине обмеження, яке я міг знайти, полягає в тому, що якщо області пам'яті перекриваються, тоді поведінка невизначена ...

Але чи можна вважати, що області пам'яті тут перекриваються?


7
Математично перетин двох порожніх множин порожній.
Бенуа

Я хотів перевірити, чи хочете ви (x) libC для вас, але оскільки це asm (elibc / glibc тут), це занадто складно для раннього ранку :)
Кевін,

16
+1 Мені це питання подобається і тому, що це такий дивний випадок, і тому, що, на мою думку, memcpy(0,0,0)це одна з найдивніших частин коду С, яку я бачив.
templatetypedef

2
@eq Ви дійсно хочете знати, або ви натякаєте, що немає ситуацій, коли ви цього хотіли б? Ви вважали, що фактичний дзвінок може бути, скажімо memcpy(outp, inp, len),? І що це може відбуватися в коді, де outpі inpдинамічно розподіляються і спочатку 0? Це працює, наприклад, з тим, p = realloc(p, len+n)коли pі lenє 0. Я сам використовував такий memcpyдзвінок - хоча це технічно UB, я ніколи не стикався з реалізацією, коли це не є забороною, і ніколи цього не очікував.
Джим Балтер

7
@templatetypedef memcpy(0, 0, 0), швидше за все, призначений для представлення динамічного, а не статичного виклику ... тобто ці значення параметрів не повинні бути літералами.
Jim Balter

Відповіді:


71

У мене є чорновий варіант стандарту С (ISO / IEC 9899: 1999), і я маю кілька цікавих речей сказати про цей дзвінок. Для початку він згадує (§7.21.1 / 2) щодо memcpyцього

Якщо аргумент, оголошений як size_tn, визначає довжину масиву для функції, n може мати значення нуля при виклику цієї функції. Якщо прямо не зазначено інше в описі певної функції в цьому підпункті, аргументи вказівника для такого виклику все одно повинні мати допустимі значення, як описано в 7.1.4 . При такому виклику функція, яка знаходить символ, не знаходить входження, функція, яка порівнює дві послідовності символів, повертає нуль, а функція, яка копіює символи, копіює нуль символів.

Посилання, вказане тут, вказує на це:

Якщо аргумент функції має недійсне значення (наприклад, значення поза доменом функції, або вказівник поза адресним простором програми, або нульовий вказівник , або вказівник на не модифікується сховище, коли відповідний параметр не підпадає під const) або тип (після просування), який не очікується функцією із змінною кількістю аргументів, поведінка невизначена .

Отже, це виглядає згідно із специфікацією C, дзвінок

призводить до невизначеної поведінки, оскільки нульові вказівники вважаються "недійсними значеннями".

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


3
Я можу стверджувати, що цитовані частини проекту проекту стандарту ідентичні у кінцевому документі. З таким дзвінком не повинно бути ніяких проблем, але все одно це буде невизначена поведінка, на яку ви покладаєтесь. Тож відповідь на питання "чи гарантовано" - це "ні".
DevSolar

8
Жодна реалізація, яку ви коли-небудь використовуєте у виробництві, не дасть нічого, крім заборони на такий виклик, але реалізації, які роблять інакше, дозволяються і є обґрунтованими ... наприклад, інтерпретатор C або доповнений компілятор із перевіркою помилок, який відхиляє дзвоніть, бо це невідповідно. Звичайно, це не було б розумно, якби Стандарт дозволив дзвінок, як це і робиться realloc(0, 0). Варіанти використання подібні, і я використовував їх обидва (див. Мій коментар до запитання). Безглуздо і прикро, що Стандарт робить цей УБ.
Jim Balter

5
"Я був би здивований, якби будь-яка реальна реалізація memcpy зламалася, якщо б ви зробили це" - я використовував такий, який би; насправді, якщо ви передали довжину 0 із дійсними покажчиками, вона фактично скопіювала 65536 байт. (Його петля зменшила довжину, а потім протестувала).
MM

14
@MattMcNabb Ця реалізація зламана.
Jim Balter

3
@MattMcNabb: Можливо, додайте "правильний" до "фактичного". Я думаю, що у всіх нас є не дуже приємні спогади про старі бібліотеки гетто С, і я не впевнений, скільки з нас цінують ці спогади, про які згадують. :)
tmyklebu

24

Для розваги примітки до випуску для gcc-4.9 вказують на те, що його оптимізатор використовує ці правила, і, наприклад, може видалити умовне в

який потім дає несподівані результати при copy(0,0,0)виклику (див. https://gcc.gnu.org/gcc-4.9/porting_to.html ).

Я дещо неоднозначно ставлюся до поведінки gcc-4.9; поведінка може відповідати стандартам, але можливість викликати memmove (0,0,0) іноді є корисним розширенням цих стандартів.


2
Цікаво. Я розумію вашу амбівалентність, але це суть оптимізацій на мові C: компілятор припускає, що розробники дотримуються певних правил, і таким чином виходить, що деякі оптимізації є дійсними (якими вони є, якщо правила дотримуються).
Matthieu M.

2
@tmyklebu: Дано char *p = 0; int i=something;, оцінка виразу (p+i)дасть невизначену поведінку, навіть коли iдорівнює нулю.
supercat

1
@tmyklebu: Наявність усієї арифметики вказівника (крім порівнянь) на нульовій пастці вказівника було б добре для IMHO; чи memcpy()слід дозволяти виконувати будь-яку арифметику покажчика на своїх аргументах перед забезпеченням ненульового підрахунку - це інше питання [якби я розробляв стандарти, я б, мабуть, зазначив, що якщо pзначення null p+0може перехоплювати, але memcpy(p,p,0)нічого не робитиме]. Набагато більшою проблемою, IMHO, є відкритість більшості невизначеної поведінки. Хоча є деякі речі, які насправді повинні представляти невизначену поведінку (наприклад, дзвінок free(p)...
supercat

1
... і згодом виконуючи p[0]=1;) є багато речей, які слід вказати як такі, що дають невизначений результат (наприклад, реляційне порівняння між непов’язаними покажчиками не повинно вказуватися як таке, що узгоджується з будь-яким іншим порівнянням, але повинно бути вказано як таке, що дає або 0 або 1), або має бути вказано як поведінка, яка є дещо вільнішою, ніж визначена реалізацією (від компіляторів слід вимагати, щоб задокументувати всі можливі наслідки, наприклад, переповнення цілого числа, але не вказувати, які наслідки можуть мати місце в будь-якому конкретному випадку).
supercat

8
хтось, будь ласка, скажіть мені, чому б мені не отримати значок
stackoverflow

0

Ви також можете врахувати це використання, яке memmoveвидно в Git 2.14.x (Q3 2017)

Див. Коміт 168e635 (16 липня 2017 р.) Та коміт 1773664 , коміт f331ab9 , коміт 5783980 (15 липня 2017 р.) Рене Шарф ( rscharfe) .
(Об’єднано Junio ​​C Hamano - gitster- у комітеті 32f9025 , 11 серпня 2017 р.)

Він використовує допоміжний макрос,MOVE_ARRAY який обчислює розмір на основі вказаної для нас кількості елементів і підтримує NULL покажчики, коли це число дорівнює нулю.
Сирі memmove(3)дзвінки з NULLможуть змусити компілятор (надто охоче) оптимізувати подальші NULLперевірки.

MOVE_ARRAYдодає безпечний та зручний помічник для переміщення потенційно перекриваються діапазонів записів масиву.
Він визначає розмір елемента, множиться автоматично і безпечно, щоб отримати розмір у байтах, робить базову перевірку безпеки типу, порівнюючи розміри елементів, і на відміну від memmove(3)підтримує NULLпокажчики, якщо 0 елементів потрібно перемістити.

Приклади :

Він використовує макрос,BUILD_ASSERT_OR_ZERO який стверджує залежність часу побудови, як вираз (при @condцьому умова часу компіляції повинна бути істинною).
Компіляція не вдасться, якщо умова не відповідає дійсності або компілятор не може оцінити.

Приклад:


2
Існування оптимізаторів, які вважають, що "розумний" і "німий" є антонімами, робить тест на n необхідним, але більш ефективний код, як правило, можливий для реалізації, яка гарантує, що memmove (будь-який, будь-який, 0) буде неприпустимим . Якщо компілятор не може замінити виклик memmove () викликом memmoveAtLeastOneByte (), обхідний спосіб захисту від «оптимізації» розумних / дурних компіляторів, як правило, призведе до додаткового порівняння, яке компілятор не зможе усунути.
supercat

-3

Ні, memcpy(0,0,0)це не безпечно. Стандартна бібліотека, швидше за все, не дасть збою під час цього дзвінка. Однак у середовищі тестування в memcpy () може бути присутній додатковий код для виявлення перевитрат буфера та інших проблем. І те, як ця спеціальна версія memcpy () реагує на покажчики NULL, точно не визначено.


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

Я написав причину, чому певні реалізації memcpy ("у середовищі тестування") можуть призвести до проблем з memcpy (0, 0, 0). Я вважаю, що це нове.
Kai Petzke
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.