Từ khóa hạn chế có nghĩa là gì trong C ++?


182

Tôi luôn không chắc chắn, từ khóa hạn chế có nghĩa gì trong C ++?

Có nghĩa là hai hoặc nhiều con trỏ được cung cấp cho hàm không trùng nhau? Nó có nghĩa gì khác?


23
restrictlà một từ khóa c99. Vâng, Rpbert S. Barnes, tôi biết rằng hầu hết các trình biên dịch đều hỗ trợ __restrict__. Bạn sẽ lưu ý rằng bất cứ điều gì có dấu gạch dưới kép, theo định nghĩa, việc triển khai cụ thể và do đó KHÔNG phải là C ++ , mà là một phiên bản cụ thể của trình biên dịch.
KitsuneYMG

5
Gì? Chỉ vì việc triển khai cụ thể không làm cho nó không phải là C ++; C ++ cho phép thực hiện các công cụ cụ thể một cách rõ ràng và không cho phép hoặc không hiển thị C ++.
Alice

4
@Alice KitsuneYMG có nghĩa là nó không phải là một phần của ISO C ++ và thay vào đó được coi là một phần mở rộng C ++. Người tạo trình biên dịch được phép tạo và phân phối các phần mở rộng của riêng họ, cùng tồn tại với ISO C ++ và hoạt động như một phần bổ sung không chính thức thường ít hoặc không mang theo cho C ++. Ví dụ sẽ là Managed C ++ cũ của MS và C ++ / CLI gần đây của họ. Các ví dụ khác sẽ là các chỉ thị và macro tiền xử lý được cung cấp bởi một số trình biên dịch, chẳng hạn như #warningchỉ thị chung hoặc các macro chữ ký hàm ( __PRETTY_FUNCTION__trên GCC, __FUNCSIG__trên MSVC, v.v.).
Thời gian của Justin - Phục hồi Monica

4
@ Alice Theo hiểu biết của tôi, C ++ 11 không bắt buộc hỗ trợ đầy đủ cho tất cả C99, cũng như C ++ 14 hoặc những gì tôi biết về C ++ 17. restrictkhông được coi là một từ khóa C ++ (xem en.cppreference.com/w/cpp/keyword ) và trên thực tế, chỉ đề cập đến restricttrong tiêu chuẩn C ++ 11 (xem open-std.org/jtc1/sc22/wg21 /docs/ con / 2012 / n3337.pdf , một bản sao của FDIS với các thay đổi biên tập nhỏ, §17.2 [library.c], PDF trang 413) nói rằng:
Thời gian của Justin - Tái lập lại Monica

4
@ Alice Làm sao vậy? Tôi đã nêu phần nói rằng restrictđược bỏ qua (loại trừ khỏi, bỏ ngoài) chữ ký và ngữ nghĩa của thư viện tiêu chuẩn C khi các chức năng đó được đưa vào thư viện chuẩn C ++. Hay nói cách khác, tôi đã nói thực tế rằng nếu chữ ký của hàm thư viện chuẩn C chứa restricttrong C, restricttừ khóa phải được xóa khỏi chữ ký tương đương của C ++.
Thời gian của Justin - Tái lập lại Monica

Câu trả lời:


141

Trong bài viết của mình, Tối ưu hóa bộ nhớ , Christer Ericson nói rằng trong khirestrict chưa phải là một phần của tiêu chuẩn C ++, nhưng nó được hỗ trợ bởi nhiều trình biên dịch và ông khuyến nghị sử dụng nó khi khả dụng:

hạn chế từ khóa

! Mới đến năm 1999 tiêu chuẩn ANSI / ISO C

! Chưa có trong tiêu chuẩn C ++, nhưng được hỗ trợ bởi nhiều trình biên dịch C ++

! Một gợi ý duy nhất, vì vậy có thể không làm gì và vẫn tuân thủ

Một con trỏ đủ điều kiện hạn chế (hoặc tham chiếu) ...

! ... về cơ bản là một lời hứa với trình biên dịch rằng đối với phạm vi của con trỏ, mục tiêu của con trỏ sẽ chỉ được truy cập thông qua con trỏ đó (và các con trỏ được sao chép từ nó).

