Saya baru-baru ini mulai belajar C dan saya mengambil kelas dengan C sebagai subjek. Saat ini saya bermain-main dengan loop dan saya menjalankan beberapa perilaku aneh yang saya tidak tahu bagaimana menjelaskannya.
#include <stdio.h>
int main()
{
int array[10],i;
for (i = 0; i <=10 ; i++)
{
array[i]=0; /*code should never terminate*/
printf("test \n");
}
printf("%d \n", sizeof(array)/sizeof(int));
return 0;
}
Di laptop saya yang menjalankan Ubuntu 14.04, kode ini tidak rusak. Ini berjalan sampai selesai. Di komputer sekolah saya yang menjalankan CentOS 6.6, itu juga berjalan dengan baik. Pada Windows 8.1, loop tidak pernah berakhir.
Yang lebih aneh adalah ketika saya mengedit kondisi for
loop ke:, i <= 11
kode hanya berakhir pada laptop saya yang menjalankan Ubuntu. Itu tidak pernah berakhir di CentOS dan Windows.
Adakah yang bisa menjelaskan apa yang terjadi di memori dan mengapa berbagai OS menjalankan kode yang sama memberikan hasil yang berbeda?
EDIT: Saya tahu loop for keluar dari batas. Saya melakukannya dengan sengaja. Saya tidak tahu bagaimana perilakunya bisa berbeda di berbagai OS dan komputer.
i
disimpan tepat setelah akhirarray
, dan Anda menimpanyaarray[10]=0;
. Ini mungkin bukan kasus di build yang dioptimalkan pada platform yang sama, yang dapat disimpani
dalam register dan tidak pernah merujuknya dalam memori sama sekali.Jawaban:
Anda baru saja menemukan memori menghentak. Anda dapat membaca lebih lanjut tentang ini di sini: Apa itu "memori stomp"?
Ketika Anda mengalokasikan
int array[10],i;
, variabel-variabel tersebut masuk ke memori (khususnya, mereka dialokasikan pada tumpukan, yang merupakan blok memori yang terkait dengan fungsi).array[]
dani
mungkin berdekatan satu sama lain dalam memori. Tampaknya pada Windows 8.1,i
terletak diarray[10]
. Di CentOS,i
terletak diarray[11]
. Dan di Ubuntu, tidak ada di tempat (mungkin diarray[-1]
?).Coba tambahkan pernyataan debug ini ke kode Anda. Anda harus memperhatikan bahwa pada iterasi 10 atau 11,
array[i]
menunjuk padai
.sumber
array[10]
menghancurkan frame stack, misalnya. Bagaimana mungkin ada perbedaan antara kode dengan atau tanpa hasil debug? Jika alamati
tidak pernah dibutuhkan, kompiler dapat mengoptimalkannyai
. menjadi register, sehingga mengubah tata letak memori pada stack ...array[10]=0
. Jika Anda mengkompilasi kode Anda dengan optimasi, ini mungkin tidak akan terjadi. (Karena C memiliki aliasing aturan yang membatasi jenis akses memori yang harus diasumsikan berpotensi tumpang tindih memori lainnya. Sebagai variabel lokal yang Anda tidak pernah mengambil alamat, saya pikir seorang kompiler harus dapat mengasumsikan bahwa tidak ada yang alias itu. Lagi pula, tuliskan akhirnya array adalah perilaku yang tidak terdefinisi. Selalu berusaha keras untuk menghindari bergantungBug ini terletak di antara potongan kode ini:
Karena
array
hanya memiliki 10 elemen, dalam iterasi terakhirarray[10] = 0;
adalah buffer overflow. Buffer overflows adalah PERILAKU YANG TIDAK DIKENAL , yang berarti mereka mungkin memformat hard drive Anda atau menyebabkan setan terbang keluar dari hidung Anda.Hal ini cukup umum untuk semua variabel stack diletakkan berdekatan satu sama lain. Jika
i
terletak di manaarray[10]
menulis, maka UB akan mengatur ulangi
ke0
, sehingga mengarah ke loop yang tidak ditentukan.Untuk memperbaikinya, ubah kondisi loop menjadi
i < 10
.sumber
rm -rf /
bahkan ketika Anda tidak root, tidak "Memformat" seluruh drive tentu saja, tetapi masih menghancurkan semua data Anda. Aduh.Dalam apa yang seharusnya menjadi putaran terakhir dari loop, Anda menulis
array[10]
, tetapi hanya ada 10 elemen dalam array, diberi nomor 0 hingga 9. Spesifikasi bahasa C mengatakan bahwa ini adalah "perilaku tidak terdefinisi". Apa artinya ini dalam praktik adalah bahwa program Anda akan mencoba untuk menulis keint
bagian memori yang berukuran yang terletak segera setelaharray
dalam memori. Apa yang terjadi kemudian tergantung pada apa yang sebenarnya, terletak di sana, dan ini tidak hanya tergantung pada sistem operasi tetapi lebih pada kompiler, pada opsi kompiler (seperti pengaturan optimisasi), pada arsitektur prosesor, pada kode di sekitarnya. , dll. Bahkan dapat bervariasi dari eksekusi ke eksekusi, misalnya karena mengalamatkan ruang pengalamatan (mungkin tidak pada contoh mainan ini, tetapi hal itu terjadi dalam kehidupan nyata). Beberapa kemungkinan termasuk:i
. Loop tidak pernah berakhir karenai
restart pada 0.array
tepat di akhir halaman memori virtual dan halaman berikutnya tidak dipetakan.Apa yang Anda amati pada Windows adalah bahwa kompiler memutuskan untuk menempatkan variabel
i
segera setelah array dalam memori, sehinggaarray[10] = 0
akhirnya ditugaskani
. Di Ubuntu dan CentOS, kompiler tidak ditempatkan dii
sana. Hampir semua implementasi C melakukan pengelompokan variabel lokal dalam memori, pada tumpukan memori , dengan satu pengecualian utama: beberapa variabel lokal dapat ditempatkan sepenuhnya dalam register . Bahkan jika variabel ada di stack, urutan variabel ditentukan oleh kompiler, dan itu mungkin tidak hanya bergantung pada urutan dalam file sumber tetapi juga pada tipenya (untuk menghindari pemborosan memori untuk menyelaraskan batasan yang akan meninggalkan lubang) , pada nama mereka, pada beberapa nilai hash yang digunakan dalam struktur data internal kompiler, dll.Jika Anda ingin mengetahui apa yang diputuskan oleh kompiler Anda, Anda dapat memerintahkannya untuk menunjukkan kepada Anda kode assembler. Oh, dan belajar menguraikan assembler (lebih mudah daripada menulisnya). Dengan GCC (dan beberapa kompiler lain, terutama di dunia Unix), berikan opsi
-S
untuk menghasilkan kode assembler alih-alih biner. Misalnya, inilah cuplikan assembler untuk loop dari kompilasi dengan GCC di amd64 dengan opsi pengoptimalan-O0
(tanpa optimasi), dengan komentar ditambahkan secara manual:Di sini variabelnya
i
adalah 52 byte di bawah bagian atas stack, sedangkan array dimulai 48 byte di bawah bagian atas stack. Jadi kompiler ini kebetulan telah ditempatkani
tepat sebelum array; Anda akan menimpai
jika Anda kebetulan menulisarray[-1]
. Jika Anda mengubaharray[i]=0
kearray[9-i]=0
, Anda akan mendapatkan sebuah loop tak terbatas pada platform tertentu dengan opsi compiler tertentu.Sekarang mari kita kompilasi program Anda dengan
gcc -O1
.Itu lebih pendek! Kompiler tidak hanya menolak untuk mengalokasikan lokasi stack untuk
i
- itu hanya pernah disimpan dalam registerebx
- tetapi juga tidak mau repot untuk mengalokasikan memori apa punarray
, atau untuk menghasilkan kode untuk mengatur elemen-elemennya, karena ia menyadari bahwa tidak ada elemen yang pernah digunakan.Untuk membuat contoh ini lebih jitu, mari pastikan bahwa tugas array dilakukan dengan menyediakan sesuatu yang tidak dapat dioptimalkan oleh kompiler. Cara mudah untuk melakukannya adalah dengan menggunakan array dari file lain - karena kompilasi terpisah, kompiler tidak tahu apa yang terjadi pada file lain (kecuali itu dioptimalkan pada waktu tautan, yang
gcc -O0
ataugcc -O1
tidak). Buat file sumber yanguse_array.c
berisidan ubah kode sumber Anda menjadi
Kompilasi dengan
Kali ini kode assembler terlihat seperti ini:
Sekarang array ada di stack, 44 byte dari atas. Bagaimana dengan
i
? Itu tidak muncul di mana pun! Tetapi penghitung loop disimpan dalam registerrbx
. Ini tidak persisi
, tetapi alamatarray[i]
. Compiler telah memutuskan bahwa karena nilaii
tidak pernah digunakan secara langsung, tidak ada gunanya melakukan aritmatika untuk menghitung di mana menyimpan 0 selama setiap putaran dijalankan. Alih-alih alamat itu adalah variabel loop, dan aritmatika untuk menentukan batas-batas dilakukan sebagian pada waktu kompilasi (kalikan 11 iterasi dengan 4 byte per elemen array untuk mendapatkan 44) dan sebagian saat run time tetapi sekali dan untuk semua sebelum loop dimulai ( lakukan pengurangan untuk mendapatkan nilai awal).Bahkan pada contoh yang sangat sederhana ini, kita telah melihat bagaimana mengubah opsi kompiler (menghidupkan optimasi) atau mengubah sesuatu yang kecil (
array[i]
menjadiarray[9-i]
) atau bahkan mengubah sesuatu yang tampaknya tidak berhubungan (menambahkan panggilan keuse_array
) dapat membuat perbedaan yang signifikan terhadap apa yang dihasilkan oleh program yang dapat dieksekusi yang dihasilkan oleh kompiler tidak. Optimalisasi kompiler dapat melakukan banyak hal yang mungkin tampak tidak intuitif pada program yang menjalankan perilaku tidak terdefinisi . Itu sebabnya perilaku yang tidak terdefinisi dibiarkan sepenuhnya tidak terdefinisi. Ketika Anda menyimpang sedikit dari trek, dalam program dunia nyata, akan sangat sulit untuk memahami hubungan antara apa yang dilakukan kode dan apa yang seharusnya dilakukan, bahkan untuk programmer yang berpengalaman.sumber
Tidak seperti Java, C tidak melakukan pemeriksaan batas array, yaitu, tidak ada
ArrayIndexOutOfBoundsException
, tugas memastikan indeks array valid diserahkan kepada programmer. Melakukan ini dengan sengaja menyebabkan perilaku yang tidak terdefinisi, apa pun bisa terjadi.Untuk sebuah array:
indeks hanya valid dalam kisaran
0
hingga9
. Namun, Anda mencoba untuk:akses di
array[10]
sini, ubah kondisinya menjadii < 10
sumber
Anda memiliki pelanggaran batas, dan pada platform yang tidak berhenti, saya yakin Anda secara tidak sengaja melakukan pengaturan
i
ke nol pada akhir loop, sehingga mulai lagi.array[10]
tidak valid; ini berisi 10 elemen,array[0]
melaluiarray[9]
, danarray[10]
ke-11. Pengulangan Anda harus ditulis untuk berhenti sebelum10
, sebagai berikut:Di mana
array[10]
lahan ditentukan oleh implementasi, dan secara mengejutkan, pada dua platform Anda, itu akan mendarati
, di mana platform tersebut tampaknya diletakkan langsung setelahnyaarray
.i
diatur ke nol dan loop berlanjut selamanya. Untuk platform Anda yang lain,i
dapat ditemukan sebelumnyaarray
, atauarray
mungkin memiliki bantalan setelahnya.sumber
Anda menyatakan
int array[10]
saranaarray
memiliki indeks0
ke9
(10
elemen integer total yang dapat ditampungnya). Namun loop berikut,akan loop
0
untuk10
sarana11
waktu. Karenanya ketikai = 10
itu akan meluap buffer dan menyebabkan Perilaku tidak terdefinisi .Jadi coba ini:
atau,
sumber
Itu tidak terdefinisi pada
array[10]
, dan memberikan perilaku yang tidak terdefinisi seperti yang dijelaskan sebelumnya. Pikirkan itu seperti ini:Saya memiliki 10 item di troli belanjaan saya. Mereka:
0: Sekotak sereal
1: Roti
2: Susu
3: Pie
4: Telur
5: Kue
6: A 2 liter soda
7: Salad
8: Burger
9: Es krim
cart[10]
tidak terdefinisi, dan dapat memberikan pengecualian di luar batas pada beberapa kompiler. Tapi, ternyata banyak yang tidak. Item ke-11 yang terlihat jelas adalah item yang tidak ada di troli. Item ke-11 menunjuk, apa yang akan saya sebut, "item poltergeist." Itu tidak pernah ada, tapi itu ada di sana.Mengapa beberapa kompiler memberikan
i
indeksarray[10]
atauarray[11]
atau bahkanarray[-1]
karena pernyataan inisialisasi / deklarasi Anda. Beberapa kompiler menafsirkan ini sebagai:int
s untukarray[10]
dan satu lagiint
blok. Agar lebih mudah, letakkan di sebelah satu sama lain."array[10]
tidak menunjuk ke sanai
.i
diarray[-1]
(karena indeks array tidak dapat, atau tidak boleh, menjadi negatif), atau mengalokasikannya di tempat yang sama sekali berbeda karena OS dapat menanganinya, dan lebih aman.Beberapa kompiler menginginkan segalanya berjalan lebih cepat, dan beberapa kompiler lebih menyukai keamanan. Ini semua tentang konteksnya. Jika saya mengembangkan aplikasi untuk OS BREW kuno (OS dari telepon dasar), misalnya, itu tidak akan peduli dengan keamanan. Jika saya sedang mengembangkan untuk iPhone 6, maka itu bisa berjalan dengan cepat tidak peduli apa, jadi saya perlu penekanan pada keamanan. (Serius, sudahkah Anda membaca Panduan App Store Apple, atau membaca tentang pengembangan Swift dan Swift 2.0?)
sumber
Karena Anda membuat array ukuran 10, untuk kondisi loop harus sebagai berikut:
Saat ini Anda mencoba mengakses lokasi yang tidak ditetapkan dari memori menggunakan
array[10]
dan itu menyebabkan perilaku yang tidak ditentukan . Perilaku tidak terdefinisi berarti program Anda akan berperilaku dengan cara yang tidak ditentukan, sehingga dapat memberikan output yang berbeda dalam setiap eksekusi.sumber
Nah, kompiler C secara tradisional tidak memeriksa batasan. Anda bisa mendapatkan kesalahan segmentasi jika Anda merujuk ke lokasi yang bukan "milik" proses Anda. Namun, variabel lokal dialokasikan pada stack dan tergantung pada cara memori dialokasikan, area tepat di luar array (
array[10]
) mungkin milik segmen memori proses. Dengan demikian, tidak ada perangkap segmentasi kesalahan yang dilemparkan dan itulah yang tampaknya Anda alami. Seperti yang ditunjukkan orang lain, ini adalah perilaku yang tidak terdefinisi dalam C dan kode Anda mungkin dianggap tidak menentu. Karena Anda belajar C, Anda lebih baik membiasakan diri memeriksa batas-batas dalam kode Anda.sumber
Di luar kemungkinan memori dapat diletakkan sehingga upaya menulis untuk
a[10]
benar - benar menimpai
, mungkin juga kompilator pengoptimal mungkin menentukan bahwa tes loop tidak dapat dicapai dengan nilaii
lebih dari sepuluh tanpa kode setelah pertama kali mengakses elemen array tidak adaa[10]
.Karena upaya untuk mengakses elemen itu akan menjadi perilaku yang tidak terdefinisi, kompiler tidak akan memiliki kewajiban sehubungan dengan apa yang mungkin dilakukan program setelah titik itu. Lebih khusus, karena kompiler tidak memiliki kewajiban untuk menghasilkan kode untuk memeriksa indeks loop dalam hal apa pun di mana itu mungkin lebih besar dari sepuluh, itu tidak memiliki kewajiban untuk menghasilkan kode untuk memeriksanya sama sekali; alih-alih bisa berasumsi bahwa
<=10
tes akan selalu menghasilkan yang benar. Perhatikan bahwa ini akan benar bahkan jika kode akan membacaa[10]
daripada menulisnya.sumber
Ketika Anda beralih melewati
i==9
Anda menetapkan nol ke 'item array' yang sebenarnya terletak melewati array , sehingga Anda menimpa beberapa data lainnya. Kemungkinan besar Anda menimpai
variabel, yang terletak setelaha[]
. Dengan begitu Anda cukup mereseti
variabel ke nol dan dengan demikian restart loop.Anda dapat menemukan sendiri jika Anda mencetak
i
di loop:bukannya adil
Tentu saja hasil yang sangat tergantung pada alokasi memori untuk variabel Anda, yang pada gilirannya tergantung pada kompiler dan pengaturannya, sehingga umumnya Perilaku tidak terdefinisi - itu sebabnya hasil pada mesin yang berbeda atau sistem operasi yang berbeda atau pada kompiler yang berbeda mungkin berbeda.
sumber
kesalahannya adalah dalam array sebagian [10] w / c juga alamat dari i (int array [10], i;). ketika array [10] diatur ke 0 maka i akan menjadi 0 w / c me-reset seluruh loop dan menyebabkan infinite loop. akan ada infinite loop jika array [10] adalah antara 0-10. loop yang benar seharusnya untuk (i = 0; i <10; i ++) {...} int array [10], i; untuk array (i = 0; i <= 10; i ++) [i] = 0;
sumber
Saya akan menyarankan sesuatu yang saya tidak temukan di atas:
Coba tetapkan array [i] = 20;
Saya kira ini harus mengakhiri kode di mana-mana .. (mengingat Anda tetap saya <= 10 atau ll)
Jika ini berjalan, Anda dapat dengan tegas memutuskan bahwa jawaban yang ditentukan di sini sudah benar [jawaban yang terkait dengan memori menginjak satu untuk mantan.]
sumber
Ada dua hal yang salah di sini. Int i sebenarnya adalah elemen array, array [10], seperti yang terlihat pada stack. Karena Anda telah memungkinkan pengindeksan untuk benar-benar membuat array [10] = 0, indeks loop, i, tidak akan pernah melebihi 10. Buatlah
for(i=0; i<10; i+=1)
.i ++ adalah, as K&R menyebutnya, 'gaya buruk'. Ini bertambah i dengan ukuran i, bukan 1. i ++ untuk pointer matematika dan i + = 1 untuk aljabar. Meskipun ini tergantung pada kompiler, itu bukan konvensi yang baik untuk portabilitas.
sumber
i
BUKAN elemen arraya[10]
, tidak ada kewajiban atau bahkan saran bagi kompiler untuk meletakkannya di stack segera setelahnyaa[]
- ia juga dapat ditemukan sebelum array, atau dipisahkan dengan beberapa ruang tambahan. Bahkan dapat dialokasikan di luar memori utama, misalnya dalam register CPU. Itu juga tidak benar++
untuk pointer dan bukan untuk integer. Benar-benar salah adalah 'i ++ menambah saya dengan ukuran i' - baca deskripsi operator dalam definisi bahasa!i
- iniint
bertipe. Ini adalah bilangan bulat , bukan pointer; integer, digunakan sebagai indeks untukarray
,.