คำหลัก จำกัด หมายถึงอะไรใน C ++


182

ฉันไม่แน่ใจอยู่เสมอคำหลักจำกัดความหมายใน C ++ คืออะไร

หมายความว่าตัวชี้ตั้งแต่สองตัวขึ้นไปที่กำหนดให้กับฟังก์ชันไม่ทับซ้อนกันหรือไม่ มันหมายความว่าอะไร?


23
restrictเป็นคำหลัก c99 ใช่ Rpbert __restrict__เอสบาร์นส์ฉันรู้ว่าคอมไพเลอร์ส่วนใหญ่สนับสนุน คุณจะทราบว่าสิ่งใดก็ตามที่มีการขีดเส้นใต้สองเท่าโดยนิยามการปรับใช้เฉพาะและไม่ใช่ C ++แต่เป็นเวอร์ชันเฉพาะของคอมไพเลอร์
KitsuneYMG

5
อะไร? เพียงเพราะการใช้งานเฉพาะไม่ได้ทำให้มันไม่ใช่ C ++; C ++ อนุญาตให้ใช้งานเฉพาะบางสิ่งได้อย่างชัดเจนและไม่อนุญาตหรือแสดงเป็น C ++
อลิซ

4
@Alice KitsuneYMG หมายความว่าไม่ใช่ส่วนหนึ่งของ ISO C ++ และถือว่าเป็นส่วนขยาย C ++ แทน ผู้สร้างคอมไพเลอร์ได้รับอนุญาตให้สร้างและแจกจ่ายส่วนขยายของตัวเองซึ่งอยู่ร่วมกับ ISO C ++ และทำหน้าที่เป็นส่วนหนึ่งของการเพิ่มอย่างไม่เป็นทางการแบบไม่ต่อเนื่องหรือไม่พกพาไปยัง C ++ ตัวอย่างจะเป็น C ++ ที่มีการจัดการเก่าของ MS และ C ++ / CLI ล่าสุดของพวกเขา ตัวอย่างอื่น ๆ จะเป็นคำสั่ง preprocessor และมาโครที่มาจากคอมไพเลอร์บางตัวเช่น#warningคำสั่งทั่วไปหรือมาโครลายเซ็นของฟังก์ชั่น ( __PRETTY_FUNCTION__บน GCC, __FUNCSIG__MSVC เป็นต้น)
Justin Time - Reinstate Monica

4
@ อลิซสำหรับความรู้ของฉัน 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) ระบุว่า:
เวลาจัสติน - Reinstate Monica

4
@Alice เป็นอย่างไร ฉันระบุส่วนที่ระบุว่าrestrictจะถูกตัดออกจาก (ยกเว้นจากซ้ายจาก) ลายเซ็นของฟังก์ชันไลบรารีมาตรฐาน C และซีแมนทิกส์เมื่อฟังก์ชันเหล่านั้นรวมอยู่ในไลบรารีมาตรฐาน C ++ หรือกล่าวอีกนัยหนึ่งฉันระบุความจริงที่ระบุว่าหากลายเซ็นของฟังก์ชันไลบรารีมาตรฐาน C มีrestrictใน C restrictคำสำคัญจะต้องถูกลบออกจากลายเซ็นเทียบเท่าของ C ++
Justin Time - Reinstate Monica

คำตอบ:


143

ในรายงานการเพิ่มประสิทธิภาพหน่วยความจำของเขาChrister Ericson กล่าวว่าถึงแม้restrictจะไม่ได้เป็นส่วนหนึ่งของมาตรฐาน C ++ แต่มันก็รองรับคอมไพเลอร์หลายตัวและเขาแนะนำให้ใช้งานเมื่อมี:

จำกัด คำหลัก

! ใหม่ถึง 1999 ANSI / ISO C มาตรฐาน

! ยังไม่ได้มาตรฐาน C ++ แต่รองรับด้วยคอมไพเลอร์ C ++ หลายตัว

! คำแนะนำเท่านั้นดังนั้นอาจไม่ทำอะไรเลยและยังคงสอดคล้องกัน