Trong các trình biên dịch C ++ hỗ trợ nó, nó có thể hoạt động giống như trong C.

Xem bài đăng SO này để biết chi tiết: Sử dụng thực tế từ khóa 'hạn chế' C99?

Dành nửa giờ để lướt qua bài báo của Ericson, thật thú vị và đáng để dành thời gian.

Biên tập

Tôi cũng thấy rằng trình biên dịch AIX C / C ++__restrict__ của IBM hỗ trợ từ khóa .

g ++ dường như cũng hỗ trợ điều này khi chương trình sau biên dịch hoàn toàn trên 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;
}

Tôi cũng tìm thấy một bài viết hay về việc sử dụng restrict:

Làm sáng tỏ từ khóa hạn chế

Chỉnh sửa2

Tôi đã xem qua một bài viết thảo luận cụ thể về việc sử dụng hạn chế trong các chương trình C ++:

Load-hit-store và từ khóa __restrict

Ngoài ra, Microsoft Visual C ++ cũng hỗ trợ __restricttừ khóa .


2
Liên kết giấy Tối ưu hóa bộ nhớ đã chết, đây là liên kết đến âm thanh từ bản trình bày GDC của anh ấy. gdcvault.com/play/1022689/Memory
Grimeh

1
@EnnMichael: Rõ ràng nếu bạn sẽ sử dụng nó trong một dự án C ++ di động, bạn nên #ifndef __GNUC__ #define __restrict__ /* no-op */hoặc tương tự. Và định nghĩa nó __restrictnếu _MSC_VERđược định nghĩa.
Peter Cordes

95

Như những người khác đã nói, nếu có nghĩa là không có gì trong C ++ 14 , vì vậy hãy xem xét __restrict__tiện ích mở rộng GCC giống như C99 restrict.

C99

restrictnói rằng hai con trỏ không thể trỏ đến các vùng bộ nhớ chồng chéo. Việc sử dụng phổ biến nhất là cho các đối số chức năng.

Điều này hạn chế cách hàm có thể được gọi, nhưng cho phép tối ưu hóa biên dịch nhiều hơn.

Nếu người gọi không tuân theo restricthợp đồng, hành vi không xác định.

Bản dự thảo C99 N1256 6.7.3 / 7 "Loại vòng loại" cho biết:

Mục đích sử dụng của vòng loại hạn chế (như lớp lưu trữ đăng ký) là để thúc đẩy tối ưu hóa và xóa tất cả các phiên bản của vòng loại khỏi tất cả các đơn vị dịch tiền xử lý soạn thảo chương trình tuân thủ không thay đổi ý nghĩa của nó (nghĩa là hành vi có thể quan sát được).

và 6.7.3.1 "Định nghĩa chính thức về hạn chế" đưa ra các chi tiết chính.

Tối ưu hóa có thể

Các ví dụ Wikipediarất sáng.

Nó cho thấy rõ làm thế nào khi nó cho phép lưu một hướng dẫn lắp ráp .

Không giới hạn:

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

Lắp ráp giả:

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 a may be equal to x.
load R1  *x
load R2  *b
add R2 += R1
set R2  *b

Với hạn chế:

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

Lắp ráp giả:

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
load R2  *b
add R2 += R1
set R2  *b

GCC có thực sự làm điều đó?

g++ 4,8 Linux x86-64:

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

Với -O0, họ là như nhau.

Với -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) 

Đối với người không quen biết, quy ước gọi là:

  • rdi = tham số đầu tiên
  • rsi = tham số thứ hai
  • rdx = tham số thứ ba

Đầu ra GCC thậm chí còn rõ ràng hơn bài viết wiki: 4 hướng dẫn so với 3 hướng dẫn.

Mảng

Cho đến nay chúng ta có các khoản tiết kiệm lệnh đơn, nhưng nếu con trỏ biểu thị các mảng được lặp lại, một trường hợp sử dụng phổ biến, thì một loạt các hướng dẫn có thể được lưu, như được đề cập bởi supercatmichael .

Xem xét ví dụ:

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;
     }
 }

Bởi vì restrict, một trình biên dịch thông minh (hoặc con người), có thể tối ưu hóa điều đó thành:

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

