Tidak dapat dikompilasi dengan GCC di Ubuntu 12.04

9

Saya mencoba mengkompilasi dan menjalankan program C di bawah ini pada mesin Ubuntu & Windows saya dengan GCC & VC9. Namun, saya menghadapi masalah di bawah ini:

Di mesin Ubuntu:

GCC mengkompilasi dengan baik, tetapi ketika dijalankan, saya ditunjukkan prompt ini:

Segmentation Fault (Core Dump).

Di mesin Windows:

VC9 Mengkompilasi & berjalan dengan baik. GCC mengkompilasi dengan baik, tetapi proses berakhir ketika program dijalankan.

Perlu bantuan ahli Anda di sini. Ini kode saya:

#include <string.h>
#include <stdio.h>

int calc_slope(int input1,int input2)
{
    int sum=0;
    int start=input1;
    int end=input2;
    int curr=start;

    //some validation:
    if (input1>input2)
        return -1;


    while(curr<=end)
    {
        if (curr>100)
        {
            char *s="";
            int length;
            int left;
            int right;
            int cent;

            sprintf(s,"%d",curr);
            length=strlen(s);
            s++;
            do
            {
                //printf("curr=%d char=%c pointer=%d length=%d \n",curr,*s,s,length);
                left = *(s-1) - '0';
                cent = *s - '0';
                right = *(s+1) - '0';
                //printf("curr=%d l=%d c=%d r=%d\n",curr,left,cent,right);
                if ( (cent>left && cent>right) || (cent<left && cent<right) )
                {
                    sum+=1; //we have either a maxima or a minima.
                }

                s++;
            } while (*(s+1)!='\0');
        }
        curr++;
    }

    return sum;
}

int main()
{
    printf("%d",calc_slope(1,150));
    return 0;
}

Memperbarui:

Penghargaan diberikan kepada Eliah karena tidak hanya membantu saya melacak kesalahan, tetapi juga memperkenalkan saya ke gdbdan alat penelusuran baliknya ( bt) yang sangat membantu dalam men-debug program yang disusun gcc. Ini adalah versi yang dimodifikasi, saya berhasil setelah beberapa percobaan dan kesalahan:

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

int calc_slope(int input1,int input2)
{
    int sum=0;
    int start=input1;
    int end=input2;
    int curr=start;

    //some validation:
    if (input1>input2)
        return -1;


    while(curr<=end)
    {
        if (curr>100)
        {
            int size=10;
            char *s=(char*)malloc((size+1) * sizeof(char));
            int left;
            int right;
            int cent;

            sprintf(s,"%d",curr);
            s++;
            do
            {
                left = *(s-1) - '0';
                cent = *s - '0';
                right = *(s+1) - '0';
                if ( (cent>left && cent>right) || (cent<left && cent<right) )
                {
                    sum+=1; //we have either a maxima or a minima.
                }

                s++;
            } while (*(s+1)!='\0');
        }
        curr++;
    }

    return sum;
}

int main()
{
    printf("%d",calc_slope(1,150));
    return 0;
}
Prahlad Yeri
sumber
3
Saya pikir ini bukan masalah kompilasi, tetapi lebih merupakan masalah runtime. Anda akan mendapatkan bantuan lebih banyak dari StackOverflow .
oaskamay
Apakah Anda yakin ini benar-benar berjalan dengan baik setelah dikompilasi dengan VC9?
Eliah Kagan
Ya, 100%. tapi tidak dengan gcc.
Prahlad Yeri
@PrahladYeri Keren! Saya telah menjelaskan alasan untuk ini dalam jawaban saya . (Ini juga berarti kita mungkin harus mempertimbangkan pertanyaan ini pada topik, karena ini tentang perilaku Ubuntu *-spesifik *. GCC di Windows menunjukkan perilaku yang sebanding tetapi tidak ada pesan kesalahan dan sulit untuk mengetahui persis apa yang terjadi di sana - terlebih lagi, ketika GCC pada Ubuntu dan Microsoft Visual C ++ kerja yang berbeda, saya pikir Ubuntu adalah tempat yang wajar untuk bertanya tentang mengapa GCC pada Ubuntu bekerja seperti halnya Tanyakan dengan mengatakan bahwa, pertanyaan lebih lanjut tentang bagaimana. melakukannya dengan benar milik pada Stack Overflow).
Elia Kagan
Mengubah string literal dalam C adalah perilaku yang tidak terdefinisi. Tolong ingat itu.
jn1kk

Jawaban:

15

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 sprintfdisebut. 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, -gflag 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 -Wallopsional. 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 gdbdengan menjalankan terlebih dahulu gdb ./slopeuntuk memulai gdbdengan program, dan kemudian, sekali di debugger, memberikan runperintah 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... supportpesan saya ; itu tidak mencegah gdbdari 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 SIGSEGVsinyal akhirnya dipicu.

gdb(dan debugger apa pun) memiliki fitur ini bawaan: ini disebut jejak stack atau backtrace . Saya menggunakan btperintah 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 mainfungsi Anda memanggil calc_slopefungsi (yang Anda maksudkan), dan kemudian calc_slopepanggilan 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 libcdisediakan 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 sprintfmerupakan 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 , sadalah 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 ssebagai 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.

Eliah Kagan
sumber
1
Terima kasih atas penjelasan yang jelas. Inilah tepatnya yang saya butuhkan .. !!
Prahlad Yeri
9

Halo buffer overflow!

char *s="";
sprintf(s,"%d",curr);
length=strlen(s);

Anda mengalokasikan satu byte untuk sebuah string pada stack dan kemudian melanjutkan untuk menulis lebih dari satu byte untuknya. Dan to top it off, Anda membaca di luar akhir array itu. Silakan baca manual C dan terutama bagian tentang string dan mengalokasikan memori untuknya.

Dennis Kaarsemaker
sumber
Ya saya jadi tahu tentang itu nanti. Tetapi ketika saya menulis ini, kompiler VC9 tidak hanya diizinkan, tetapi juga menunjukkan kepada saya hasilnya dengan benar. Saya printf-ed strlen (s) dan itu menunjukkan saya 4, bukan 1 !!
Prahlad Yeri
Bisakah Anda juga memberi tahu saya bagaimana saya harus memperbaiki ini? Seperti yang Anda duga dari kode, saya tidak punya cara mengalokasikan ukuran tetap ke * s sebelumnya. Panjangnya adalah jumlah digit dalam variabel arus yang tidak dapat diketahui sampai saya mengonversinya menjadi string !! ?
Prahlad Yeri
Saya bisa, tetapi Anda benar-benar harus pergi ke Stack Overflow untuk saran pemrograman, karena cukup offtopic di sini.
Dennis Kaarsemaker
1
@DennisKaarsemaker Pertanyaan asli di sini mungkin bukan di luar topik karena tampaknya melibatkan perilaku yang berbeda antara Ubuntu dan platform lain (dan saya sudah menjelaskan alasan yang paling mungkin untuk ini dalam jawaban saya ). Saya setuju bahwa pertanyaan tentang cara mengalokasikan string dalam C dengan benar pada Stack Overflow dan tidak di sini.
Eliah Kagan