ตัวชี้ที่มีคุณสมบัติ จำกัด (หรืออ้างอิง) ...

! ... เป็นสัญญาที่คอมไพเลอร์ว่าสำหรับขอบเขตของตัวชี้เป้าหมายของตัวชี้จะสามารถเข้าถึงได้ผ่านตัวชี้นั้นเท่านั้น (และตัวชี้ที่คัดลอกมาจากมัน)

ในคอมไพเลอร์ C ++ ที่รองรับมันก็น่าจะมีพฤติกรรมเหมือนกับใน C

ดูโพสต์ SO นี้เพื่อดูรายละเอียด: การใช้งานจริงของคำหลัก 'จำกัด ' C99 หรือไม่

ใช้เวลาครึ่งชั่วโมงในการอ่านผ่านกระดาษของ Ericson มันน่าสนใจและคุ้มค่ากับเวลา

แก้ไข

ฉันยังพบว่า IBM AIX C / C ++ คอมไพเลอร์สนับสนุน__restrict__คำหลัก

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:

เข้าใจคำหลักที่ จำกัด

Edit2

ฉันพบบทความที่กล่าวถึงการใช้งานข้อ จำกัด ในโปรแกรม C ++ โดยเฉพาะ:

ร้านค้าโหลดและคำหลัก __ จำกัด

นอกจากนี้ Microsoft Visual c ++ นอกจากนี้ยังสนับสนุน__restrictคำหลัก


2
ลิงก์กระดาษการเพิ่มประสิทธิภาพหน่วยความจำเสียชีวิตนี่คือลิงค์ไปยังเสียงจากงานนำเสนอ GDC ของเขา gdcvault.com/play/1022689/Memory
Grimeh

1
@EnnMichael: เห็นได้ชัดว่าถ้าคุณจะใช้มันในโครงการ C ++ แบบพกพาคุณควร#ifndef __GNUC__ #define __restrict__ /* no-op */หรือคล้ายกัน และกำหนดให้__restrictหาก_MSC_VERมีการกำหนดไว้
Peter Cordes

96

เป็นคนอื่น ๆ กล่าวว่าถ้าหมายถึงอะไรที่เป็นของ C ++ 14เพื่อให้พิจารณา__restrict__ขยาย GCC ซึ่งไม่เหมือนกันเป็น restrictC99

C99

restrictบอกว่าตัวชี้สองตัวไม่สามารถชี้ไปที่ส่วนของหน่วยความจำที่ทับซ้อนกันได้ การใช้งานทั่วไปส่วนใหญ่มีไว้สำหรับอาร์กิวเมนต์ของฟังก์ชัน

สิ่งนี้ จำกัด วิธีการเรียกใช้ฟังก์ชัน แต่อนุญาตให้มีการปรับแต่งคอมไพล์ได้มากขึ้น

หากผู้โทรไม่ปฏิบัติตามrestrictสัญญาให้ระบุพฤติกรรมที่ไม่ได้กำหนดไว้

C99 N1256 ร่าง 6.7.3 / 7 "ชนิดบ่น" พูดว่า:

การใช้งานที่ จำกัด ของ qualifier ที่ จำกัด (เช่นคลาสหน่วยเก็บข้อมูลรีจิสเตอร์) คือการโปรโมตการออปติไมซ์และการลบอินสแตนซ์ทั้งหมดของ qualifier จากหน่วยการแปลก่อนการประมวลผลทั้งหมดที่ประกอบด้วยโปรแกรมที่สอดคล้องกัน

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 นั้นชัดเจนยิ่งกว่าบทความ wiki: 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*) เพราะกฎระเบียบที่เข้มงวด aliasing บอกว่า aliasing ชนิดเข้ากันไม่ได้เป็นพฤติกรรมที่ไม่ได้กำหนดโดยค่าเริ่มต้นและเพื่อให้คอมไพเลอร์สามารถสันนิษฐานได้ว่ามันไม่ได้เกิดขึ้นและเพิ่มประสิทธิภาพออกไป

ดู: กฎนามแฝงที่เข้มงวดคืออะไร

มันใช้งานได้สำหรับการอ้างอิง?

