Không thể biên dịch với GCC trên Ubuntu 12.04


9

Tôi đang cố gắng biên dịch và chạy chương trình C dưới đây trên các máy Ubuntu và Windows của mình bằng cả GCC & VC9. Tuy nhiên, tôi đang đối mặt với các vấn đề dưới đây:

Trên máy Ubuntu:

GCC biên dịch tốt, nhưng khi chạy, tôi đã hiển thị lời nhắc này:

Segmentation Fault (Core Dump).

Trên máy Windows:

VC9 Biên dịch & chạy tốt. GCC biên dịch tốt, nhưng quá trình chấm dứt khi chương trình được chạy.

Cần sự giúp đỡ chuyên gia của bạn ở đây. Đây là mã của tôi:

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

Cập nhật:

Tín dụng đến Eliah không chỉ giúp tôi theo dõi lỗi mà còn giới thiệu cho tôi gdbvà công cụ truy tìm ngược ( bt) rất hữu ích trong việc gỡ lỗi chương trình biên dịch gcc. Đây là phiên bản sửa đổi, tôi đã làm việc sau một số thử nghiệm và lỗi:

#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
Tôi nghĩ đó không phải là vấn đề biên dịch, mà là vấn đề thời gian chạy. Bạn sẽ nhận được nhiều sự giúp đỡ hơn từ StackOverflow .
oaskamay

Bạn có chắc chắn điều này thực sự chạy tốt sau khi được biên dịch với VC9?
Eliah Kagan

Có, 100%. nhưng không phải với gcc.
Prahlad Yeri

@PrahladYeri Tuyệt! Tôi đã giải thích lý do cho điều này trong câu trả lời của tôi . (Điều này cũng có nghĩa là chúng ta có lẽ nên xem xét câu hỏi này theo chủ đề, vì đó là về hành vi của Ubuntu * -specific *. GCC trong Windows thể hiện hành vi tương đương nhưng không có thông báo lỗi và thật khó để biết chính xác những gì đang xảy ra ở đó - hơn nữa, khi nào GCC trên Ubuntu và Microsoft Visual C ++ hoạt động khác nhau, tôi nghĩ Hỏi Ubuntu là nơi hợp lý để hỏi về lý do GCC trên Ubuntu hoạt động như vậy. Với những câu hỏi tiếp theo về cách thực hiện đúng thuộc về Stack Overflow.)
Eliah Kagan

Sửa đổi một chuỗi ký tự trong C là hành vi không xác định. Hãy nhớ điều đó.
jn1kk

Câu trả lời:


15

Một lỗi phân đoạn xảy ra khi một chương trình cố gắng truy cập bộ nhớ bên ngoài khu vực đã được phân bổ cho nó.

Trong trường hợp này, một lập trình viên C có kinh nghiệm có thể thấy rằng vấn đề đang xảy ra trong dòng sprintfđược gọi. Nhưng nếu bạn không thể biết lỗi xảy ra ở đâu hoặc nếu bạn không muốn đọc qua mã để cố gắng tìm ra nó, thì bạn có thể xây dựng chương trình của mình bằng các ký hiệu gỡ lỗi (với gcc, -gcờ thực hiện điều này ) và sau đó chạy nó thông qua một trình sửa lỗi.

Tôi đã sao chép mã nguồn của bạn và dán nó vào một tập tin mà tôi đặt tên slope.c. Sau đó, tôi đã xây dựng nó như thế này:

gcc -Wall -g -o slope slope.c

(Đây -Walllà tùy chọn. Nó chỉ để làm cho nó đưa ra cảnh báo cho nhiều tình huống hơn. Điều này có thể giúp tìm ra những gì có thể sai, quá.)

Sau đó, tôi đã chạy chương trình trong trình gỡ lỗi gdbbằng cách chạy đầu tiên gdb ./slopeđể bắt đầu gdbvới chương trình, và sau đó, một lần trong trình gỡ lỗi, đưa ra runlệnh cho trình gỡ lỗi:

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

