Ketika bertanya tentang perilaku umum yang tidak terdefinisi dalam C , orang kadang-kadang merujuk pada aturan aliasing yang ketat.
Apa yang mereka bicarakan?
804
Ketika bertanya tentang perilaku umum yang tidak terdefinisi dalam C , orang kadang-kadang merujuk pada aturan aliasing yang ketat.
Apa yang mereka bicarakan?
c
danc++faq
.Jawaban:
Situasi umum di mana Anda menemukan masalah aliasing yang ketat adalah ketika overlay struct (seperti perangkat / pesan jaringan) ke buffer ukuran kata sistem Anda (seperti pointer ke
uint32_t
s atauuint16_t
s). Ketika Anda overlay struct ke buffer tersebut, atau buffer ke struct tersebut melalui casting pointer Anda dapat dengan mudah melanggar aturan aliasing yang ketat.Jadi dalam pengaturan seperti ini, jika saya ingin mengirim pesan ke sesuatu, saya harus memiliki dua petunjuk yang tidak kompatibel yang menunjuk ke potongan memori yang sama. Saya kemudian mungkin secara naif kode sesuatu seperti ini (pada sistem dengan
sizeof(int) == 2
):Aturan aliasing yang ketat membuat pengaturan ini ilegal: mendereferensi penunjuk yang alias objek yang bukan tipe yang kompatibel atau salah satu dari tipe lain yang diizinkan oleh C 2011 6.5 paragraf 7 1 adalah perilaku yang tidak terdefinisi. Sayangnya, Anda masih bisa membuat kode dengan cara ini, mungkin mendapatkan beberapa peringatan, mengkompilasinya dengan baik, hanya untuk memiliki perilaku aneh yang tidak terduga ketika Anda menjalankan kode.
(GCC tampaknya agak tidak konsisten dalam kemampuannya untuk memberikan peringatan alias, kadang-kadang memberi kita peringatan ramah dan kadang-kadang tidak.)
Untuk melihat mengapa perilaku ini tidak terdefinisi, kita harus berpikir tentang apa aturan aliasing yang ketat membeli kompiler. Pada dasarnya, dengan aturan ini, tidak perlu memikirkan memasukkan instruksi untuk menyegarkan konten dari
buff
setiap putaran. Alih-alih, ketika mengoptimalkan, dengan beberapa asumsi yang tidak didukung tentang aliasing, ini dapat menghilangkan instruksi tersebut, memuatbuff[0]
danbuff[1
] ke register CPU sekali sebelum loop dijalankan, dan mempercepat tubuh loop. Sebelum alias ketat diperkenalkan, kompiler harus hidup dalam keadaan paranoia bahwa isibuff
dapat berubah kapan saja dari mana saja oleh siapa saja. Jadi untuk mendapatkan keunggulan kinerja tambahan, dan dengan asumsi kebanyakan orang tidak mengetik pointer kata-kata, aturan aliasing yang ketat diperkenalkan.Perlu diingat, jika Anda pikir contohnya dibuat-buat, ini bahkan dapat terjadi jika Anda meneruskan buffer ke fungsi lain yang melakukan pengiriman untuk Anda, jika sebaliknya Anda memilikinya.
Dan tulis ulang loop kami sebelumnya untuk memanfaatkan fungsi yang nyaman ini
Kompiler mungkin atau mungkin tidak bisa atau cukup pintar untuk mencoba menyatukan SendMessage dan mungkin atau mungkin tidak memutuskan untuk memuat atau tidak memuat buff lagi. Jika
SendMessage
merupakan bagian dari API lain yang dikompilasi secara terpisah, ia mungkin memiliki instruksi untuk memuat konten buff. Kemudian lagi, mungkin Anda berada di C ++ dan ini adalah beberapa implementasi templated header saja yang menurut kompiler dapat inline. Atau mungkin itu hanya sesuatu yang Anda tulis dalam file .c Anda untuk kenyamanan Anda sendiri. Bagaimanapun perilaku yang tidak terdefinisi mungkin masih terjadi. Bahkan ketika kita mengetahui sebagian dari apa yang terjadi di bawah tenda, itu masih merupakan pelanggaran aturan sehingga tidak ada perilaku yang jelas yang dijamin. Jadi hanya dengan membungkus suatu fungsi yang mengambil kata buffer terbatas kami tidak selalu membantu.Jadi bagaimana saya mengatasi ini?
Gunakan serikat pekerja. Kebanyakan kompiler mendukung ini tanpa mengeluh tentang alias ketat. Ini diizinkan di C99 dan secara eksplisit diizinkan di C11.
Anda dapat menonaktifkan alias ketat di kompiler Anda ( f [no-] strict-aliasing di gcc))
Anda dapat menggunakan
char*
untuk alias daripada kata-kata sistem Anda. Aturan memungkinkan pengecualian untukchar*
(termasuksigned char
danunsigned char
). Itu selalu dianggap bahwachar*
alias jenis lain. Namun ini tidak akan bekerja sebaliknya: tidak ada asumsi bahwa struct Anda alias buffer chars.Hati-hati pemula
Ini hanya satu ladang ranjau yang potensial ketika overlay dua jenis satu sama lain. Anda juga harus belajar tentang endianness , penyelarasan kata , dan cara menangani masalah penyelarasan melalui pengemasan struct dengan benar.
Catatan kaki
1 Jenis yang dapat diakses oleh C 2011 6.5 7 adalah nilai:
sumber
unsigned char*
digunakan jauhchar*
sebagai gantinya? Saya cenderung menggunakanunsigned char
daripadachar
sebagai tipe yang mendasaribyte
karena byte saya tidak ditandatangani dan saya tidak ingin keanehan perilaku yang ditandatangani (terutama wrt to overflow)unsigned char *
tidak apa-apa.uint32_t* buff = malloc(sizeof(Msg));
serikat Anda dan selanjutnyaunsigned int asBuffer[sizeof(Msg)];
akan memiliki ukuran yang berbeda dan tidak ada yang benar. Themalloc
panggilan mengandalkan pada keselarasan 4 byte bawah tenda (tidak melakukannya) dan serikat pekerja akan 4 kali lebih besar dari itu perlu ... Saya mengerti bahwa itu adalah untuk kejelasan tetapi mengganggu saya tidak ada-the- less ...Penjelasan terbaik yang saya temukan adalah oleh Mike Acton, Understanding Strict Aliasing . Ini sedikit berfokus pada pengembangan PS3, tapi itu pada dasarnya hanya GCC.
Dari artikel:
Jadi pada dasarnya jika Anda memiliki
int*
menunjuk ke beberapa memori yang mengandungint
dan kemudian Anda mengarahkanfloat*
ke memori itu dan menggunakannya sebagaifloat
Anda melanggar aturan. Jika kode Anda tidak menghargai ini, maka pengoptimal kompiler kemungkinan besar akan memecahkan kode Anda.Pengecualian aturan adalah a
char*
, yang diizinkan untuk menunjuk ke jenis apa pun.sumber
Ini adalah aturan aliasing yang ketat, ditemukan di bagian 3.10 dari standar C ++ 03 (jawaban lain memberikan penjelasan yang baik, tetapi tidak ada yang memberikan aturan itu sendiri):
Kata-kata C ++ 11 dan C ++ 14 (perubahan ditekankan):
Dua perubahan kecil: glvalue bukan lvalue , dan klarifikasi kasus agregat / serikat pekerja.
Perubahan ketiga membuat jaminan yang lebih kuat (melonggarkan aturan aliasing yang kuat): Konsep baru jenis serupa yang sekarang aman untuk alias.
Juga kata-kata C (C99; ISO / IEC 9899: 1999 6.5 / 7; kata-kata yang persis sama digunakan dalam ISO / IEC 9899: 2011 §6.5 ¶7):
sumber
wow(&u->s1,&u->s2)
akan perlu legal bahkan ketika pointer digunakan untuk memodifikasiu
, dan itu akan meniadakan sebagian besar optimisasi bahwa aturan aliasing dirancang untuk memfasilitasi.Catatan
Ini dikutip dari "Apa Aturan Ketegasan Mengasingkan Diri dan Mengapa Kita Peduli?"menulis
Apa itu alias ketat?
Dalam C dan C ++ aliasing harus dilakukan dengan tipe ekspresi apa yang diizinkan untuk mengakses nilai yang disimpan. Dalam C dan C ++ standar menentukan jenis ekspresi yang diizinkan untuk alias jenis apa. Kompilator dan pengoptimal diizinkan untuk menganggap kami mengikuti aturan aliasing secara ketat, oleh karena itu istilah aturan aliasing ketat . Jika kami mencoba mengakses nilai menggunakan tipe yang tidak diizinkan, itu diklasifikasikan sebagai perilaku tidak terdefinisi ( UB ). Setelah kami memiliki perilaku yang tidak terdefinisi, semua taruhan dimatikan, hasil dari program kami tidak lagi dapat diandalkan.
Sayangnya dengan pelanggaran alias ketat, kita akan sering mendapatkan hasil yang kita harapkan, meninggalkan kemungkinan versi kompiler masa depan dengan optimasi baru akan memecahkan kode yang kita anggap valid. Ini tidak diinginkan dan merupakan tujuan yang berharga untuk memahami aturan alias yang ketat dan bagaimana cara menghindari pelanggaran.
Untuk memahami lebih lanjut mengapa kami peduli, kami akan membahas masalah yang muncul saat melanggar aturan aliasing yang ketat, mengetik hukuman karena teknik umum yang digunakan dalam hukuman jenis sering melanggar aturan alias yang ketat dan cara mengetik pun dengan benar.
Contoh pendahuluan
Mari kita lihat beberapa contoh, lalu kita bisa bicara tentang apa yang standar katakan, periksa beberapa contoh lebih lanjut dan kemudian lihat bagaimana menghindari alias ketat dan menangkap pelanggaran yang kita lewatkan. Berikut adalah contoh yang tidak mengejutkan ( contoh langsung ):
Kami memiliki int * yang menunjuk ke memori yang ditempati oleh int dan ini adalah alias yang valid. Pengoptimal harus mengasumsikan bahwa penugasan melalui ip dapat memperbarui nilai yang ditempati oleh x .
Contoh berikut menunjukkan aliasing yang mengarah ke perilaku tidak terdefinisi ( contoh langsung ):
Dalam fungsi foo kita mengambil int * dan float * , dalam contoh ini kita memanggil foo dan mengatur kedua parameter untuk menunjuk ke lokasi memori yang sama yang dalam contoh ini berisi int . Catatan, reinterpret_cast memberi tahu kompiler untuk memperlakukan ekspresi seolah-olah memiliki tipe yang ditentukan oleh parameter templatnya. Dalam hal ini kami mengatakan untuk memperlakukan ekspresi & x seolah-olah ia memiliki tipe float * . Kami mungkin secara naif mengharapkan hasil dari cout kedua menjadi 0 tetapi dengan optimasi yang diaktifkan menggunakan -O2 gcc dan dentang menghasilkan hasil berikut:
Yang mungkin tidak diharapkan tetapi sangat valid karena kami telah memanggil perilaku yang tidak terdefinisi. Sebuah pelampung tidak bisa secara sah alias sebuah int objek. Oleh karena itu pengoptimal dapat mengasumsikan konstanta 1 yang disimpan ketika dereferencing i akan menjadi nilai kembali karena toko melalui f tidak dapat secara valid memengaruhi objek int . Memasukkan kode di Compiler Explorer menunjukkan ini persis seperti apa yang terjadi ( contoh langsung ):
Pengoptimal menggunakan Analisis Alias Berbasis Jenis (TBAA) mengasumsikan 1 akan dikembalikan dan langsung memindahkan nilai konstan ke register eax yang membawa nilai kembali. TBAA menggunakan aturan bahasa tentang jenis apa yang diizinkan alias untuk mengoptimalkan pemuatan dan penyimpanan. Dalam hal ini TBAA tahu bahwa float tidak bisa alias dan int dan mengoptimalkan beban i .
Sekarang, ke Buku Aturan
Apa sebenarnya yang menurut standar ini diizinkan dan tidak boleh kita lakukan? Bahasa standar tidak langsung, jadi untuk setiap item saya akan mencoba memberikan contoh kode yang menunjukkan artinya.
Apa yang dikatakan standar C11?
Standar C11 mengatakan yang berikut ini di bagian 6.5 Ekspresi paragraf 7 :
gcc / clang memiliki ekstensi dan juga yang memungkinkan menetapkan int * ke int * yang tidak ditandatangani meskipun mereka bukan tipe yang kompatibel.
Apa yang dikatakan Standar Draf C ++ 17
Draf standar C ++ 17 pada bagian [basic.lval] paragraf 11 mengatakan:
Perlu dicatat char yang ditandatangani tidak termasuk dalam daftar di atas, ini adalah perbedaan penting dari C yang mengatakan tipe karakter .
Apa itu Tipe Punning
Kami telah sampai pada titik ini dan kami mungkin bertanya-tanya, mengapa kami ingin alias untuk? Jawabannya biasanya adalah mengetik pun , seringkali metode yang digunakan melanggar aturan aliasing yang ketat.
Kadang-kadang kita ingin menghindari sistem tipe dan menafsirkan objek sebagai tipe yang berbeda. Ini disebut type punning , untuk menafsirkan kembali segmen memori sebagai tipe lain. Jenis punning berguna untuk tugas-tugas yang menginginkan akses ke representasi objek yang mendasarinya untuk dilihat, dipindahkan, atau dimanipulasi. Area umum yang kami temukan jenis punning yang digunakan adalah kompiler, serialisasi, kode jaringan, dll ...
Secara tradisional ini telah dicapai dengan mengambil alamat objek, melemparkannya ke pointer dari jenis yang ingin kita tafsirkan sebagai dan kemudian mengakses nilai, atau dengan kata lain dengan alias. Sebagai contoh:
Seperti yang telah kita lihat sebelumnya, ini bukan alias yang valid, jadi kami menerapkan perilaku yang tidak terdefinisi. Tapi kompiler tradisional tidak mengambil keuntungan dari aturan aliasing yang ketat dan jenis kode ini biasanya hanya bekerja, sayangnya pengembang sudah terbiasa melakukan hal-hal seperti ini. Metode alternatif umum untuk jenis hukuman adalah melalui serikat pekerja, yang berlaku di C tetapi perilaku tidak terdefinisi dalam C ++ ( lihat contoh langsung ):
Ini tidak valid dalam C ++ dan beberapa orang menganggap tujuan serikat pekerja semata-mata untuk menerapkan jenis varian dan merasa menggunakan serikat pekerja untuk jenis hukuman adalah penyalahgunaan.
Bagaimana cara kita Mengetik Pun dengan benar?
Metode standar untuk mengetik jenis dalam C dan C ++ adalah memcpy . Ini mungkin tampak agak berat, tetapi pengoptimal harus mengenali penggunaan memcpy untuk jenis hukuman dan mengoptimalkannya dan menghasilkan register untuk mendaftar pindah. Sebagai contoh jika kita tahu int64_t berukuran sama dengan ganda :
kita bisa menggunakan memcpy :
Pada tingkat optimisasi yang memadai setiap kompiler modern yang layak menghasilkan kode yang identik dengan metode reinterpret_cast yang disebutkan sebelumnya atau metode gabungan untuk jenis punning . Meneliti kode yang dihasilkan, kami melihatnya hanya menggunakan mov saja ( contoh Compiler Explorer langsung ).
C ++ 20 dan bit_cast
Dalam C ++ 20 kita dapat memperoleh bit_cast ( implementasi tersedia dalam tautan dari proposal ) yang memberikan cara sederhana dan aman untuk mengetik-pun serta dapat digunakan dalam konteks constexpr.
Berikut ini adalah contoh cara menggunakan bit_cast untuk mengetik pun int yang tidak ditandatangani ke float , ( lihat langsung ):
Dalam kasus di mana jenis Ke dan Dari tidak memiliki ukuran yang sama, itu mengharuskan kita untuk menggunakan struktur perantara15. Kami akan menggunakan struct yang berisi array karakter sizeof (unsigned int) ( mengasumsikan 4 byte unsigned int ) sebagai tipe Dari dan unsigned int sebagai tipe Ke . :
Sangat disayangkan bahwa kita membutuhkan tipe perantara ini tetapi itu adalah batasan bit_cast saat ini .
Menangkap Pelanggaran yang Mengasingkan Ketat
Kami tidak memiliki banyak alat bagus untuk menangkap aliasing ketat di C ++, alat yang kami miliki akan menangkap beberapa kasus pelanggaran aliasing ketat dan beberapa kasus pemuatan dan penyimpanan yang tidak selaras.
gcc menggunakan flag -fstrict-aliasing dan -Wstrict-aliasing dapat menangkap beberapa case meskipun bukan tanpa false positive / negative. Misalnya, kasus-kasus berikut akan menghasilkan peringatan dalam gcc ( lihat langsung ):
meskipun tidak akan menangkap kasus tambahan ini ( lihat langsung ):
Meskipun dentang memungkinkan bendera ini, tampaknya itu tidak benar-benar menerapkan peringatan.
Alat lain yang kami miliki adalah ASan yang dapat menangkap banyak barang dan toko yang tidak selaras. Meskipun ini bukan pelanggaran alias langsung yang ketat, namun ini adalah hasil umum dari pelanggaran alias yang ketat. Sebagai contoh kasus-kasus berikut akan menghasilkan kesalahan runtime ketika dibangun dengan dentang menggunakan -fsanitize = alamat
Alat terakhir yang akan saya rekomendasikan adalah C ++ spesifik dan tidak sepenuhnya alat tetapi praktik pengkodean, jangan izinkan gips C-style. Baik gcc dan dentang akan menghasilkan diagnostik untuk cast gaya-C menggunakan -Wold-style-cast . Ini akan memaksa setiap jenis permainan kata yang tidak terdefinisi untuk menggunakan reinterpret_cast, secara umum reinterpret_cast harus menjadi bendera untuk peninjauan kode yang lebih dekat. Juga lebih mudah untuk mencari basis kode Anda untuk reinterpret_cast untuk melakukan audit.
Untuk C kami memiliki semua alat yang sudah dibahas dan kami juga memiliki tis-interpreter, penganalisa statis yang secara mendalam menganalisis program untuk sebagian besar bahasa C. Diberikan versi C dari contoh sebelumnya di mana menggunakan -fstrict-aliasing melewatkan satu kasus ( lihat langsung )
tis-interpeter dapat menangkap ketiganya, contoh berikut memanggil tis-kernal sebagai tis-interpreter (output diedit untuk singkatnya):
Akhirnya ada TySan yang saat ini dalam pengembangan. Pembersih ini menambahkan tipe memeriksa informasi dalam segmen memori bayangan dan memeriksa akses untuk melihat apakah mereka melanggar aturan alias. Alat tersebut berpotensi dapat menangkap semua pelanggaran alias tetapi mungkin memiliki overhead run-time yang besar.
sumber
reinterpret_cast
mungkin dilakukan atau apa yangcout
mungkin berarti. (Tidak apa-apa menyebutkan C ++ tetapi pertanyaan aslinya adalah tentang C dan IIUC contoh-contoh ini dapat ditulis dalam bahasa C.)Aliasing yang ketat tidak hanya merujuk ke pointer, tetapi juga mempengaruhi referensi, saya menulis makalah tentang itu untuk meningkatkan wiki pengembang dan diterima dengan sangat baik sehingga saya mengubahnya menjadi halaman di situs web konsultasi saya. Ini menjelaskan sepenuhnya apa itu, mengapa hal itu membingungkan banyak orang dan apa yang harus dilakukan. Kertas Putih Aliasing Yang Ketat . Secara khusus ini menjelaskan mengapa serikat pekerja adalah perilaku berisiko untuk C ++, dan mengapa menggunakan memcpy adalah satu-satunya portable fix di C dan C ++. Semoga ini bermanfaat.
sumber
Sebagai tambahan untuk apa yang sudah ditulis Doug T., berikut adalah contoh kasus sederhana yang mungkin memicunya dengan gcc:
check.c
Kompilasi dengan
gcc -O2 -o check check.c
. Biasanya (dengan sebagian besar versi gcc yang saya coba) ini menghasilkan "masalah aliasing yang ketat", karena kompilator mengasumsikan bahwa "h" tidak boleh alamat yang sama dengan "k" dalam fungsi "centang". Karena itu kompiler mengoptimalkanif (*h == 5)
pergi dan selalu memanggil printf.Bagi mereka yang tertarik di sini adalah kode assembler x64, diproduksi oleh gcc 4.6.3, berjalan di ubuntu 12.04.2 untuk x64:
Jadi jika kondisi benar-benar hilang dari kode assembler.
sumber
long long*
danint64_t
*). Orang mungkin berharap bahwa sebuah kompiler waras harus mengenali bahwa along long*
danint64_t*
dapat mengakses penyimpanan yang sama jika disimpan secara identik, tetapi perlakuan seperti itu tidak lagi modis.Jenis punning via cast pointer (sebagai lawan menggunakan union) adalah contoh utama dari melanggar alias ketat.
sumber
fpsync()
arahan antara menulis sebagai fp dan membaca sebagai int atau sebaliknya [pada implementasi dengan integer terpisah dan jalur pipa dan cache FPU , arahan semacam itu mungkin mahal, tetapi tidak semahal kompiler melakukan sinkronisasi seperti itu pada setiap akses serikat]. Atau suatu implementasi dapat menentukan bahwa nilai yang dihasilkan tidak akan pernah dapat digunakan kecuali dalam keadaan menggunakan Common Initial Sequences.Menurut alasan C89, penulis Standar tidak ingin mengharuskan kompiler memberikan kode seperti:
harus diminta untuk memuat kembali nilai
x
antara penugasan dan pernyataan kembali sehingga memungkinkan untuk kemungkinan yangp
menunjukx
, dan penugasan untuk*p
dapat akibatnya mengubah nilaix
. Gagasan bahwa seorang kompiler harus berhak berasumsi bahwa tidak akan ada alias dalam situasi seperti di atas adalah tidak kontroversial.Sayangnya, para penulis C89 menulis aturan mereka dengan cara yang, jika dibaca secara harfiah, akan membuat bahkan fungsi berikut memohon Perilaku Tidak Terdefinisi:
karena ia menggunakan nilai tipe
int
untuk mengakses objek tipestruct S
, danint
tidak di antara tipe yang dapat digunakan mengaksesstruct S
. Karena tidak masuk akal untuk memperlakukan semua penggunaan anggota tipe non-karakter dari struct dan serikat sebagai Perilaku Tidak Terdefinisi, hampir semua orang mengakui bahwa setidaknya ada beberapa keadaan di mana nilai suatu jenis dapat digunakan untuk mengakses objek dari tipe lain. . Sayangnya, Komite Standar C telah gagal untuk menentukan keadaan apa itu.Sebagian besar masalah adalah hasil dari Laporan Cacat # 028, yang bertanya tentang perilaku program seperti:
Laporan Cacat # 28 menyatakan bahwa program ini memanggil Perilaku Tidak Terdefinisi karena tindakan menulis anggota serikat tipe "ganda" dan membaca salah satu tipe "int" memunculkan perilaku yang Ditetapkan Implementasi. Alasan seperti itu tidak masuk akal, tetapi membentuk dasar bagi aturan Tipe Efektif yang tidak perlu mempersulit bahasa saat tidak melakukan apa pun untuk mengatasi masalah aslinya.
Cara terbaik untuk menyelesaikan masalah asli mungkin dengan memperlakukan catatan kaki tentang tujuan aturan seolah-olah itu normatif, dan membuat aturan tidak dapat diterapkan kecuali dalam kasus yang sebenarnya melibatkan akses yang saling bertentangan menggunakan alias. Diberikan sesuatu seperti:
Tidak ada konflik di dalamnya
inc_int
karena semua akses ke penyimpanan yang diakses melalui*p
dilakukan dengan nilai tipe yang tinggiint
, dan tidak ada konflik di dalamtest
karenap
terlihat berasal daristruct S
, dan pada saats
digunakan, semua akses ke penyimpanan yang akan dibuat melaluip
akan sudah terjadi.Jika kode diubah sedikit ...
Di sini, ada konflik alias antara
p
dan akses kes.x
pada baris yang ditandai karena pada saat itu dalam eksekusi referensi lain ada yang akan digunakan untuk mengakses penyimpanan yang sama .Seandainya Laporan Cacat 028 mengatakan contoh asli meminta UB karena tumpang tindih antara penciptaan dan penggunaan dua petunjuk, yang akan membuat segalanya lebih jelas tanpa harus menambahkan "Tipe Efektif" atau kompleksitas lainnya.
sumber
Setelah membaca banyak jawaban, saya merasa perlu menambahkan sesuatu:
Aliasing yang ketat (yang akan saya jelaskan sedikit) adalah penting karena :
Akses memori bisa mahal (berdasarkan kinerja), itulah sebabnya data dimanipulasi dalam register CPU sebelum ditulis kembali ke memori fisik.
Jika data dalam dua register CPU yang berbeda akan ditulis ke ruang memori yang sama, kami tidak dapat memprediksi data mana yang akan "bertahan" ketika kami kode dalam C.
Dalam perakitan, di mana kita mengkode pemuatan dan pembongkaran register CPU secara manual, kita akan tahu data mana yang tetap utuh. Tapi C (untungnya) abstrak detail ini.
Karena dua pointer dapat menunjuk ke lokasi yang sama di memori, ini dapat menghasilkan kode kompleks yang menangani kemungkinan tabrakan .
Kode tambahan ini lambat dan mengganggu kinerja karena menjalankan operasi baca / tulis memori ekstra yang lebih lambat dan (mungkin) tidak perlu.
The Aturan aliasing ketat memungkinkan kita untuk menghindari kode mesin berlebihan dalam kasus-kasus di mana harus aman untuk mengasumsikan bahwa dua pointer tidak menunjuk ke blok memori yang sama (lihat juga
restrict
kata kunci).Status aliasing yang ketat aman untuk mengasumsikan bahwa pointer ke tipe yang berbeda menunjuk ke lokasi yang berbeda dalam memori.
Jika kompiler memperhatikan bahwa dua pointer menunjuk ke tipe yang berbeda (misalnya, a
int *
dan afloat *
), itu akan menganggap alamat memori berbeda dan itu tidak akan melindungi terhadap benturan alamat memori, menghasilkan kode mesin yang lebih cepat.Sebagai contoh :
Mari kita asumsikan fungsi berikut:
Untuk menangani kasus di mana
a == b
(kedua pointer menunjuk ke memori yang sama), kita perlu memesan dan menguji cara kita memuat data dari memori ke register CPU, sehingga kode mungkin berakhir seperti ini:memuat
a
danb
dari memori.tambahkan
a
keb
.simpan
b
dan muat ulanga
.(simpan dari register CPU ke memori dan muat dari memori ke register CPU).
tambahkan
b
kea
.simpan
a
(dari register CPU) ke memori.Langkah 3 sangat lambat karena perlu mengakses memori fisik. Namun, itu diperlukan untuk melindungi terhadap contoh di mana
a
danb
menunjuk ke alamat memori yang sama.Aliasing yang ketat akan memungkinkan kami untuk mencegah hal ini dengan memberi tahu kompiler bahwa alamat memori ini sangat berbeda (yang, dalam hal ini, akan memungkinkan optimasi lebih lanjut yang tidak dapat dilakukan jika pointer berbagi alamat memori).
Ini dapat diceritakan ke kompiler dengan dua cara, dengan menggunakan tipe yang berbeda untuk menunjuk. yaitu:
Menggunakan
restrict
kata kunci. yaitu:Sekarang, dengan memenuhi aturan Stasing Aliasing, langkah 3 dapat dihindari dan kode akan berjalan secara signifikan lebih cepat.
Bahkan, dengan menambahkan
restrict
kata kunci, seluruh fungsi dapat dioptimalkan untuk:memuat
a
danb
dari memori.tambahkan
a
keb
.simpan hasil untuk
a
dan untukb
.Optimalisasi ini tidak dapat dilakukan sebelumnya, karena kemungkinan tabrakan (di mana
a
danb
akan menjadi tiga kali lipat daripada dua kali lipat).sumber
b
(tidak memuat ulang) dan memuat ulanga
. Saya harap ini lebih jelas sekarang.restrict
, tetapi saya akan berpikir bahwa yang terakhir akan dalam kebanyakan keadaan lebih efektif, dan melonggarkan beberapa kendalaregister
akan memungkinkannya untuk mengisi beberapa kasus di manarestrict
tidak akan membantu. Saya tidak yakin itu pernah "penting" untuk memperlakukan Standar sebagai menggambarkan sepenuhnya semua kasus di mana programmer harus mengharapkan kompiler untuk mengenali bukti aliasing, daripada hanya menggambarkan tempat-tempat di mana kompiler harus mengandaikan aliasing bahkan ketika tidak ada bukti tertentu itu ada .restrict
kata kunci meminimalkan tidak hanya kecepatan operasi tetapi jumlah mereka juga, yang bisa bermakna ... Maksudku, bagaimanapun juga, operasi tercepat adalah tidak ada operasi sama sekali :)Aliasing yang ketat tidak memungkinkan tipe pointer yang berbeda untuk data yang sama.
Artikel ini akan membantu Anda memahami masalah ini secara terperinci.
sumber
int
dan struct yang berisi aint
).Secara teknis di C ++, aturan aliasing yang ketat mungkin tidak pernah berlaku.
Perhatikan definisi tipuan ( * operator ):
Juga dari definisi glvalue
Jadi dalam setiap jejak program yang didefinisikan dengan baik, glvalue merujuk ke suatu objek. Jadi aturan aliasing yang ketat tidak berlaku, tidak pernah. Ini mungkin bukan yang diinginkan oleh para desainer.
sumber
int foo;
, apa yang diakses oleh ekspresi nilai*(char*)&foo
? Apakah itu tipe objekchar
? Apakah objek itu muncul pada saat yang samafoo
? Apakah tulisan akanfoo
mengubah nilai yang disimpan dari objek jenis yang disebutkan di ataschar
? Jika demikian, apakah ada aturan yang akan memungkinkan nilai yang disimpan dari objek bertipechar
dapat diakses menggunakan nilai ltipeint
?int i;
membuat empat objek dari setiap jenis karakterin addition to one of type
int? I see no way to apply a consistent definition of "object" which would allow for operations on both
* (char *) & i` dani
. Akhirnya, tidak ada dalam Standar yang memungkinkan bahkanvolatile
pointer yang memenuhi syarat untuk mengakses register perangkat keras yang tidak memenuhi definisi "objek".