ตามเอกสาร GCC มันทำ: https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Restricted-Pointers.htmlด้วยไวยากรณ์:

int &__restrict__ rref

มีแม้แต่รุ่นสำหรับthisฟังก์ชั่นสมาชิก:

void T::fn () __restrict__

asnwer ที่ดี จะเกิดอะไรขึ้นหากการปิดใช้นามแฝงที่เข้มงวด-fno-strict-aliasingนั้นrestrictไม่ควรสร้างความแตกต่างระหว่างพอยน์เตอร์ที่เป็นประเภทเดียวกันหรือต่างประเภทกันไม่? (ฉันหมายถึง "คำหลัก จำกัด มีผลเฉพาะกับตัวชี้ประเภทที่เข้ากันได้")
idclev 463035818

@ tobi303 ฉันไม่รู้! แจ้งให้เราทราบหากคุณแน่ใจ ;-)
Ciro Santilli 郝海东冠状病六四事件法轮功

@ jww ใช่นั่นเป็นวิธีที่ดีกว่าในการใช้ถ้อยคำ Updated
Ciro Santilli 郝海东冠状病六四事件法轮功

restrictหมายความว่าบางสิ่งใน C ++ หากคุณเรียกใช้ฟังก์ชันไลบรารี C พร้อมrestrictพารามิเตอร์จากโปรแกรม C ++ คุณต้องเชื่อฟังความหมายของสิ่งนั้น โดยทั่วไปหากrestrictใช้ในไลบรารี C API หมายถึงบางสิ่งสำหรับทุกคนที่เรียกใช้จากภาษาใด ๆ รวมถึงไดนามิก FFI จาก Lisp
Kaz

22

ไม่มีอะไร มันถูกเพิ่มเข้าไปในมาตรฐาน C99


8
นั่นไม่เป็นความจริงอย่างสมบูรณ์ เห็นได้ชัดว่ามันได้รับการสนับสนุนโดยคอมไพเลอร์ C ++ บางคนและบางคนแนะนำให้ใช้อย่างมากเมื่อมีให้ดูคำตอบของฉันด้านล่าง
Robert S. Barnes

18
@Robert S Barnes: มาตรฐาน C ++ ไม่รู้จักrestrictเป็นคำหลัก ดังนั้นคำตอบของฉันถูกต้อง สิ่งที่คุณอธิบายคือการใช้งานพฤติกรรมที่เฉพาะเจาะจงและสิ่งที่คุณไม่ควรพึ่งพา
dirkgently

27
@dirkgently: ด้วยความเคารพเนื่องจากทั้งหมดทำไมไม่? หลายโครงการเชื่อมโยงกับส่วนขยายของภาษาที่ไม่ได้มาตรฐานซึ่งรองรับโดยคอมไพเลอร์เฉพาะหรือน้อยมาก เคอร์เนลและ gcc เป็นสิ่งที่คำนึงถึง ไม่ใช่เรื่องแปลกที่จะติดกับคอมไพเลอร์เฉพาะหรือแม้แต่การแก้ไขเฉพาะคอมไพเลอร์เฉพาะสำหรับอายุการใช้งานที่มีประโยชน์ทั้งหมดของโครงการ ไม่ใช่ทุกโปรแกรมที่จะต้องปฏิบัติตามอย่างเคร่งครัด
Robert S. Barnes

7
@Rpbert S. Barnes: ฉันไม่สามารถเครียดได้อีกต่อไปทำไมคุณไม่ควรขึ้นอยู่กับพฤติกรรมการใช้งานที่เฉพาะเจาะจง สำหรับ Linux และ gcc - คิดและคุณจะเห็นว่าทำไมพวกเขาถึงไม่ใช่ตัวอย่างที่ดีในการป้องกันของคุณ ฉันยังไม่เห็นแม้แต่ชิ้นส่วนซอฟต์แวร์ที่ประสบความสำเร็จในระดับปานกลางรันบนคอมไพเลอร์เวอร์ชันเดียวตลอดอายุการใช้งาน
dirkgently