(Đừng lo lắng về you have broken Linux kernel i386 NX... supporttin nhắn của tôi ; nó không ngăn chặn gdbviệc sử dụng hiệu quả để gỡ lỗi chương trình này.)

Thông tin đó rất khó hiểu ... và nếu bạn không cài đặt các biểu tượng gỡ lỗi cho libc, thì bạn sẽ nhận được một tin nhắn thậm chí còn khó hiểu hơn có địa chỉ thập lục phân thay vì tên hàm biểu tượng _IO_default_xsputn. May mắn thay, điều đó không thành vấn đề, bởi vì điều chúng tôi thực sự muốn biết là vấn đề đang xảy ra ở đâu trong chương trình của bạn .

Vì vậy, giải pháp là nhìn về phía sau, để xem những cuộc gọi chức năng nào đã diễn ra dẫn đến cuộc gọi chức năng cụ thể đó trong thư viện hệ thống nơi SIGSEGVtín hiệu cuối cùng được kích hoạt.

gdb(và bất kỳ trình gỡ lỗi nào) đều có tính năng này được tích hợp: nó được gọi là dấu vết ngăn xếp hoặc backtrace . Tôi sử dụng btlệnh gỡ lỗi để tạo backtrace trong 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)

Bạn có thể thấy rằng mainhàm của bạn gọi calc_slopehàm (mà bạn dự định), và sau đó calc_slopegọi sprintf, (trên hệ thống này) được triển khai với các cuộc gọi đến một vài chức năng thư viện liên quan khác.

Điều bạn thường quan tâm là chức năng gọi trong chương trình của bạn gọi một chức năng bên ngoài chương trình của bạn . Trừ khi có lỗi trong chính thư viện / thư viện mà bạn đang sử dụng (trong trường hợp này là thư viện C tiêu chuẩn libcđược cung cấp bởi tệp thư viện libc.so.6), lỗi gây ra sự cố nằm trong chương trình của bạn và thường sẽ ở hoặc gần cuộc gọi cuối cùng trong chương trình của bạn.

Trong trường hợp này, đó là:

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

Đó là nơi chương trình của bạn gọi sprintf. Chúng tôi biết điều này bởi vì sprintflà bước tiếp theo lên. Nhưng ngay cả khi không nói rõ điều đó, bạn vẫn biết điều này bởi vì đó là những gì xảy ra trên dòng 26 và nó nói:

... at slope.c:26

Trong chương trình của bạn, dòng 26 chứa:

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

.