Điều này có khả năng hiệu quả hơn nhiều vì nó có thể được lắp ráp được tối ưu hóa khi triển khai libc (như glibc) tốt hơn nên sử dụng std :: memcpy () hoặc std :: copy () để thực hiện? , có thể với hướng dẫn SIMD .

Nếu không, hạn chế, việc tối ưu hóa này không thể được thực hiện, ví dụ: xem xét:

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

Sau đó, forphiên bản làm cho:

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

trong khi memsetphiên bản tạo ra:

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

GCC có thực sự làm điều đó?

GCC 5.2.1.Linux x86-64 Ubuntu 15.10:

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

Với -O0, cả hai đều giống nhau.

Với -O3:

  • với hạn chế:

    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

    Hai memsetcuộc gọi như mong đợi.

  • không hạn chế: không có cuộc gọi stdlib, chỉ cần một vòng lặp rộng 16 vòng lặp mà tôi không có ý định sao chép ở đây :-)

Tôi không đủ kiên nhẫn để đánh giá chúng, nhưng tôi tin rằng phiên bản giới hạn sẽ nhanh hơn.

Quy tắc răng cưa nghiêm ngặt

Các restricttừ khóa chỉ ảnh hưởng đến con trỏ của các loại tương thích (ví dụ như hai int*) bởi vì các quy tắc nghiêm ngặt răng cưa nói rằng răng cưa loại không tương thích là hành vi không xác định theo mặc định, và do đó trình biên dịch có thể giả định nó không xảy ra và tối ưu hóa đi.

Xem: quy tắc răng cưa nghiêm ngặt là gì?

Nó hoạt động để tham khảo?

Theo các tài liệu GCC, nó thực hiện: https://gcc.gnu.org/onlinesocs/gcc-5.1.0/gcc/Restricted-Pointers.html với cú pháp:

int &__restrict__ rref

Thậm chí còn có một phiên bản cho thiscác chức năng thành viên:

void T::fn () __restrict__

asnwer tốt đẹp. Điều gì sẽ xảy ra nếu khử răng cưa nghiêm ngặt -fno-strict-aliasing, sau đó restrictsẽ không tạo ra sự khác biệt giữa các con trỏ cùng loại hoặc các loại khác nhau, không? (tôi đang đề cập đến "Từ khóa hạn chế chỉ ảnh hưởng đến con trỏ của các loại tương thích")
idclev 463035818

@ tobi303 Tôi không biết! Hãy cho tôi biết nếu bạn biết chắc chắn ;-)
Ciro Santilli 郝海东 冠状 病 六四

@jww vâng, đó là một cách tốt hơn để diễn đạt nó. Cập nhật.
Ciro Santilli 郝海东 冠状 病 事件

restrictkhông có nghĩa gì trong C ++. Nếu bạn gọi hàm thư viện C với restrictcác tham số từ chương trình C ++, bạn phải tuân theo hàm ý của điều đó. Về cơ bản, nếu restrictđược sử dụng trong API thư viện C, nó có nghĩa là bất cứ ai gọi nó từ bất kỳ ngôn ngữ nào, bao gồm FFI động từ Lisp.
Kaz

22

Không có gì. Nó đã được thêm vào tiêu chuẩn C99.


8
Điều đó không hoàn toàn đúng. Rõ ràng nó được hỗ trợ bởi một số trình biên dịch C ++ và một số người thực sự khuyên bạn nên sử dụng nó khi có sẵn, xem câu trả lời của tôi dưới đây.
Robert S. Barnes

18
@Robert S Barnes: Tiêu chuẩn C ++ không công nhận restrictlà một từ khóa. Do đó câu trả lời của tôi là chính xác. Những gì bạn mô tả là thực hiện hành vi cụ thể và một cái gì đó mà bạn không thực sự dựa vào.
dirkgently

