Що означає ключове слово обмеження в C ++?


182

Я завжди був не впевнений, що означає ключове слово обмеження у C ++?

Чи означає це, що два чи більше вказівника на функцію не перетинаються? Що ще це означає?


23
restrict- це ключове слово c99. Так, Рпберт С. Барнс, я знаю, що більшість компіляторів підтримують __restrict__. Ви зауважите, що все, що має подвійні підкреслення, за визначенням є специфічним для реалізації, і, таким чином, НЕ C ++ , а конкретна версія для компілятора.
KitsuneYMG

5
Що? Просто тому, що конкретна реалізація не робить це C ++; C ++ дозволяє явно реалізувати конкретні речі, і не забороняє їх і не робить їх не C ++.
Аліса

4
@Alice KitsuneYMG означає, що він не є частиною ISO C ++, і замість цього вважається розширенням C ++. Творцям-компіляторам дозволено створювати та розповсюджувати власні розширення, які співіснують із ISO C ++ та виступають частиною звичайно менш-менш-не-портативного неофіційного додатку до C ++. Прикладами можуть бути старий керований C ++ MS та їх останній C ++ / CLI. Іншими прикладами можуть бути директиви препроцесора та макроси, що надаються деякими компіляторами, такими як загальна #warningдиректива або макроси підпису функції ( __PRETTY_FUNCTION__на GCC, __FUNCSIG__на MSVC тощо).
Час Джастіна - Поновіть Моніку

4
@Alice Наскільки мені відомо, C ++ 11 не передбачає повної підтримки для всіх C99, а також C ++ 14 або те, що я знаю про C ++ 17. restrictне вважається ключовим словом C ++ (див. en.cppreference.com/w/cpp/keyword ), а насправді єдина згадка restrictу стандарті C ++ 11 (див. open-std.org/jtc1/sc22/wg21 /docs/papers/2012/n3337.pdf , копія FDIS з незначними редакційними змінами, § 17.2 [library.c], PDF сторінка 413) зазначає, що:
Час Джастіна - відновіть Моніку

4
@Alice Як так? Я заявив про частину, яка говорить про те, що restrictїї слід опустити (виключити з, не залишилося) підписів та семантики функцій C стандартної бібліотеки, коли ці функції включені до стандартної бібліотеки C ++. Або іншими словами, я констатував той факт, який говорить про те, що якщо підпис функції стандартної бібліотеки restrictC міститься в C, restrictключове слово має бути видалено з підпису еквівалента C ++.
Час Джастіна - Відновіть Моніку

Відповіді:


143

У своїй роботі « Оптимізація пам’яті» Крістер Еріксон каже, що поки restrictце не є частиною стандарту C ++, що його підтримують багато компіляторів, і він рекомендує використовувати його, коли це доступно:

обмежити ключове слово

! Новий стандарт 1999 ANSI / ISO C

! Ще не в стандарті C ++, але підтримується багатьма компіляторами C ++

! Тільки натяк, тому може нічого не робити і все ще відповідати

Покажчик (або посилання) з обмеженою кваліфікацією ...

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

У компіляторах C ++, які його підтримують, він, ймовірно, повинен вести себе так само, як і у C.

Докладні відомості див. У цій публікації SO: Реальне використання ключового слова C99 "обмежити"?

Пройдіть півгодини, щоб пролізти папери Еріксона, це цікаво і варто того часу.

Редагувати

Я також виявив, що компілятор AIX C / C ++__restrict__ IBM підтримує ключове слово .

g ++ також, здається, підтримує це, оскільки наступна програма чітко збирається в g ++:

#include <stdio.h>

int foo(int * __restrict__ a, int * __restrict__ b) {
    return *a + *b;
}

int main(void) {
    int a = 1, b = 1, c;

    c = foo(&a, &b);

    printf("c == %d\n", c);

    return 0;
}

Я також знайшов хорошу статтю про використання restrict:

Демістифікація ключового слова обмеження

Правка2

Я натрапив на статтю, яка конкретно обговорює використання обмежень у програмах на C ++:

Load-hit-магазини та ключове слово __restrict

Також Microsoft Visual C ++ також підтримує __restrictключове слово .


2
Посилання на паперову оптимізацію пам’яті мертве, ось посилання на аудіо з його GDC-презентації. gdcvault.com/play/1022689/Memory
Grimeh

1
@EnnMichael: Очевидно, що якщо ви збираєтеся використовувати його в портативному проекті C ++, вам слід #ifndef __GNUC__ #define __restrict__ /* no-op */або схоже. І визначте це, __restrictякщо _MSC_VERвизначено.
Пітер Кордес

96

Як говорили інші, якщо C ++ 14 нічого не означає , давайте розглянемо __restrict__розширення GCC, яке робить те саме, що і C99 restrict.

C99

restrictговорить, що два покажчики не можуть вказувати на перекриття областей пам'яті. Найбільш поширене використання для аргументів функції.

Це обмежує те, як можна викликати функцію, але дозволяє отримати більше оптимізацій компіляції.

Якщо абонент не дотримується restrictдоговору, не визначена поведінка.

У проекті 6.7.3 / 7 "Кваліфікатори типу" C99 N1256 написано:

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

та 6.7.3.1 "Формальне визначення обмеження" містить детальні відомості.

Можлива оптимізація

Приклад Вікіпедії є дуже освітлювальним.

Це чітко показує, як це дозволяє зберегти одну інструкцію зі зборки .

Без обмежень:

void f(int *a, int *b, int *x) {
  *a += *x;
  *b += *x;
}

Псевдозбірка:

load R1  *x    ; Load the value of x pointer
load R2  *a    ; Load the value of a pointer
add R2 += R1    ; Perform Addition
set R2  *a     ; Update the value of a pointer
; Similarly for b, note that x is loaded twice,
; because x may point to a (a aliased by x) thus 
; the value of x will change when the value of a
; changes.
load R1  *x
load R2  *b
add R2 += R1
set R2  *b

З обмеженням:

void fr(int *restrict a, int *restrict b, int *restrict x);

Псевдозбірка:

load R1  *x
load R2  *a
add R2 += R1
set R2  *a
; Note that x is not reloaded,
; because the compiler knows it is unchanged
; "load R1 ← *x" is no longer needed.
load R2  *b
add R2 += R1
set R2  *b

Чи справді це робить GCC?

g++ 4.8 Linux x86-64:

g++ -g -std=gnu++98 -O0 -c main.cpp
objdump -S main.o

З -O0, вони однакові.

З -O3:

void f(int *a, int *b, int *x) {
    *a += *x;
   0:   8b 02                   mov    (%rdx),%eax
   2:   01 07                   add    %eax,(%rdi)
    *b += *x;
   4:   8b 02                   mov    (%rdx),%eax
   6:   01 06                   add    %eax,(%rsi)  

void fr(int *__restrict__ a, int *__restrict__ b, int *__restrict__ x) {
    *a += *x;
  10:   8b 02                   mov    (%rdx),%eax
  12:   01 07                   add    %eax,(%rdi)
    *b += *x;
  14:   01 06                   add    %eax,(%rsi) 

Для неусвідомлених закликає конвенція :

  • rdi = перший параметр
  • rsi = другий параметр
  • rdx = третій параметр

Вихід з GCC був ще чіткішим, ніж стаття Вікі: 4 інструкції проти 3 інструкцій.

Масиви

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

Розглянемо для прикладу:

void f(char *restrict p1, char *restrict p2, size_t size) {
     for (size_t i = 0; i < size; i++) {
         p1[i] = 4;
         p2[i] = 9;
     }
 }

Через те restrict, що смарт-компілятор (або людина) може оптимізувати це для:

memset(p1, 4, size);
memset(p2, 9, size);

Що потенційно набагато ефективніше, оскільки це може бути оптимізовано збірка на гідній реалізації libc (наприклад, glibc) Чи краще використовувати std :: memcpy () або std :: copy () з точки зору продуктивності? , можливо, із інструкціями SIMD .

Без обмеження цю оптимізацію неможливо здійснити, наприклад, врахуйте:

char p1[4];
char *p2 = &p1[1];
f(p1, p2, 3);

Тоді forверсія робить:

p1 == {4, 4, 4, 9}

а memsetверсія робить:

p1 == {4, 9, 9, 9}

Чи справді це робить GCC?

GCC 5.2.1.Linux x86-64 Ubuntu 15.10:

gcc -g -std=c99 -O0 -c main.c
objdump -dr main.o

З -O0, обидва однакові.

З -O3:

  • з обмеженням:

    3f0:   48 85 d2                test   %rdx,%rdx
    3f3:   74 33                   je     428 <fr+0x38>
    3f5:   55                      push   %rbp
    3f6:   53                      push   %rbx
    3f7:   48 89 f5                mov    %rsi,%rbp
    3fa:   be 04 00 00 00          mov    $0x4,%esi
    3ff:   48 89 d3                mov    %rdx,%rbx
    402:   48 83 ec 08             sub    $0x8,%rsp
    406:   e8 00 00 00 00          callq  40b <fr+0x1b>
                            407: R_X86_64_PC32      memset-0x4
    40b:   48 83 c4 08             add    $0x8,%rsp
    40f:   48 89 da                mov    %rbx,%rdx
    412:   48 89 ef                mov    %rbp,%rdi
    415:   5b                      pop    %rbx
    416:   5d                      pop    %rbp
    417:   be 09 00 00 00          mov    $0x9,%esi
    41c:   e9 00 00 00 00          jmpq   421 <fr+0x31>
                            41d: R_X86_64_PC32      memset-0x4
    421:   0f 1f 80 00 00 00 00    nopl   0x0(%rax)
    428:   f3 c3                   repz retq

    Два memsetдзвінки, як очікувалося.

  • без обмежень: жодних дзвінків stdlib, просто розгортання циклу з 16 ітерацій, який я не збираюся відтворювати тут :-)

У мене не було терпіння їх орієнтувати, але я вірю, що обмежена версія буде швидшою.

Суворе правило дозволу

restrictКлючове слово впливає тільки покажчики сумісних типів (наприклад , два int*) , оскільки суворі правила накладення спектрів кажуть , що згладжування несумісних типів не визначено поведінку за замовчуванням, і тому компілятори можуть припустити , що це не відбудеться і оптимізує геть.

Дивіться: Що таке суворе правило псевдоніму?

Це працює для довідок?

Згідно з документами GCC це робить: https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Restricted-Pointers.html із синтаксисом:

int &__restrict__ rref

Існує навіть версія для thisфункцій членів:

void T::fn () __restrict__

приємна відповідь. Що робити, якщо жорстке згладжування вимкнено -fno-strict-aliasing, то не restrictповинно бути різницею між вказівниками одного типу чи різними типами, ні? (я зазначаю "Ключове слово обмеження стосується лише покажчиків сумісних типів")
idclev 463035818

@ tobi303 Я не знаю! Повідомте мене, якщо ви точно дізнаєтесь ;-)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

@jww так, це кращий спосіб сформулювати це. Оновлено.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

restrictщось означає в C ++. Якщо ви викликаєте функцію бібліотеки С з restrictпараметрами програми C ++, ви повинні підкорятися її наслідкам. В основному, якщо restrictвін використовується в API бібліотеки С, це означає, що хтось називає його з будь-якої мови, включаючи динамічний FFI від Lisp.
Каз

22

Нічого. Він був доданий до стандарту C99.


