ไม่สามารถรวบรวมกับ GCC บน Ubuntu 12.04


9

ฉันพยายามรวบรวมและเรียกใช้โปรแกรม C ด้านล่างในเครื่อง Ubuntu และ Windows ด้วย GCC และ VC9 อย่างไรก็ตามฉันกำลังเผชิญปัญหาด้านล่าง:

บนเครื่อง Ubuntu:

GCC คอมไพล์ได้ดี แต่เมื่อทำงานฉันจะแสดงข้อความนี้:

Segmentation Fault (Core Dump).

บนเครื่อง Windows:

รวบรวม VC9 และทำงานได้ดี GCC คอมไพล์ได้ดี แต่กระบวนการยุติลงเมื่อโปรแกรมทำงาน

ต้องการความช่วยเหลือจากผู้เชี่ยวชาญของคุณที่นี่ นี่คือรหัสของฉัน:

#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;
}

ปรับปรุง:

เครดิตไปที่Eliahไม่เพียง แต่ช่วยฉันติดตามข้อผิดพลาด แต่ยังแนะนำให้ฉันรู้จักgdbและเครื่องมือการติดตามย้อนกลับ ( bt) ซึ่งเป็นประโยชน์อย่างมากในการดีบักโปรแกรมที่คอมไพล์ด้วย gcc นี่คือเวอร์ชันที่แก้ไขฉันทำงานหลังจากทดลองใช้และข้อผิดพลาด:

#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;
}

3
ฉันคิดว่ามันไม่ใช่ปัญหาในการรวบรวม แต่เป็นปัญหารันไทม์มากกว่า คุณจะได้รับความช่วยเหลือเพิ่มเติมจากStackOverflow
oaskamay

คุณแน่ใจหรือว่ามันทำงานได้ดีหลังจากคอมไพล์ด้วย VC9 หรือไม่?
Eliah Kagan

ใช่ 100% แต่ไม่ใช่กับ gcc
Prahlad Yeri

@PraladYeri สุดยอด! ผมได้อธิบายเหตุผลนี้ในคำตอบของฉัน (นี่หมายความว่าเราควรพิจารณาคำถามนี้ในหัวข้อเนื่องจากเป็นเรื่องเกี่ยวกับ Ubuntu * -specific * พฤติกรรม GCC ใน Windows แสดงพฤติกรรมที่คล้ายคลึงกัน แต่ไม่มีข้อความแสดงข้อผิดพลาดและเป็นการยากที่จะรู้ว่าเกิดอะไรขึ้นที่นั่น - นอกจากนี้เมื่อ GCC บน Ubuntu และ Microsoft Visual C ++ ทำงานแตกต่างกันฉันคิดว่า Ask Ubuntu เป็นสถานที่ที่เหมาะสมที่จะถามว่าเหตุใด GCC บน Ubuntu จึงเป็นเช่นนั้นด้วยเหตุนี้คำถามเพิ่มเติมเกี่ยวกับวิธีการทำให้ถูกต้องอยู่ใน Stack Overflow)
Eliah Kagan

การปรับเปลี่ยนสตริงตัวอักษรใน C คือพฤติกรรมที่ไม่ได้กำหนด โปรดจำไว้ว่า
jn1kk

คำตอบ:


15

ผิดส่วนเกิดขึ้นเมื่อโปรแกรมพยายามที่จะเข้าถึงหน่วยความจำภายนอกของพื้นที่ที่ได้รับการจัดสรรสำหรับมัน

ในกรณีนี้โปรแกรมเมอร์ C ที่มีประสบการณ์จะเห็นว่าปัญหาเกิดขึ้นในบรรทัดที่sprintfเรียกว่า แต่ถ้าคุณไม่สามารถบอกได้ว่าผิดส่วนของคุณจะเกิดขึ้นหรือถ้าคุณไม่ต้องการที่จะรำคาญการอ่านผ่านรหัสเพื่อพยายามที่จะคิดออกแล้วคุณสามารถสร้างโปรแกรมของคุณที่มีสัญลักษณ์การแก้ปัญหา (มีgccที่-gธงไม่นี้ ) จากนั้นเรียกใช้ผ่านตัวดีบัก