26
@dirkgently: Với tất cả sự tôn trọng, tại sao không? Nhiều dự án được gắn với các phần mở rộng ngôn ngữ phi tiêu chuẩn cụ thể chỉ được hỗ trợ bởi các trình biên dịch cụ thể hoặc rất ít. Hạt nhân Linux và gcc xuất hiện trong tâm trí. Không có gì lạ khi gắn bó với một trình biên dịch cụ thể, hoặc thậm chí là một bản sửa đổi cụ thể của một trình biên dịch cụ thể trong toàn bộ vòng đời hữu ích của một dự án. Không phải mọi chương trình cần phải tuân thủ nghiêm ngặt.
Robert S. Barnes

7
@Rpbert S. Barnes: Tôi không thể nhấn mạnh thêm nữa tại sao bạn không nên phụ thuộc vào hành vi cụ thể thực hiện. Đối với Linux và gcc - hãy suy nghĩ và bạn sẽ thấy lý do tại sao chúng không phải là một ví dụ tốt trong phòng thủ của bạn. Tôi vẫn chưa thấy một phần mềm thành công vừa phải chạy trên một phiên bản trình biên dịch duy nhất trong suốt vòng đời của nó.
dirkgently

16
@Rpbert S. Barnes: Câu hỏi cho biết c ++. Không phải MSVC, không phải gcc, không phải AIX. Nếu acidzombie24 muốn các phần mở rộng cụ thể của trình biên dịch, thì anh ta nên nói / gắn thẻ như vậy.
KitsuneYMG

12

Đây là đề xuất ban đầu để thêm từ khóa này. Như dirkgently đã chỉ ra, đây là một tính năng của C99 ; nó không có gì để làm với C ++.


5
Nhiều trình biên dịch C ++ hỗ trợ __restrict__từ khóa giống hệt như tôi có thể nói.
Robert S. Barnes

Nó có mọi thứ để làm với C ++, vì các chương trình C ++ gọi thư viện C và thư viện C sử dụng restrict. Hành vi của chương trình C ++ trở nên không xác định nếu nó vi phạm các hạn chế ngụ ý restrict.
Kaz

@kaz Hoàn toàn sai. Nó không liên quan gì đến C ++ vì đây không phải là từ khóa hay tính năng của C ++ và nếu bạn sử dụng tệp tiêu đề C trong C ++, bạn phải xóa restricttừ khóa. Tất nhiên, nếu bạn chuyển các con trỏ bí danh cho một hàm C khai báo chúng bị hạn chế (mà bạn có thể làm từ C ++ hoặc C) thì nó không được xác định, nhưng đó là ở bạn.
Jim Balter

@JimBalter Tôi thấy, vì vậy điều bạn đang nói là các chương trình C ++ gọi thư viện C và thư viện C sử dụng restrict. Hành vi của chương trình C ++ trở nên không xác định nếu nó vi phạm các hạn chế ngụ ý bởi hạn chế. Nhưng điều này thực sự không liên quan gì đến C ++, vì nó "thuộc về bạn".
Kaz

5

Không có từ khóa như vậy trong C ++. Danh sách các từ khóa C ++ có thể được tìm thấy trong phần 2.11 / 1 của tiêu chuẩn ngôn ngữ C ++. restrictlà một từ khóa trong phiên bản C99 của ngôn ngữ C và không phải trong C ++.


5
Nhiều trình biên dịch C ++ hỗ trợ __restrict__từ khóa giống hệt như tôi có thể nói.
Robert S. Barnes

17
@Robert: Nhưng không có từ khóa như vậy trong C ++ . Những gì trình biên dịch riêng lẻ làm là việc riêng của họ, nhưng nó không phải là một phần của ngôn ngữ C ++.
jalf

4

Vì các tệp tiêu đề từ một số thư viện C sử dụng từ khóa, nên ngôn ngữ C ++ sẽ phải làm gì đó với nó .. ở mức tối thiểu, bỏ qua từ khóa, vì vậy chúng tôi không phải #define từ khóa vào một macro trống để loại bỏ từ khóa .


3
Tôi đoán rằng điều đó được xử lý bằng cách sử dụng một extern Ckhai báo hoặc bằng cách nó được bỏ âm thầm, như trường hợp với trình biên dịch AIX C / C ++, thay vào đó xử lý __rerstrict__từ khóa. Từ khóa đó cũng được hỗ trợ theo gcc để mã sẽ biên dịch tương tự dưới g ++.
Robert S. Barnes
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.