Kesalahan segmentasi terjadi ketika suatu program mencoba mengakses memori di luar area yang telah dialokasikan untuk itu.
Dalam hal ini, seorang programmer C yang berpengalaman dapat melihat bahwa masalahnya terjadi pada baris di mana sprintf
disebut. Tetapi jika Anda tidak dapat mengetahui di mana kesalahan segmentasi Anda terjadi, atau jika Anda tidak ingin repot membaca kode untuk mencoba mencari tahu, maka Anda dapat membangun program Anda dengan simbol debug (dengan gcc
, -g
flag melakukan ini ) dan kemudian jalankan melalui debugger.
Saya menyalin kode sumber Anda dan menempelkannya ke file yang saya beri nama slope.c
. Lalu saya membangunnya seperti ini:
gcc -Wall -g -o slope slope.c
(Ini -Wall
opsional. Hanya untuk membuatnya menghasilkan peringatan untuk lebih banyak situasi. Ini dapat membantu dalam mencari tahu apa yang mungkin salah, juga.)
Kemudian saya menjalankan program di debugger gdb
dengan menjalankan terlebih dahulu gdb ./slope
untuk memulai gdb
dengan program, dan kemudian, sekali di debugger, memberikan run
perintah kepada debugger:
ek@Kip:~/source$ gdb ./slope
GNU gdb (GDB) 7.5-ubuntu
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/ek/source/slope...done.
(gdb) run
Starting program: /home/ek/source/slope
warning: Cannot call inferior functions, you have broken Linux kernel i386 NX (non-executable pages) support!
Program received signal SIGSEGV, Segmentation fault.
0x001a64cc in _IO_default_xsputn () from /lib/i386-linux-gnu/libc.so.6
(Jangan khawatir tentang you have broken Linux kernel i386 NX
... support
pesan saya ; itu tidak mencegah gdb
dari digunakan secara efektif untuk men-debug program ini.)
Informasi itu sangat samar ... dan jika Anda tidak memiliki simbol debug diinstal untuk libc, maka Anda akan mendapatkan pesan lebih samar yang memiliki alamat heksadesimal alih-alih nama fungsi simbolik _IO_default_xsputn
. Untungnya, itu tidak masalah, karena apa yang benar-benar ingin kami ketahui adalah di mana dalam program Anda masalahnya terjadi.
Jadi, solusinya adalah melihat ke belakang, untuk melihat pemanggilan fungsi apa yang terjadi yang mengarah ke pemanggilan fungsi tertentu di pustaka sistem tempat SIGSEGV
sinyal akhirnya dipicu.
gdb
(dan debugger apa pun) memiliki fitur ini bawaan: ini disebut jejak stack atau backtrace . Saya menggunakan bt
perintah debugger untuk menghasilkan backtrace di gdb
:
(gdb) bt
#0 0x001a64cc in _IO_default_xsputn () from /lib/i386-linux-gnu/libc.so.6
#1 0x00178e04 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
#2 0x0019b234 in vsprintf () from /lib/i386-linux-gnu/libc.so.6
#3 0x0017ff7b in sprintf () from /lib/i386-linux-gnu/libc.so.6
#4 0x080484cc in calc_slope (input1=1, input2=150) at slope.c:26
#5 0x08048578 in main () at slope.c:52
(gdb)
Anda dapat melihat bahwa main
fungsi Anda memanggil calc_slope
fungsi (yang Anda maksudkan), dan kemudian calc_slope
panggilan sprintf
, yang (pada sistem ini) diimplementasikan dengan panggilan ke beberapa fungsi pustaka terkait lainnya.
Apa yang secara umum Anda minati adalah pemanggilan fungsi dalam program Anda yang memanggil fungsi di luar program Anda . Kecuali jika ada bug di perpustakaan / perpustakaan itu sendiri yang Anda gunakan (dalam hal ini, perpustakaan C standar yang libc
disediakan oleh file perpustakaan libc.so.6
), bug yang menyebabkan crash ada di program Anda dan sering akan berada di atau di dekat panggilan terakhir dalam program Anda.
Dalam hal ini, itu adalah:
#4 0x080484cc in calc_slope (input1=1, input2=150) at slope.c:26
Di situlah panggilan program Anda sprintf
. Kami tahu ini karena sprintf
merupakan langkah selanjutnya. Tetapi bahkan tanpa itu menyatakan itu, Anda tahu ini karena itulah yang terjadi pada baris 26 , dan ia mengatakan:
... at slope.c:26
Dalam program Anda, baris 26 berisi:
sprintf(s,"%d",curr);
(Anda harus selalu menggunakan editor teks yang secara otomatis menampilkan nomor baris, setidaknya untuk baris yang sedang Anda gunakan. Ini sangat membantu dalam menafsirkan kesalahan waktu kompilasi, dan masalah runtime yang terungkap saat menggunakan debugger.)
Sebagaimana dibahas dalam jawaban Dennis Kaarsemaker , s
adalah array satu byte. (Bukan nol, karena nilai yang Anda tetapkan ""
, adalah panjang satu byte, yaitu sama dengan { '\0' }
, dengan cara yang "Hello, world!\n"
sama dengan { 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\0' }
.)
Jadi, mengapa ini masih bisa bekerja pada beberapa platform (dan ternyata tidak ketika dikompilasi dengan VC9 untuk Windows)?
Orang sering mengatakan bahwa ketika Anda mengalokasikan memori dan kemudian mencoba mengakses memori di luarnya, itu menghasilkan kesalahan. Tapi itu tidak sepenuhnya benar. Menurut standar teknis C dan C ++, apa yang sebenarnya dihasilkan ini adalah perilaku yang tidak terdefinisi.
Dengan kata lain, apa pun bisa terjadi!
Namun, beberapa hal lebih mungkin daripada yang lain. Mengapa array kecil pada stack akan, pada beberapa implementasi, tampak berfungsi seperti array yang lebih besar pada stack?
Ini bermuara pada bagaimana alokasi tumpukan diimplementasikan, yang diizinkan bervariasi dari platform ke platform. Eksekusi Anda dapat mengalokasikan lebih banyak memori ke tumpukan daripada yang sebenarnya dimaksudkan untuk digunakan pada satu waktu. Kadang-kadang ini memungkinkan Anda untuk menulis ke lokasi memori yang belum Anda klaim secara eksplisit dalam kode Anda. Sangat mungkin inilah yang terjadi ketika Anda membangun program Anda di VC9.
Namun, Anda tidak harus mengandalkan perilaku ini bahkan di VC9. Ini berpotensi bergantung pada versi pustaka yang berbeda yang bisa ada pada sistem Windows yang berbeda. Tetapi yang lebih mungkin adalah masalah bahwa ruang stack tambahan dialokasikan dengan maksud bahwa itu akan benar-benar digunakan, dan karenanya sebenarnya dapat digunakan.Kemudian Anda mengalami mimpi buruk penuh "perilaku tidak terdefinisi," di mana, dalam hal ini, lebih dari satu variabel dapat disimpan di tempat yang sama, di mana menulis ke satu menimpa yang lain ... tetapi tidak selalu, karena kadang-kadang menulis ke variabel di-cache dalam register dan tidak benar-benar dilakukan segera (atau membaca ke variabel dapat di-cache, atau suatu variabel dapat diasumsikan sama dengan sebelumnya karena memori yang dialokasikan untuk itu diketahui oleh kompiler tidak dituliskan melalui variabel itu sendiri).
Dan itu membawa saya ke kemungkinan lain mengapa program bekerja ketika dibangun dengan VC9. Mungkin, dan agak mungkin, beberapa array atau variabel lain sebenarnya dialokasikan oleh program Anda (yang dapat mencakup dialokasikan oleh perpustakaan yang digunakan program Anda) untuk menggunakan ruang setelah array satu byte s
. Jadi, memperlakukan s
sebagai array lebih dari satu byte akan memiliki efek mengakses konten dari variabel / array tersebut, yang juga bisa menjadi buruk.
Kesimpulannya, ketika Anda memiliki kesalahan seperti ini, untungnya mendapatkan kesalahan seperti "kesalahan segmentasi" atau "kesalahan perlindungan umum." Ketika Anda tidak memilikinya, Anda mungkin tidak tahu sampai sudah terlambat bahwa program Anda memiliki perilaku yang tidak jelas.