Như đã thảo luận trong câu trả lời của Dennis Kaarsemaker , slà một mảng một byte. (Không phải bằng 0, bởi vì giá trị bạn đã gán cho nó, ""dài một byte, nghĩa là, nó bằng { '\0' }, theo cùng một cách "Hello, world!\n"tương đương với { 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\0' }.)

Vậy, tại sao điều này vẫn có thể hoạt động trên một số nền tảng (và rõ ràng là khi được biên dịch với VC9 cho Windows)?

Mọi người thường nói rằng khi bạn phân bổ bộ nhớ và sau đó cố gắng truy cập bộ nhớ bên ngoài nó, điều đó sẽ gây ra lỗi. Nhưng điều đó không thực sự đúng. Theo tiêu chuẩn kỹ thuật của C và C ++, những gì nó thực sự tạo ra là hành vi không xác định.

Nói cách khác, bất cứ điều gì cũng có thể xảy ra!

Tuy nhiên, một số thứ có nhiều khả năng hơn những thứ khác. Tại sao một mảng nhỏ trên ngăn xếp sẽ, trên một số triển khai, dường như hoạt động như một mảng lớn hơn trên ngăn xếp?

Điều này dẫn đến cách phân bổ ngăn xếp được thực hiện, được phép thay đổi từ nền tảng này sang nền tảng khác. Tập tin thực thi của bạn có thể phân bổ nhiều bộ nhớ hơn cho ngăn xếp của nó so với thực tế dự định sẽ được sử dụng bất cứ lúc nào. Đôi khi, điều này có thể cho phép bạn ghi vào các vị trí bộ nhớ mà bạn chưa đặt yêu cầu rõ ràng trong mã của mình. Rất có khả năng đây là những gì đang xảy ra khi bạn xây dựng chương trình của mình trong VC9.

Tuy nhiên, bạn không nên dựa vào hành vi này ngay cả trong VC9. Nó có khả năng phụ thuộc vào các phiên bản thư viện khác nhau có thể tồn tại trên các hệ thống Windows khác nhau. Nhưng thậm chí nhiều khả năng là vấn đề không gian ngăn xếp thêm được phân bổ với ý định rằng nó sẽ thực sự được sử dụng, và vì vậy nó thực sự có thể được sử dụng.Sau đó, bạn trải qua cơn ác mộng đầy đủ của "hành vi không xác định", trong trường hợp này, nhiều hơn một biến có thể được lưu trữ ở cùng một nơi, nơi viết cho một ghi đè lên ... nhưng không phải lúc nào cũng vậy, bởi vì đôi khi ghi vào các biến được lưu trữ trong các thanh ghi và không thực sự được thực hiện ngay lập tức (hoặc đọc các biến có thể được lưu trong bộ nhớ cache hoặc một biến có thể được coi là giống như trước đây vì bộ nhớ được cấp cho nó bởi trình biên dịch không được ghi vào thông qua chính biến).

Và điều đó đưa tôi đến khả năng khác về lý do tại sao chương trình hoạt động khi được xây dựng với VC9. Có thể, và có khả năng, một số mảng hoặc biến khác thực sự được phân bổ bởi chương trình của bạn (có thể bao gồm việc được phân bổ bởi thư viện mà chương trình của bạn đang sử dụng) để sử dụng khoảng trắng sau mảng một byte s. Vì vậy, việc xử lý snhư một mảng dài hơn một byte sẽ có tác dụng truy cập nội dung của các biến / mảng đó, cũng có thể là xấu.

Tóm lại, khi bạn gặp một lỗi như thế này, thật may mắn khi gặp một lỗi như "Lỗi phân đoạn" hoặc "Lỗi bảo vệ chung". Khi bạn không có điều đó, bạn có thể không phát hiện ra cho đến khi quá muộn rằng chương trình của bạn có hành vi không xác định.


1
Cảm ơn lời giải thích sáng suốt như vậy. Đây chính xác là những gì tôi cần .. !!
Prahlad Yeri

9

Xin chào bộ đệm tràn!

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

Bạn phân bổ một byte cho một chuỗi trên ngăn xếp và sau đó tiến hành viết nhiều hơn một byte cho chuỗi đó. Và trên hết, bạn đọc vượt ra ngoài phần cuối của mảng đó. Vui lòng đọc hướng dẫn sử dụng C và đặc biệt là phần trên chuỗi và phân bổ bộ nhớ cho chúng.


Vâng, tôi đã biết về điều đó sau. Nhưng khi tôi viết bài này, trình biên dịch VC9 không chỉ cho phép mà còn cho tôi thấy kết quả chính xác. Tôi printf-ed các strlen (s) và nó cho tôi thấy 4, không phải 1 !!
Prahlad Yeri

Bạn cũng có thể tư vấn cho tôi làm thế nào tôi nên sửa chữa điều này? Vì bạn phải phỏng đoán từ mã, tôi không có cách nào phân bổ kích thước cố định cho * s trước. Độ dài của nó là số chữ số trong biến dòng không thể biết được cho đến khi tôi chuyển đổi nó thành chuỗi !! ?
Prahlad Yeri

Tôi có thể, nhưng bạn thực sự nên tìm đến Stack Overflow để tư vấn lập trình, vì nó khá bất thường ở đây.
Dennis Kaarsemaker

1
@DennisKaarsemaker Câu hỏi ban đầu ở đây có thể không có chủ đề vì nó dường như liên quan đến hành vi khác nhau giữa Ubuntu và nền tảng khác (và tôi đã giải thích lý do rất có thể cho câu trả lời này ). Tôi đồng ý rằng các câu hỏi về cách phân bổ đúng chuỗi trong C thuộc về Stack Overflow chứ không phải ở đây.
Eliah Kagan
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.