slope.cผมคัดลอกรหัสต้นฉบับของคุณและวางมันลงไปฉันไฟล์ชื่อ จากนั้นฉันก็สร้างมันขึ้นมาเช่นนี้

gcc -Wall -g -o slope slope.c

( -Wallตัวเลือกนี้เป็นเพียงการทำให้คำเตือนสำหรับสถานการณ์เพิ่มเติมเท่านั้นซึ่งสามารถช่วยในการหาสิ่งที่อาจผิดเช่นกัน)

จากนั้นฉันก็รันโปรแกรมในตัวดีบั๊กเกอร์gdbโดยการรันครั้งแรกgdb ./slopeเพื่อเริ่มต้นgdbด้วยโปรแกรมจากนั้นหนึ่งครั้งในตัวดีบักให้runคำสั่งกับตัวดีบัก:

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

(ไม่ต้องกังวลกับข้อความyou have broken Linux kernel i386 NX... ของฉันsupportมันไม่ได้ป้องกันgdbการใช้งานอย่างมีประสิทธิภาพในการดีบักโปรแกรมนี้)

ข้อมูลที่เป็นความลับอย่างมาก ... และถ้าคุณไม่ได้มีสัญลักษณ์การแก้ปัญหาการติดตั้งสำหรับ libc _IO_default_xsputnแล้วคุณจะได้รับข้อความแม้คลุมเครือมากขึ้นว่ามีที่อยู่เลขฐานสิบหกแทนชื่อฟังก์ชันสัญลักษณ์ โชคดีที่มันไม่สำคัญเพราะสิ่งที่เราอยากรู้คือปัญหาที่เกิดขึ้นในโปรแกรมของคุณ

ดังนั้นวิธีแก้ไขคือดูย้อนหลังเพื่อดูว่ามีการเรียกใช้ฟังก์ชันใดที่นำไปสู่การเรียกใช้ฟังก์ชั่นนั้นในไลบรารีระบบที่SIGSEGVสัญญาณถูกทริกเกอร์ในที่สุด

gdb(และดีบักใด ๆ ) มีคุณลักษณะนี้สร้างขึ้นใน: ก็เรียกว่ากองติดตามหรือการติดตามย้อนหลัง ฉันใช้btคำสั่ง debugger เพื่อสร้าง backtrace ใน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)

คุณสามารถเห็นได้ว่าmainฟังก์ชั่นของคุณเรียกใช้calc_slopeฟังก์ชั่น (ซึ่งคุณตั้งใจ) แล้วcalc_slopeเรียกใช้sprintfซึ่ง (ในระบบนี้) นำไปใช้กับการเรียกไปยังฟังก์ชั่นห้องสมุดอื่น ๆ ที่เกี่ยวข้อง

สิ่งที่คุณกำลังสนใจทั่วไปในคือการเรียกใช้ฟังก์ชันในโปรแกรมของคุณที่เรียกฟังก์ชั่นด้านนอกของโปรแกรมของคุณ หากไม่มีข้อผิดพลาดในไลบรารี / ไลบรารีเองที่คุณใช้ (ในกรณีนี้ไลบรารี C มาตรฐานที่libcจัดเตรียมโดยไฟล์ไลบรารีlibc.so.6) ข้อผิดพลาดที่ทำให้เกิดความผิดพลาดนั้นอยู่ในโปรแกรมของคุณและมักจะอยู่ที่หรือใกล้กับ การโทรครั้งสุดท้ายในโปรแกรมของคุณ

ในกรณีนี้นั่นคือ:

#4  0x080484cc in calc_slope (input1=1, input2=150) at slope.c:26

sprintfนั่นคือสิ่งที่โทรโปรแกรมของคุณ เรารู้สิ่งนี้เพราะsprintfเป็นขั้นตอนต่อไป แต่ถึงกระนั้นก็ไม่ได้ระบุว่าคุณรู้เรื่องนี้เพราะนั่นคือสิ่งที่เกิดขึ้นในบรรทัดที่ 26และมันบอกว่า:

... at slope.c:26

ในโปรแกรมของคุณบรรทัด 26 ประกอบด้วย:

            sprintf(s,"%d",curr);

