Apa arti kata kunci pembatasan dalam C ++?

182

Saya selalu tidak yakin, apa arti kata kunci pembatasan dalam C ++?

Apakah ini berarti dua atau lebih pointer yang diberikan pada fungsi tidak tumpang tindih? Apa lagi artinya?

Tikar
sumber
23
restrictadalah kata kunci c99. Ya, Rpbert S. Barnes, saya tahu sebagian besar penyusun mendukung __restrict__. Anda akan mencatat bahwa apa pun dengan garis bawah ganda adalah, menurut definisi, implementasi spesifik dan karenanya BUKAN C ++ , tetapi merupakan versi spesifik kompilator.
KitsuneYMG
5
Apa? Hanya karena implementasi spesifik tidak membuatnya bukan C ++; C ++ memungkinkan implementasi hal-hal spesifik secara eksplisit, dan tidak melarangnya atau menjadikannya bukan C ++.
Alice
4
@Alice KitsuneYMG berarti bukan bagian dari ISO C ++, dan sebaliknya dianggap sebagai ekstensi C ++. Pembuat kompiler diizinkan untuk membuat dan mendistribusikan ekstensi mereka sendiri, yang hidup berdampingan dengan ISO C ++ dan bertindak sebagai bagian dari penambahan tidak resmi yang biasanya kurang atau tidak portabel untuk C ++. Contohnya adalah C ++ Managed lama milik MS, dan C ++ / CLI yang lebih baru. Contoh lainnya adalah arahan preprocessor dan makro yang dipasok oleh beberapa kompiler, seperti #warningarahan umum , atau makro tanda tangan fungsi ( __PRETTY_FUNCTION__pada GCC, __FUNCSIG__pada MSVC, dll.).
Justin Time - Pasang kembali Monica
4
@ Alice Setahu saya, C ++ 11 tidak mengamanatkan dukungan penuh untuk semua C99, juga C ++ 14 atau yang saya tahu tentang C ++ 17. restricttidak dianggap sebagai kata kunci C ++ (lihat en.cppreference.com/w/cpp/keyword ), dan pada kenyataannya, satu-satunya penyebutan restrictdalam standar C ++ 11 (lihat open-std.org/jtc1/sc22/wg21 /docs/papers/2012/n3337.pdf , salinan FDIS dengan perubahan editorial kecil, §17.2 [library.c], halaman PDF 413) menyatakan bahwa:
Waktu Justin - Kembalikan Monica
4
@ Alice Bagaimana bisa begitu? Saya menyatakan bagian yang mengatakan bahwa restrictharus dihilangkan dari (dikecualikan dari, ditinggalkan dari) tanda tangan dan semantik fungsi pustaka C ketika fungsi-fungsi tersebut dimasukkan dalam pustaka standar C ++. Atau dengan kata lain, saya menyatakan fakta yang mengatakan bahwa jika tanda tangan fungsi pustaka standar C berisi restrictdalam C, restrictkata kunci harus dihapus dari tanda tangan setara C ++.
Justin Time - Pasang kembali Monica

Jawaban:

143

Dalam makalahnya, Memory Optimization , Christer Ericson mengatakan bahwa sementara restrictini belum menjadi bagian dari standar C ++, ia didukung oleh banyak kompiler dan ia merekomendasikan penggunaannya ketika tersedia:

batasi kata kunci

! Baru dengan standar ANSI / ISO C 1999

! Belum dalam standar C ++, tetapi didukung oleh banyak kompiler C ++

! Hanya sebuah petunjuk, jadi mungkin tidak melakukan apa-apa dan masih menyesuaikan diri

Pointer yang memenuhi syarat (atau referensi) ...

! ... pada dasarnya adalah janji kepada kompiler bahwa untuk lingkup pointer, target pointer hanya akan diakses melalui pointer itu (dan pointer disalin dari itu).

Dalam kompiler C ++ yang mendukungnya mungkin harus berperilaku sama seperti pada C.

Lihat posting SO ini untuk perincian: Penggunaan realistis kata kunci 'pembatasan' C99?

Butuh setengah jam untuk membaca kertas Ericson, itu menarik dan sepadan dengan waktu.

Edit

Saya juga menemukan bahwa kompiler AIX C / C ++__restrict__ IBM mendukung kata kunci .

g ++ juga sepertinya mendukung hal ini karena program berikut mengkompilasi dengan bersih di 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;
}

Saya juga menemukan artikel yang bagus tentang penggunaan restrict:

Demistifying The Restrict Keyword

Edit2

Saya menemukan artikel yang secara khusus membahas penggunaan pembatasan dalam program C ++:

Muat-tekan-toko dan kata kunci __restrict

Selain itu, Microsoft Visual C ++ juga mendukung __restrictkata kunci .

Robert S. Barnes
sumber
2
Tautan kertas Optimalisasi Memori sudah mati, berikut ini tautan ke audio dari presentasi GDC-nya. gdcvault.com/play/1022689/Memory
Grimeh
1
@ EnnMichael: Jelas jika Anda akan menggunakannya dalam proyek C ++ portabel, Anda harus #ifndef __GNUC__ #define __restrict__ /* no-op */atau serupa. Dan mendefinisikannya __restrictjika _MSC_VERdidefinisikan.
Peter Cordes
96