16
@Rpbert S. Barnes: คำถามดังกล่าว c ++ ไม่ใช่ MSVC ไม่ใช่ gcc ไม่ใช่ AIX ถ้า acidzombie24 ต้องการส่วนขยายเฉพาะของคอมไพเลอร์เขาควรพูด / ติดแท็กอย่างนั้น
KitsuneYMG

12

นี่เป็นข้อเสนอเดิมเพื่อเพิ่มคำหลักนี้ ตามที่ระบุไว้อย่างชัดเจนว่านี่เป็นคุณลักษณะC99 ; มันไม่มีส่วนเกี่ยวข้องกับ C ++


5
คอมไพเลอร์ C ++ หลายคนสนับสนุน__restrict__คำหลักที่เหมือนกันที่สุดเท่าที่ฉันจะบอกได้
Robert S. Barnes

มันมีทุกอย่างที่ต้องทำกับ C ++ เพราะโปรแกรม C ++ เรียกrestrictไลบรารี่ C และไลบรารี่ C ใช้ ลักษณะการทำงานของโปรแกรม c ++ กลายเป็นไม่ได้กำหนดถ้ามีการละเมิดข้อ จำกัด restrictโดยนัย
Kaz

@kaz ผิดทั้งหมด ไม่มีส่วนเกี่ยวข้องกับ C ++ เนื่องจากไม่ใช่คำหลักหรือคุณลักษณะของ C ++ และหากคุณใช้ไฟล์ส่วนหัว C ใน C ++ คุณต้องลบrestrictคำหลักออก แน่นอนว่าถ้าคุณผ่านตัวชี้ที่ใช้นามแฝงไปยังฟังก์ชัน C ซึ่งประกาศว่าถูก จำกัด (ซึ่งคุณสามารถทำได้จาก C ++ หรือ C) ก็จะไม่ได้กำหนด แต่ก็อยู่ที่คุณ
Jim Balter

@JimBalter ฉันเห็นดังนั้นสิ่งที่คุณกำลังพูดคือว่าโปรแกรม c ++ โทรห้องสมุด C, และ C restrictห้องสมุดการใช้งาน พฤติกรรมของโปรแกรม C ++ จะไม่ได้กำหนดถ้ามันละเมิดข้อ จำกัด โดยนัย จำกัด แต่สิ่งนี้ไม่เกี่ยวข้องกับ C ++ เนื่องจากเป็น "กับคุณ"
Kaz

5

ไม่มีคำหลักเช่นนี้ใน C ++ รายการคำหลัก C ++ สามารถดูได้ในส่วน 2.11 / 1 ของมาตรฐานภาษา C ++ restrictเป็นคำสำคัญในภาษา C รุ่น C99 ไม่ใช่ C ++


5
คอมไพเลอร์ C ++ หลายคนสนับสนุน__restrict__คำหลักที่เหมือนกันที่สุดเท่าที่ฉันจะบอกได้
Robert S. Barnes

18
@ Robert: แต่ไม่มีคำหลักเช่นใน C ++ สิ่งที่คอมไพเลอร์แต่ละรายทำคือธุรกิจของตัวเอง แต่มันไม่ได้เป็นส่วนหนึ่งของภาษา C ++
jalf

4

เนื่องจากไฟล์ส่วนหัวจากห้องสมุด C บางแห่งใช้คำหลักภาษา C ++ จะต้องทำอะไรบางอย่างเกี่ยวกับมัน .. อย่างน้อยละเว้นคำสำคัญดังนั้นเราจึงไม่ต้อง # กำหนดคำหลักเป็นแมโครว่างเปล่าเพื่อระงับคำหลัก .


3
ฉันเดาว่าทั้งจัดการโดยใช้การextern Cประกาศหรือโดยมันถูกทิ้งเงียบ ๆ เป็นกรณีที่มีคอมไพเลอร์ AIX C / C ++ ซึ่งแทนที่จะจัดการ__rerstrict__คำหลัก คำหลักนั้นยังได้รับการสนับสนุนภายใต้ gcc เพื่อให้โค้ดจะคอมไพล์เหมือนกันภายใต้ g ++
Robert S. Barnes
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.