Ubuntu 12.04のGCCでコンパイルできない


9

以下のCプログラムを、GCCとVC9の両方を搭載したUbuntuとWindowsマシンでコンパイルして実行しようとしています。しかし、私は以下の問題に直面しています:

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

更新:

エラーの追跡を助けてくれただけでなく、gccコンパイル済みプログラムのデバッグに非常に役立つバックトレースツール()を紹介してくれたEliahに感謝します。これが変更されたバージョンです、私はいくつかの試行錯誤の後に仕上げました:gdbbt

#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 2013年

あなたは確かにこれは本当にされて実行されます VC9でコンパイルされた後、罰金を?
Eliah Kagan 2013

はい、100%です。gccではできません。
Prahlad Yeri 2013年

@PrahladYeriかっこいい!私はその理由を説明してきた私の答えに。(これは、Ubuntu *固有の動作に関するものであるため、おそらくこの質問をトピックで検討する必要があることも意味します。WindowsのGCCは同等の動作を示しますが、エラーメッセージはなく、そこで何が起こっているのか正確に知ることは困難です。 UbuntuのGCCとMicrosoft Visual C ++の動作は異なります。AskUbuntuは、UbuntuのGCCが機能する理由を尋ねるのに適した場所だと思います。それで、それを正しく行う方法についてのさらなる質問は、Stack Overflowに属しています。)
エリアカーガン

Cで文字列リテラルを変更することは、未定義の動作です。覚えておいてください。
jn1kk 2013年

回答:


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にデバッグシンボルがインストールされていない場合、シンボリック関数名の代わりに16進数のアドレスを持つさらに暗号化されたメッセージが表示されます_IO_default_xsputn。幸い、私たちが本当に知りたいのはプログラムどこで問題が発生しているのかということなので、それは問題ではありません。

したがって、解決策は、SIGSEGV信号を最後にトリガーしたシステムライブラリ内のその特定の関数呼び出しに至るまでに、どの関数呼び出しが行われたかを逆に調べることです。

gdb(および任意のデバッガー)にはこの機能が組み込まれています。これはスタックトレースまたはバックトレースと呼ばれますbtデバッガーコマンドを使用して、バックトレースを生成します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その他の関連するライブラリ関数のカップルへの呼び出しで実装(このシステム上で)です。

一般的に興味があるのは、プログラムの外部の関数を呼び出すプログラムの関数呼び出しです。使用しているライブラリ自体にバグがない限り(この場合、libcライブラリファイルによって提供される標準Cライブラリ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);

(少なくとも現在の行については、常に行番号を自動的に表示するテキストエディターを使用する必要があります。これは、デバッガーの使用中に判明したコンパイル時のエラーと実行時の問題の両方を解釈するのに非常に役立ちます。)

Dennis Kaarsemakerの回答で説明されているようにs、1バイトの配列です。(ゼロではありません。割り当てた値""は1バイトの長さです。{ '\0' }つまり、と同じです"Hello, world!\n"が、と同じ{ 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\0' }です。)

それでは、なぜできたいくつかのプラットフォーム上で、このまだ作業は(どうやらWindows用VC9でコンパイルさない場合)?

多くの場合、メモリを割り当ててから、その外部のメモリにアクセスしようとすると、エラーが発生すると言われています。しかし、それは本当ではありません。CおよびC ++の技術標準によると、これが実際に生成するのは未定義の動作です。

言い換えれば、何でも起こり得るのです!

それでも、いくつかのものは他のものよりも可能性が高いです。一部の実装では、スタック上の小さな配列がスタック上の大きな配列のように機能するように見えるのはなぜですか?

これは、プラットフォームごとに異なるスタック割り当ての実装方法に起因します。実行可能ファイルは、一度に実際に使用することを目的としたものよりも多くのメモリをスタックに割り当てることがあります。これにより、コードで明示的に要求していないメモリ位置に書き込むことができる場合があります。これは、VC9でプログラムをビルドするときに起こっていることです。

ただし、VC9でもこの動作に依存しないでください。異なるWindowsシステムに存在する可能性のあるライブラリの異なるバージョンに依存する可能性があります。ただし、実際に使用されることを意図して追加のスタックスペースが割り当てられ、実際に使用される可能性があるという問題が発生する可能性さらに高くなります次に、「未定義の動作」の悪夢を体験します。この場合、複数の変数が同じ場所に格納され、一方が他方に上書きされますが、常に変数に書き込まれるとは限らないためです。レジスタにキャッシュされ、実際にはすぐには実行されません(または、変数への読み取りがキャッシュされる場合があります。または、変数に割り当てられたメモリがコンパイラによって書き込まれていないことがわかっているため、変数は以前と同じであると見なされる場合があります変数自体)。

そして、それは私がVC9でビルドされたときにプログラムが機能した理由について他の可能性のある可能性をもたらします。その、多少可能性が可能だし、いくつかの配列または他の変数は実際には1バイトの配列の後にスペースを使用するように(あなたのプログラムが使用しているライブラリによって割り当てられ含めることができます)プログラムによって割り当てられましたs。そのsため、1バイトより長い配列として扱うと、その内容/これらの変数/配列の内容にアクセスすることになり、これも悪い結果になる可能性があります。

結論として、このような間違いがあった場合、「セグメンテーション違反」または「一般保護違反」のようなエラーが発生するのは幸運です。あなたがするとしていないことがあり、それは遅すぎるあなたのプログラムが持っていることになるまで、あなたは見つけることがありません未定義の動作を。


1
そのような明快な説明をありがとう。これがまさに私が必要としたものです。
Prahlad Yeri 2013年

9

こんにちはバッファオーバーフロー!

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

スタック上の文字列に1バイトを割り当ててから、そのバイトに複数のバイトを書き込みます。さらに、その配列の最後を超えて読みます。Cのマニュアル、特に文字列とメモリの割り当てに関するセクションをお読みください。


はい、後で知りました。しかし、私がこれを書いたとき、VC9コンパイラーは許可するだけでなく、結果を適切に表示しました。strlen(s)を印刷すると、1ではなく4が表示されました。
Prahlad Yeri 2013年

これを修正するにはどうすればよいですか?コードから推測したように、固定サイズを* sに事前に割り当てる方法はありません。その長さは、curr変数の桁数であり、文字列に変換するまでわかりません!! ?
Prahlad Yeri 2013年

できますが、プログラミングアドバイスについてはスタックオーバーフローに行くべきです。
Dennis Kaarsemaker 2013年

1
@DennisKaarsemaker Ubuntuと別のプラットフォームで異なる動作が含まれているため、ここでの元の質問はトピックから外れていない可能性があります(そして、この最も可能性の高い理由を私の回答で説明しました)。Cで文字列を適切に割り当てる方法に関する質問は、ここではなくスタックオーバーフローに属することに同意します。
Eliah Kagan 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.