Seperti yang dikatakan orang lain, jika tidak ada artinya pada C ++ 14 , jadi mari kita pertimbangkan __restrict__ekstensi GCC yang melakukan hal yang sama dengan C99 restrict.

C99

restrictmengatakan bahwa dua petunjuk tidak dapat menunjuk ke wilayah memori yang tumpang tindih. Penggunaan paling umum adalah untuk argumen fungsi.

Ini membatasi bagaimana fungsi dapat dipanggil, tetapi memungkinkan untuk lebih kompilasi optimasi.

Jika penelepon tidak mengikuti restrictkontrak, perilaku tidak terdefinisi.

The C99 N1256 rancangan 6.7.3 / 7 "Jenis kualifikasi" mengatakan:

Tujuan penggunaan pembatasan kualifikasi (seperti kelas penyimpanan register) adalah untuk mempromosikan optimasi, dan menghapus semua instance kualifikasi dari semua unit terjemahan preprocessing yang menyusun program yang sesuai tidak mengubah artinya (yaitu, perilaku yang dapat diamati).

dan 6.7.3.1 "Definisi formal pembatasan" memberikan detail yang mengerikan.

Kemungkinan pengoptimalan

The Wikipedia Contoh yang sangat mencerahkan.

Ini jelas menunjukkan bagaimana memungkinkan untuk menyimpan satu instruksi perakitan .

Tanpa batasan:

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

Perakitan semu:

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

Dengan batasan:

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

Perakitan semu:

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

Apakah GCC benar-benar melakukannya?

g++ 4.8 Linux x86-64:

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

Dengan -O0, mereka sama.

Dengan -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) 

Untuk yang belum tahu, konvensi pemanggilan adalah:

  • rdi = parameter pertama
  • rsi = parameter kedua
  • rdx = parameter ketiga

Output GCC bahkan lebih jelas daripada artikel wiki: 4 instruksi vs 3 instruksi.

Array

Sejauh ini kami memiliki penghematan instruksi tunggal, tetapi jika pointer mewakili array yang akan dilewati, kasus penggunaan umum, maka banyak instruksi dapat disimpan, seperti yang disebutkan oleh supercat dan michael .

Pertimbangkan misalnya:

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

Karena itu restrict, kompiler pintar (atau manusia), dapat mengoptimalkannya untuk:

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

Yang berpotensi jauh lebih efisien karena dapat dioptimalkan perakitan pada implementasi libc yang layak (seperti glibc) Apakah lebih baik menggunakan std :: memcpy () atau std :: copy () dalam hal kinerja? , mungkin dengan instruksi SIMD .

Tanpa, batasi, optimasi ini tidak dapat dilakukan, misalnya pertimbangkan:

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

Kemudian forversi membuat:

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

sedangkan memsetversi membuat:

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

Apakah GCC benar-benar melakukannya?

GCC 5.2.1.Linux x86-64 Ubuntu 15.10:

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

Dengan -O0, keduanya sama.

Dengan -O3:

  • dengan batasan:

    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

    Dua memsetpanggilan seperti yang diharapkan.

  • tanpa batasan: tidak ada panggilan stdlib, hanya loop lebar iterasi 16 terbuka yang saya tidak ingin mereproduksi di sini :-)

Saya belum memiliki kesabaran untuk membandingkan mereka, tetapi saya percaya bahwa versi pembatasan akan lebih cepat.

Aturan aliasing yang ketat

Kata restrictkunci hanya memengaruhi pointer dari tipe yang kompatibel (misalnya dua int*) karena aturan aliasing yang ketat mengatakan bahwa aliasing tipe yang tidak kompatibel adalah perilaku yang tidak terdefinisi secara default, sehingga kompiler dapat menganggap itu tidak terjadi dan mengoptimalkannya.

Lihat: Apa aturan aliasing yang ketat?

Apakah ini berfungsi untuk referensi?

Menurut dokumen GCC, ia melakukannya: https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Restricted-Pointers.html dengan sintaks:

int &__restrict__ rref

Bahkan ada versi untuk thisfungsi anggota:

void T::fn () __restrict__
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
asnwer bagus. Bagaimana jika aliasing ketat dinonaktifkan -fno-strict-aliasing, maka restrictseharusnya tidak ada perbedaan antara pointer dari tipe yang sama atau tipe yang berbeda, bukan? (saya merujuk ke "Batasan kata kunci hanya memengaruhi pointer dari jenis yang kompatibel")
idclev 463035818
@ tobi303 Saya tidak tahu! Beri tahu saya jika Anda mengetahuinya dengan pasti ;-)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
@jww ya, itu cara yang lebih baik untuk mengungkapkannya. Diperbarui.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
restrictmemang berarti sesuatu dalam C ++. Jika Anda memanggil fungsi pustaka C dengan restrictparameter dari program C ++, Anda harus mematuhi implikasinya. Pada dasarnya, jika restrictdigunakan dalam C library API, itu berarti sesuatu bagi siapa saja yang memanggilnya dari bahasa apa pun, termasuk FFI dinamis dari Lisp.
Kaz
22