8
Це не зовсім правда. Мабуть, це підтримується деякими компіляторами C ++, і деякі люди настійно рекомендують використовувати його, коли воно доступне, дивіться мою відповідь нижче.
Роберт С. Барнс

18
@Robert S Barnes: Стандарт C ++ не визнається restrictключовим словом. Отже, моя відповідь стоїть правильною. Те, що ви описуєте, - це конкретна поведінка та те, на що ви не повинні покладатися.
несподівано

27
@dirkgently: З усією повагою, чому б і ні? Багато проектів прив’язані до конкретних нестандартних розширень мови, підтримуваних лише певними або дуже мало компіляторами. Ядро Linux і gcc приходить на думку. Не рідкість дотримуватися конкретного компілятора або навіть конкретного перегляду конкретного компілятора протягом усього строку корисного використання проекту. Не кожна програма повинна суворо відповідати.
Роберт С. Барнс

7
@ Рпберт С. Барнс: Я більше не можу наголосити, чому ти не повинен залежати від поведінки конкретної реалізації. Що стосується Linux та gcc - подумайте, і ви побачите, чому вони не є хорошим прикладом на вашу захист. Я ще не бачу навіть помірно успішного програмного забезпечення, що працює на одній версії компілятора протягом свого життя.
примхливо

16
@Rpbert S. Barnes: Питання сказало c ++. Не MSVC, не gcc, не AIX. Якщо acidzombie24 хотів, щоб компілятори специфічні розширення, s? Він повинен був сказати / позначити так.
KitsuneYMG

12

Це оригінальна пропозиція додати це ключове слово. Як насправді зазначено, це особливість C99 ; це не має нічого спільного з C ++.


5
Багато компіляторів C ++ підтримують __restrict__ключове слово, ідентичне наскільки я можу сказати.
Роберт С. Барнс

Це стосується всього C ++, оскільки програми C ++ викликають бібліотеки C, а бібліотеки C використовують restrict. Поведінка програми C ++ стає невизначеною, якщо вона порушує обмеження, на які покладається restrict.
Каз

@kaz Повністю помиляється. Це не має нічого спільного з C ++, оскільки це не ключове слово чи особливість C ++, і якщо ви використовуєте файли заголовків C у C ++, ви повинні видалити restrictключове слово. Звичайно, якщо ви передаєте відчужені вказівники на функцію C, яка оголошує їх обмеженими (що ви можете зробити або з C ++, або C), це не визначено, але це на вас.
Джим Балтер

@JimBalter Я бачу, тому, що ви говорите, це те, що програми C ++ викликають бібліотеки C, а бібліотеки C використовують restrict. Поведінка програми C ++ стає невизначеною, якщо вона порушує обмеження, передбачені обмеженням. Але це насправді не має нічого спільного з C ++, тому що це "на вас".
Каз

5

У C ++ такого ключового слова немає. Список ключових слів C ++ можна знайти в розділі 2.11 / 1 мовного стандарту C ++. restrict- це ключове слово у C99-версії мови C, а не в C ++.


5
Багато компіляторів C ++ підтримують __restrict__ключове слово, ідентичне наскільки я можу сказати.
Роберт С. Барнс

18
@Robert: Але в C ++ такого ключового слова немає . Індивідуальні компілятори займаються власним бізнесом, але це не є мовою C ++.
jalf

4

Оскільки файли заголовків з деяких бібліотек C використовують ключове слово, мова C ++ повинна щось робити з цим .. як мінімум, ігноруючи ключове слово, тому нам не потрібно # визначати ключове слово пустим макросом, щоб придушити ключове слово .


3
Я б здогадався, що це або обробляється за допомогою extern Cдекларації, або шляхом мовчазного скидання, як це відбувається у компіляторі AIX C / C ++, який замість цього обробляє __rerstrict__ключове слово. Це ключове слово також підтримується під gcc, так що код буде компілювати те саме під g ++.
Роберт С. Барнс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.