Виклик функції з покажчиком на non-const і вказівником на const аргументи однієї адреси


14

Я хочу написати функцію, яка вводить масив даних і виводить інший масив даних за допомогою покажчиків.

Мені цікаво, що це результат, якщо обидва srcі dstвказали на одну і ту ж адресу, тому що я знаю, що компілятор може оптимізувати const. Це невизначена поведінка? (Я позначив як C, так і C ++, тому що я не впевнений, чи може відповідь між ними відрізнятися, і я хочу знати про обидва.)

void f(const char *src, char *dst) {
    dst[2] = src[0];
    dst[1] = src[1];
    dst[0] = src[2];
}

int main() {
    char s[] = "123";
    f(s,s);
    printf("%s\n", s);
    return 0;
}

На додаток до вищезазначеного питання, чи добре це визначено, якщо я видаляю constвихідний код?

Відповіді:


17

Хоча це правда , що поведінка визначено коректно - це НЕ правда , що компілятори можуть «оптимізувати для сопзЬ» в тому сенсі , що ви маєте в виду.

Тобто компілятору не дозволяється припускати, що лише тому, що параметр є a const T* ptr, пам'ять, на яку вказує, ptrне буде змінена через інший покажчик. Покажчики навіть не повинні бути рівними. Це constзобов'язання, а не гарантія - зобов'язання з боку вас (= функція) не вносити змін через цей покажчик.

Щоб насправді мати цю гарантію, потрібно позначити вказівник restrictключовим словом. Таким чином, якщо скласти ці дві функції:

int foo(const int* x, int* y) {
    int result = *x;
    (*y)++;
    return result + *x;
}

int bar(const int* x, int* restrict y) {
    int result = *x;
    (*y)++;
    return result + *x;
}

foo()функція повинна прочитати двічі з x, в той час як bar()тільки потрібно прочитати його один раз:

foo:
        mov     eax, DWORD PTR [rdi]
        add     DWORD PTR [rsi], 1
        add     eax, DWORD PTR [rdi]  # second read
        ret
bar:
        mov     eax, DWORD PTR [rdi]
        add     DWORD PTR [rsi], 1
        add     eax, eax              # no second read
        ret

Дивіться це в прямому ефірі GodBolt.

restrictє лише ключовим словом у C (починаючи з C99); на жаль, до цього часу він не був введений в C ++ (з тієї поганої причини, що складніше його впровадити в C ++). Однак багато компіляторів ніби не підтримують це __restrict.

Підсумок: компілятор повинен підтримувати ваш "езотеричний" випадок використання при компілюванні f(), і не матиме з цим жодних проблем.


Дивіться цю публікацію щодо випадків використання для restrict.


constне є "зобов'язанням з боку вас (= функція) не вносити змін через цей покажчик". Стандарт C дозволяє виконувати функцію видалення constза допомогою кастра, а потім модифікувати об'єкт через результат. По суті, constце лише рекомендація та зручність для програміста, щоб допомогти уникнути зміни об'єкта ненавмисно.
Eric Eric Postpischil

@EricPostpischil: Ви можете вийти з цього зобов'язання.
einpoklum

Зобов'язання, з якого ви можете позбутися, не є зобов'язанням.
Eric Postpischil

2
@EricPostpischil: 1. Ви розділяєте тут волоски. 2. Це неправда.
einpoklum

1
Ось чому memcpyі strcpyоголошуються restrictаргументами, а memmoveні - лише останні дозволяють перекриватися між блоками пам'яті.
Вармар

5

Це чітко визначено (на C ++, більше не впевнене в C), з і без constкваліфікатора.

Перше, на що слід звернути увагу, - це суворе правило 1 . Якщо srcі dstвказує на той самий об'єкт:

Щодо constкваліфікатора, ви можете стверджувати, що з тих пір, коли dst == srcваша функція ефективно модифікує те, на що srcвказує, srcне слід кваліфікувати const. Це не так, як constпрацює. Необхідно розглянути два випадки:

  1. Коли об'єкт визначений як такий const, як у char const data[42];, його зміна (прямо чи опосередковано) призводить до не визначеної поведінки.
  2. Коли посилання або вказівник на constоб'єкт визначено, як у char const* pdata = data;, можна змінити базовий об'єкт за умови, що він не був визначений як const2 (див. 1.). Отже, чітко визначено наступне:
int main()
{
    int result = 42;
    int const* presult = &result;
    *const_cast<int*>(presult) = 0;
    return *presult; // 0
}

1) Яке правило суворого псевдоніму?
2) Чи const_castбезпечно?


Може, ОП означає можливе переупорядкування завдань?
Ігор Р.

char*і char const*не сумісні. _Generic((char *) 0, const char *: 1, default: 0))оцінює до нуля.
Eric Postpischil

Фраза «Коли визначено посилання або вказівник на constоб’єкт» невірна. Ви маєте на увазі, що коли визначено посилання або вказівник на const-класифікований тип , це не означає, що об'єкт, якому він встановлений, може не змінюватися (різними способами). (Якщо вказівник дійсно вказує на constоб’єкт, це означає, що об'єкт дійсно є constза визначенням, тому поведінка спроб його модифікувати не визначається.)
Ерік Postpischil

@Eric, я конкретний лише тоді, коли питання стосується стандарту чи тегів language-lawyer. Точність - це цінність, яку я дорожу, але я також усвідомлюю, що вона постає з більшою складністю. Тут я вирішив скористатися простотою та зрозумілими пропозиціями, тому що я вважаю, що це WAP хотів. Якщо ви думаєте про інше, будь ласка, дайте відповідь, я буду одним із перших, хто виголосив це. У будь-якому випадку, дякую за ваш коментар.
ВАТ

3

Це чітко визначено в C. Суворі правила дозволу не застосовуються charні з типом, ні з двома вказівниками одного типу.

Я не впевнений, що ви маєте на увазі під «оптимізацією для const». Мій компілятор (GCC 8.3.0 x86-64) генерує абсолютно однаковий код для обох випадків. Якщо ви додасте restrictспецифікатор до покажчиків, то згенерований код трохи кращий, але це не буде працювати для вашого випадку, вказівники однакові.

(C11 §6.5 7)

Об'єкт має збережене значення, доступ до якого має лише вираз lvalue, який має один з таких типів:
- тип, сумісний з ефективним типом об'єкта,
- кваліфікована версія типу, сумісна з ефективним типом об'єкта,
- тип, який є типовим або непідписаним типом, що відповідає ефективному типу об'єкта,
- тип, який є типовим або непідписаним типом, що відповідає кваліфікованій версії ефективного типу об'єкта,
- сукупний або об'єднаний тип, що включає в себе один вищезазначених типів серед його членів (у тому числі, рекурсивно, член субагрегату або вміщеного союзу), або
- тип символів.

У цьому випадку (без restrict) ви завжди отримаєте 121результат.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.