Tidak ada. Itu ditambahkan ke standar C99.

secara langsung
sumber
8
Itu tidak sepenuhnya benar. Rupanya itu didukung oleh beberapa kompiler C ++ dan beberapa orang sangat merekomendasikan penggunaannya ketika tersedia, lihat jawaban saya di bawah ini.
Robert S. Barnes
18
@Robert S Barnes: Standar C ++ tidak dikenali restrictsebagai kata kunci. Karenanya jawaban saya benar. Apa yang Anda gambarkan adalah penerapan perilaku spesifik dan sesuatu yang tidak seharusnya Anda andalkan.
dirkgently
27
@ luar biasa: Dengan segala hormat, mengapa tidak? Banyak proyek terkait dengan ekstensi bahasa non-standar spesifik yang didukung oleh hanya beberapa penyusun khusus atau sangat sedikit. Kernel Linux dan gcc muncul di pikiran. Ini tidak biasa untuk tetap dengan kompiler tertentu, atau bahkan revisi spesifik dari kompiler tertentu untuk seluruh masa manfaat proyek. Tidak semua program harus benar-benar sesuai.
Robert S. Barnes
7
@Rpbert S. Barnes: Saya tidak mungkin menekankan lebih jauh mengapa Anda tidak harus bergantung pada implementasi perilaku spesifik. Adapun Linux dan gcc - pikirkan dan Anda akan melihat mengapa mereka bukan contoh yang baik dalam pertahanan Anda. Saya belum melihat bahkan perangkat lunak yang cukup sukses dijalankan pada versi kompiler tunggal untuk seumur hidup.
dirkgently
16
@Rpbert S. Barnes: Pertanyaannya mengatakan c ++. Bukan MSVC, bukan gcc, bukan AIX. Jika acidzombie24 ingin mengkompilasi ekstensi spesifik, maka ia seharusnya mengatakannya.
KitsuneYMG
12

Ini adalah proposal asli untuk menambahkan kata kunci ini. Seperti yang ditunjukkan secara langsung, ini adalah fitur C99 ; tidak ada hubungannya dengan C ++.

beristirahat
sumber
5
Banyak kompiler C ++ mendukung __restrict__kata kunci yang identik sejauh yang saya tahu.
Robert S. Barnes
Ini semua berhubungan dengan C ++, karena program C ++ memanggil C library, dan C library digunakan restrict. Perilaku program C ++ menjadi tidak terdefinisi jika melanggar batasan yang disiratkan oleh restrict.
Kaz
@ kaz Benar-benar salah. Ini tidak ada hubungannya dengan C ++ karena itu bukan kata kunci atau fitur C ++, dan jika Anda menggunakan file header C di C ++ Anda harus menghapus restrictkata kunci. Tentu saja jika Anda meneruskan pointer alias ke fungsi C yang menyatakan mereka dibatasi (yang dapat Anda lakukan dari C ++ atau C) maka itu tidak terdefinisi, tapi itu pada Anda.
Jim Balter
@ JimBalter saya mengerti, jadi apa yang Anda katakan adalah bahwa program C ++ memanggil C library, dan C library digunakan restrict. Perilaku program C ++ menjadi tidak terdefinisi jika melanggar batasan yang dinyatakan oleh pembatasan. Tapi ini sebenarnya tidak ada hubungannya dengan C ++, karena itu "pada Anda".
Kaz
5

Tidak ada kata kunci seperti itu di C ++. Daftar kata kunci C ++ dapat ditemukan di bagian 2.11 / 1 dari standar bahasa C ++. restrictadalah kata kunci dalam versi C99 bahasa C dan bukan dalam C ++.

Semut
sumber
5
Banyak kompiler C ++ mendukung __restrict__kata kunci yang identik sejauh yang saya tahu.
Robert S. Barnes
18
@Robert: Tapi tidak ada kata kunci seperti itu di C ++ . Apa yang dilakukan kompiler perorangan adalah bisnis mereka sendiri, tetapi itu bukan bagian dari bahasa C ++.
jalf
4

Karena file header dari beberapa pustaka C menggunakan kata kunci, bahasa C ++ harus melakukan sesuatu tentang itu .. minimal, mengabaikan kata kunci, jadi kami tidak perlu # mendefinisikan kata kunci ke makro kosong untuk menekan kata kunci .

Johan Boulé
sumber
3
Saya kira itu ditangani dengan menggunakan extern Cdeklarasi, atau dengan itu diam-diam dijatuhkan, seperti halnya dengan kompiler A / C ++ AIX, yang alih-alih menangani __rerstrict__kata kunci. Kata kunci itu juga didukung di bawah gcc sehingga kode tersebut akan dikompilasi di bawah g ++.
Robert S. Barnes