Saya menetapkan nilai dalam program C ++ di luar batas seperti ini:
#include <iostream>
using namespace std;
int main()
{
int array[2];
array[0] = 1;
array[1] = 2;
array[3] = 3;
array[4] = 4;
cout << array[3] << endl;
cout << array[4] << endl;
return 0;
}
Program mencetak 3
dan 4
. Seharusnya tidak mungkin. Saya menggunakan g ++ 4.3.3
Berikut adalah kompilasi dan jalankan perintah
$ g++ -W -Wall errorRange.cpp -o errorRange
$ ./errorRange
3
4
Hanya ketika menetapkan array[3000]=3000
apakah itu memberi saya kesalahan segmentasi.
Jika gcc tidak memeriksa batasan array, bagaimana saya bisa yakin jika program saya benar, karena dapat menyebabkan beberapa masalah serius nanti?
Saya mengganti kode di atas dengan
vector<int> vint(2);
vint[0] = 0;
vint[1] = 1;
vint[2] = 2;
vint[5] = 5;
cout << vint[2] << endl;
cout << vint[5] << endl;
dan yang ini juga tidak menghasilkan kesalahan.
vector
tidak secara otomatis mengubah ukuran ketika mengakses elemen di luar batas! Itu hanya UB!Jawaban:
Selamat datang di teman terbaik setiap programmer C / C ++: Perilaku Tidak Terdefinisi .
Ada banyak hal yang tidak ditentukan oleh standar bahasa, karena berbagai alasan. Ini salah satunya.
Secara umum, setiap kali Anda menemukan perilaku yang tidak terdefinisi, apa pun bisa terjadi. Aplikasi mungkin macet, mungkin membeku, mungkin mengeluarkan drive CD-ROM Anda atau membuat setan keluar dari hidung Anda. Ini dapat memformat hard drive Anda atau mengirim email semua porno Anda ke nenek Anda.
Bahkan mungkin, jika Anda benar-benar sial, tampaknya berfungsi dengan benar.
Bahasa hanya mengatakan apa yang harus terjadi jika Anda mengakses elemen dalam batas-batas array. Tidak ditentukan apa yang terjadi jika Anda keluar dari batas. Mungkin tampaknya bekerja hari ini, pada compiler, tetapi tidak legal C atau C ++, dan tidak ada jaminan bahwa hal itu masih akan bekerja pada saat Anda menjalankan program. Atau yang memiliki data penting tidak ditimpa bahkan sekarang, dan Anda hanya tidak mengalami masalah, bahwa itu adalah akan menyebabkan - belum.
Adapun mengapa tidak ada batas memeriksa, ada beberapa aspek untuk jawabannya:
std::vector
templat kelas, yang memungkinkan keduanya.operator[]
dirancang agar efisien. Standar bahasa tidak mensyaratkan bahwa ia melakukan pengecekan batas (meskipun tidak melarangnya juga). Vektor juga memilikiat()
fungsi anggota yang dijamin untuk melakukan pemeriksaan batas. Jadi di C ++, Anda mendapatkan yang terbaik dari kedua dunia jika Anda menggunakan vektor. Anda mendapatkan kinerja seperti array tanpa memeriksa batas, dan Anda mendapatkan kemampuan untuk menggunakan akses yang diperiksa batas ketika Anda menginginkannya.sumber
Menggunakan g ++, Anda dapat menambahkan opsi baris perintah:
-fstack-protector-all
.Pada contoh Anda ini menghasilkan sebagai berikut:
Itu tidak benar-benar membantu Anda menemukan atau memecahkan masalah, tetapi setidaknya segfault akan memberi tahu Anda bahwa ada sesuatu yang salah.
sumber
-fsanitize=address
yang menangkap bug ini baik pada waktu kompilasi (jika mengoptimalkan) dan saat runtime.-fsanitize=undefined,address
. Tetapi perlu dicatat bahwa ada kasus sudut langka dengan perpustakaan std, ketika akses di luar batas tidak terdeteksi oleh pembersih . Untuk alasan ini saya akan merekomendasikan untuk menggunakan-D_GLIBCXX_DEBUG
opsi tambahan , yang menambahkan lebih banyak cek.g ++ tidak memeriksa batas array, dan Anda mungkin menimpa sesuatu dengan 3,4 tetapi tidak ada yang benar-benar penting, jika Anda mencoba dengan angka yang lebih tinggi Anda akan mendapatkan crash.
Anda hanya menimpa bagian-bagian tumpukan yang tidak digunakan, Anda dapat melanjutkan sampai Anda mencapai akhir ruang yang dialokasikan untuk tumpukan dan akhirnya akan macet
EDIT: Anda tidak memiliki cara untuk menghadapinya, mungkin penganalisa kode statis dapat mengungkapkan kegagalan itu, tetapi itu terlalu sederhana, Anda mungkin memiliki kegagalan yang serupa (tetapi lebih kompleks) tidak terdeteksi bahkan untuk penganalisa statis
sumber
Itu perilaku yang tidak terdefinisi sejauh yang saya tahu. Jalankan program yang lebih besar dengan itu dan itu akan crash di suatu tempat di sepanjang jalan. Pengecekan batas bukan bagian dari array mentah (atau bahkan std :: vector).
Gunakan std :: vector dengan
std::vector::iterator
's sehingga Anda tidak perlu khawatir.Edit:
Hanya untuk bersenang-senang, jalankan ini dan lihat berapa lama sampai Anda crash:
Sunting2:
Jangan jalankan itu.
Sunting3:
OK, berikut ini adalah pelajaran singkat tentang array dan hubungannya dengan pointer:
Ketika Anda menggunakan pengindeksan array, Anda benar-benar menggunakan pointer dalam penyamaran (disebut "referensi"), yang secara otomatis direferensikan. Inilah sebabnya mengapa alih-alih * (array [1]), array [1] secara otomatis mengembalikan nilai pada nilai itu.
Saat Anda memiliki pointer ke array, seperti ini:
Kemudian "array" dalam deklarasi kedua benar-benar membusuk menjadi pointer ke array pertama. Ini adalah perilaku yang setara dengan ini:
Ketika Anda mencoba mengakses di luar apa yang Anda alokasikan, Anda benar-benar hanya menggunakan pointer ke memori lain (yang tidak akan dikeluhkan oleh C ++). Mengambil contoh program saya di atas, itu setara dengan ini:
Kompiler tidak akan mengeluh karena dalam pemrograman, Anda sering harus berkomunikasi dengan program lain, terutama sistem operasi. Ini dilakukan dengan pointer cukup sedikit.
sumber
Petunjuk
Jika Anda ingin memiliki array ukuran batasan cepat dengan pemeriksaan kesalahan rentang, coba gunakan boost :: array , (juga std :: tr1 :: array dari
<tr1/array>
itu akan menjadi kontainer standar dalam spesifikasi C ++ berikutnya). Jauh lebih cepat daripada std :: vector. Ini cadangan memori pada heap atau instance kelas, seperti int array [].Ini adalah contoh kode sederhana:
Program ini akan mencetak:
sumber
C atau C ++ tidak akan memeriksa batas-batas akses array.
Anda mengalokasikan array pada stack. Mengindeks array via
array[3]
sama dengan *(array + 3)
, di mana array adalah pointer ke & array [0]. Ini akan menghasilkan perilaku yang tidak terdefinisi.Salah satu cara untuk menangkap ini kadang-kadang dalam C adalah dengan menggunakan pemeriksa statis, seperti belat . Jika Anda menjalankan:
di,
maka Anda akan mendapatkan peringatan:
sumber
Anda tentu menimpa tumpukan Anda, tetapi program ini cukup sederhana sehingga efek dari ini tidak diperhatikan.
sumber
Jalankan ini melalui Valgrind dan Anda mungkin melihat kesalahan.
Seperti yang ditunjukkan Falaina, valgrind tidak mendeteksi banyak contoh tumpukan korupsi. Saya baru saja mencoba sampel di bawah valgrind, dan memang melaporkan nol kesalahan. Namun, Valgrind dapat berperan dalam menemukan banyak jenis masalah memori lainnya, hanya saja tidak terlalu berguna dalam kasus ini kecuali Anda memodifikasi bulid Anda untuk menyertakan opsi --stack-check. Jika Anda membangun dan menjalankan sampel sebagai
valgrind akan melaporkan kesalahan.
sumber
Perilaku yang tidak terdefinisi menguntungkan Anda. Memori apa pun yang Anda hancurkan tampaknya tidak memiliki hal yang penting. Perhatikan bahwa C dan C ++ tidak melakukan batas memeriksa array, jadi hal-hal seperti itu tidak akan tertangkap pada waktu kompilasi atau run.
sumber
Ketika Anda menginisialisasi array dengan
int array[2]
, ruang untuk 2 bilangan bulat dialokasikan; tetapi pengidentifikasiarray
hanya menunjuk ke awal ruang itu. Ketika Anda kemudian mengaksesarray[3]
danarray[4]
, kompiler kemudian hanya menambah alamat yang menunjuk ke tempat nilai-nilai itu akan, jika array cukup panjang; coba akses sesuatu sepertiarray[42]
tanpa menginisialisasi terlebih dahulu, Anda akan mendapatkan nilai apa pun yang sudah ada di memori di lokasi itu.Edit:
Info lebih lanjut tentang pointer / array: http://home.netcom.com/~tjensen/ptr/pointers.htm
sumber
ketika Anda mendeklarasikan array int [2]; Anda memesan 2 ruang memori masing-masing 4 byte (program 32bit). jika Anda mengetik array [4] dalam kode Anda, kode itu masih sesuai dengan panggilan yang valid, tetapi hanya pada saat run time saja ia akan melemparkan pengecualian yang tidak tertangani. C ++ menggunakan manajemen memori manual. Ini sebenarnya adalah celah keamanan yang digunakan untuk meretas program
ini dapat membantu memahami:
int * somepointer;
somepointer [0] = somepointer [5];
sumber
Seperti yang saya mengerti, variabel lokal dialokasikan pada stack, jadi keluar dari batas pada stack Anda sendiri hanya dapat menimpa beberapa variabel lokal lainnya, kecuali Anda terlalu banyak dan melebihi ukuran stack Anda. Karena Anda tidak memiliki variabel lain yang dideklarasikan dalam fungsi Anda - itu tidak menyebabkan efek samping. Cobalah mendeklarasikan variabel / array lain tepat setelah yang pertama dan lihat apa yang akan terjadi dengannya.
sumber
Ketika Anda menulis 'array [indeks]' di C, ia menerjemahkannya ke instruksi mesin.
Terjemahan berjalan seperti:
Hasilnya membahas sesuatu yang mungkin, atau mungkin tidak, menjadi bagian dari array. Sebagai gantinya kecepatan instruksi mesin yang menyala-nyala Anda kehilangan jaring pengaman komputer yang memeriksa barang-barang untuk Anda. Jika Anda teliti dan berhati-hati, itu bukan masalah. Jika Anda ceroboh atau membuat kesalahan, Anda terbakar. Terkadang mungkin menghasilkan instruksi yang tidak valid yang menyebabkan pengecualian, terkadang tidak.
sumber
Pendekatan yang bagus yang sering saya lihat dan saya telah digunakan sebenarnya adalah untuk menyuntikkan beberapa elemen tipe NULL (atau yang dibuat, seperti
uint THIS_IS_INFINITY = 82862863263;
) di akhir array.Kemudian pada pemeriksaan kondisi loop,
TYPE *pagesWords
adalah beberapa jenis array pointer:Solusi ini tidak akan mengatakan apakah array diisi dengan
struct
tipe.sumber
Seperti yang disebutkan sekarang dalam pertanyaan menggunakan std :: vector :: at akan menyelesaikan masalah dan melakukan pemeriksaan terikat sebelum mengakses.
Jika Anda memerlukan array ukuran konstan yang terletak di tumpukan sebagai kode pertama Anda gunakan C ++ 11 container std :: array baru; sebagai vektor ada std :: array :: at function. Bahkan fungsi tersebut ada di semua kontainer standar yang memiliki arti, yaitu, di mana operator [] didefinisikan :( deque, map, unordered_map) dengan pengecualian std :: bitset di mana ia disebut std :: bitset: :uji.
sumber
libstdc ++, yang merupakan bagian dari gcc, memiliki mode debug khusus untuk pengecekan kesalahan. Ini diaktifkan oleh flag compiler
-D_GLIBCXX_DEBUG
. Di antara hal-hal lain itu tidak terbatas memeriksastd::vector
dengan biaya kinerja. Ini demo online dengan versi terbaru gcc.Jadi sebenarnya Anda dapat melakukan batas memeriksa dengan mode libstdc ++ debug tetapi Anda harus melakukannya hanya saat pengujian karena biayanya kinerja yang terkenal dibandingkan dengan mode libstdc ++ normal.
sumber
Jika Anda sedikit mengubah program Anda:
(Perubahan modal - masukkan huruf kecil jika Anda akan mencoba ini.)
Anda akan melihat bahwa variabel foo telah dibuang. Kode Anda akan menyimpan nilai ke dalam array [3] dan array [4] yang tidak ada, dan dapat mengambilnya dengan benar, tetapi penyimpanan aktual yang digunakan berasal dari foo .
Jadi Anda dapat "lolos" dengan melampaui batas-batas array dalam contoh asli Anda, tetapi dengan biaya menyebabkan kerusakan di tempat lain - kerusakan yang mungkin terbukti sangat sulit untuk didiagnosis.
Mengapa tidak ada batas otomatis memeriksa - program yang ditulis dengan benar tidak memerlukannya. Setelah itu dilakukan, tidak ada alasan untuk melakukan run-time bounds memeriksa dan melakukan hal itu hanya akan memperlambat program. Yang terbaik untuk mengetahui semuanya selama desain dan pengkodean.
C ++ didasarkan pada C, yang dirancang sedekat mungkin dengan bahasa assembly.
sumber