(คุณควรใช้โปรแกรมแก้ไขข้อความที่แสดงหมายเลขบรรทัดโดยอัตโนมัติอย่างน้อยที่สุดสำหรับบรรทัดที่คุณกำลังใช้อยู่ซึ่งเป็นประโยชน์อย่างมากในการตีความข้อผิดพลาดทั้งในเวลาคอมไพล์

ตามที่กล่าวไว้ในคำตอบที่เดนนิส Kaarsemaker ของ , sเป็นอาร์เรย์หนึ่งไบต์ (ไม่เป็นศูนย์เนื่องจากค่าที่คุณกำหนดให้""มีความยาวหนึ่งไบต์นั่นคือมันจะเท่ากับ{ '\0' }ในวิธีเดียวกันกับที่"Hello, world!\n"เท่ากับ{ 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\0' })

ดังนั้นจึงอาจจะยังคงทำงานนี้บนแพลตฟอร์มบาง (และเห็นได้ชัดว่าเมื่อรวบรวมกับ vc9 สำหรับ Windows)?

คนมักจะพูดว่าเมื่อคุณจัดสรรหน่วยความจำแล้วลองเข้าถึงหน่วยความจำภายนอกมันจะสร้างข้อผิดพลาด แต่นั่นไม่จริงเลย ตามมาตรฐานทางเทคนิค C และ C ++ สิ่งที่สิ่งนี้สร้างขึ้นจริงๆคือพฤติกรรมที่ไม่ได้กำหนด

ในคำอื่น ๆ สิ่งที่สามารถเกิดขึ้นได้!

ถึงกระนั้นบางสิ่งก็มีแนวโน้มมากกว่าสิ่งอื่น เหตุใดอาร์เรย์ขนาดเล็กบนสแต็กจะปรากฏขึ้นในการใช้งานบางอย่างเพื่อให้ทำงานเหมือนอาร์เรย์ขนาดใหญ่บนสแต็ก

สิ่งนี้เกิดขึ้นกับวิธีการนำการจัดสรรสแต็กมาใช้ซึ่งได้รับอนุญาตให้เปลี่ยนจากแพลตฟอร์มหนึ่งไปอีกแพลตฟอร์มหนึ่ง ความสามารถในการปฏิบัติการของคุณอาจจัดสรรหน่วยความจำให้กับสแต็กมากกว่าที่ตั้งใจจะใช้ในแต่ละครั้ง บางครั้งสิ่งนี้อาจทำให้คุณสามารถเขียนไปยังตำแหน่งหน่วยความจำที่คุณไม่ได้อ้างสิทธิ์ไว้อย่างชัดเจนในรหัสของคุณ เป็นไปได้มากว่านี่คือสิ่งที่เกิดขึ้นเมื่อคุณสร้างโปรแกรมใน VC9

อย่างไรก็ตามคุณไม่ควรพึ่งพาพฤติกรรมนี้แม้ใน VC9 มันอาจขึ้นอยู่กับไลบรารีเวอร์ชันต่าง ๆ ที่อาจมีอยู่ในระบบ Windows ที่แตกต่างกัน แต่มีโอกาสมากขึ้นที่จะเกิดปัญหาว่าการจัดสรรพื้นที่สแต็กเพิ่มเติมด้วยความตั้งใจที่จะใช้จริงและอาจใช้งานได้จริงจากนั้นคุณจะพบกับฝันร้ายเต็มรูปแบบของ "พฤติกรรมที่ไม่ได้กำหนด" ซึ่งในกรณีนี้มากกว่าหนึ่งตัวแปรสามารถจบลงด้วยการเก็บไว้ในที่เดียวกันโดยที่การเขียนไปที่หนึ่งเขียนทับอีก ... แต่ไม่เสมอไปเพราะบางครั้งเขียนถึงตัวแปร ถูกแคชในรีจิสเตอร์และไม่ได้ดำเนินการจริงทันที (หรืออ่านตัวแปรอาจถูกแคชหรือตัวแปรอาจสันนิษฐานว่าเป็นเหมือนเดิมมาก่อนเพราะหน่วยความจำที่จัดสรรให้มันเป็นที่รู้จักกันโดยคอมไพเลอร์ที่ไม่ได้เขียนถึง ตัวแปรเอง)

และนั่นทำให้ฉันมีความเป็นไปได้อื่น ๆ ว่าทำไมโปรแกรมทำงานเมื่อสร้างด้วย VC9 มันเป็นไปได้และมีแนวโน้มที่ค่อนข้างที่บางอาร์เรย์หรืออื่น ๆ ตัวแปรถูกจัดสรรจริงโดยโปรแกรมของคุณ (ซึ่งอาจรวมถึงการได้รับการจัดสรรโดยห้องสมุดโปรแกรมของคุณใช้) sเพื่อใช้ช่องว่างหลังจากอาร์เรย์หนึ่งไบต์ ดังนั้นการsถือว่าเป็นอาร์เรย์ที่มีความยาวมากกว่าหนึ่งไบต์จะมีผลในการเข้าถึงเนื้อหาของตัวแปรนั้น / อาเรย์เหล่านั้นซึ่งอาจไม่ดีเช่นกัน

สรุปได้ว่าเมื่อคุณมีความผิดพลาดเช่นนี้ก็โชคดีที่จะได้รับข้อผิดพลาดเช่น "ความผิดส่วน" หรือ "การป้องกันความผิดทั่วไป." เมื่อคุณไม่มีสิ่งนั้นคุณอาจไม่ทราบจนกว่าจะสายเกินไปที่โปรแกรมของคุณจะมีพฤติกรรมที่ไม่ได้กำหนด


1
ขอบคุณสำหรับคำอธิบายที่ชัดเจนเช่นนี้ นี่คือสิ่งที่ฉันต้องการอย่างแน่นอน .. !!
Prahlad Yeri

9

สวัสดีบัฟเฟอร์ล้น!

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

คุณจัดสรรหนึ่งไบต์สำหรับสตริงบนสแต็กจากนั้นดำเนินการเขียนมากกว่าหนึ่งไบต์ และยิ่งไปกว่านั้นคุณอ่านเกินจุดสิ้นสุดของอาเรย์นั้น โปรดอ่านคู่มือ C โดยเฉพาะอย่างยิ่งส่วนที่เกี่ยวกับสตริงและการจัดสรรหน่วยความจำสำหรับพวกเขา


ใช่ฉันรู้เรื่องนี้ในภายหลัง แต่เมื่อฉันเขียนสิ่งนี้คอมไพเลอร์ VC9 ไม่เพียง แต่ได้รับอนุญาต แต่ยังแสดงผลลัพธ์ให้ฉันอย่างถูกต้อง ฉันพิมพ์ strlen (s) และมันแสดงให้ฉัน 4 ไม่ใช่ 1 !!
Prahlad Yeri

คุณสามารถให้คำแนะนำฉันได้อย่างไรว่าฉันจะแก้ไขข้อผิดพลาดนี้ได้อย่างไร เนื่องจากคุณต้องเดาจากรหัสฉันจึงไม่สามารถจัดสรรขนาดคงที่ให้กับ * s ล่วงหน้าได้ ความยาวของมันคือจำนวนหลักในตัวแปร curr ที่ไม่สามารถรู้ได้จนกว่าฉันจะแปลงเป็นสตริง !! ?
Prahlad Yeri

ฉันทำได้ แต่คุณควรตรงไปที่ Stack Overflow เพื่อรับคำแนะนำในการเขียนโปรแกรมเนื่องจากมันค่อนข้างตรงข้ามกับที่นี่
Dennis Kaarsemaker

1
@DennisKaarsemaker คำถามดั้งเดิมที่นี่อาจไม่ได้อยู่นอกหัวข้อเนื่องจากเห็นได้ชัดว่ามันเกี่ยวข้องกับพฤติกรรมที่แตกต่างระหว่าง Ubuntu และแพลตฟอร์มอื่น (และฉันอธิบายเหตุผลที่เป็นไปได้มากที่สุดสำหรับคำตอบของฉัน ) ฉันยอมรับว่าคำถามเกี่ยวกับวิธีการจัดสรรสตริงใน Cอยู่ใน Stack Overflow อย่างเหมาะสมไม่ใช่ที่นี่
Eliah Kagan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.