アプリ版:「スタンプのみでお礼する」機能のリリースについて

プログラミング初心者です
自然数の四則演算のプログラムを作ったところ
コンパイル時にはエラーは起こりませんでしたが実行時にエラーが起こり、動作が停止してしまいました。
自分で原因が特定できなかったのでこちらを頼らせてもらうことにしました。
ご教授お願いします。
<環境>
Windows 7 Home Premium 64-bit
Visual Studio 2013


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

int main( void )
{

char str[2][128]={{0}};
int i,j,err=0,cont=0,wari=0;
double c,d,dans;
long a,b,lans;


puts("自然数の四則演算('+''-''*''/')を計算");

//被加数,被減数,被乗数,被除数と演算の判定
i=0;
while((str[0][i]=getchar())!='\n'){
if(str[0][0]=='+' || str[0][0]=='*' || str[0][0]=='/' || str[0][0]=='-'){
err=1;
}
if(str[0][i]=='+' || str[0][i]=='*' || str[0][i]=='/' || str[0][i]=='-'){
cont=1; //2つ目の数字を読み込むか否かの判定
break;
}else if(isdigit(str[0][i])==0){
err=1; //数字と演算方法以外の入力はエラー
}
i++;
}

//加数,減数,乗数,除数の判定
if(cont==1 && err!=1){
j=0;
while(str[1][j]!='\n'){
if(isdigit(str[0][i])==0){
err=1; //数字以外の入力があればエラー
}
j++;
}
str[1][j]='\0';

if(err==1){
printf("エラー入力");
return 0;
}

switch(str[0][i]){
case '+':
str[0][i]='\0';
a=atol(str[0]);
b=atol(str[1]);
lans=a+b;
break;
case '-':
str[0][i]='\0';
a=atol(str[0]);
b=atol(str[1]);
lans=a-b;
break;
case '*':
str[0][i]='\0';
a=atol(str[0]);
b=atol(str[1]);
lans=a*b;
break;
case '/':
str[0][i]='\0';
c=atol(str[0]);
d=atol(str[1]);
dans=(double)c/d;
wari=1; //割り算の判定
break;
default:
break;
}
}

if(err==0){
if(wari==0){
printf("計算結果:%ld\n",lans);
}else if(wari==1){
printf("計算結果:%f\n",dans);
}
}else{
printf("エラー入力です\n");
}
return 0;
}

A 回答 (5件)

>コンパイル時にはエラーは起こりませんでしたが実行時にエラーが起こり、動作が停止してしまいました。


>自分で原因が特定できなかったのでこちらを頼らせてもらうことにしました。

どういう入力を行い、どんなエラーが発生して、動作が停止したと判断した根拠はなんです?

軽くしか見ていませんが、不思議な仕様のコードですねぇ。
・入力エラーがあってもその文字は有効とする。
 ->のでatol()は0(変換不可でエラー)とする。
・2つ目の数字入力は数字かどうかは関係ない。
 ->ので、やっぱりatol()は0を返す。
・除算だったらOSが止めてくれる。

この回答への補足

解答ありがとうございます。

>どういう入力を行い、どんなエラーが発生して、動作が停止したと判断した根拠はなんです?

大変申し訳ありません。私の説明不足です。
terapadで作成した拡張子[.c]のファイルを開発者コマンドプロンプトfor VS2013 にてコンパイルしexeファイルを実行した際にキーボードから 
・123+123
・123*aiueo
のような入力をしましたところ

・(省略).exeは動作を停止しました
問題が発生したため,プログラムが正しく動作しなくなりました。プログラムは閉じられ、解決策がある場合はWindowsから通知されます。

上記のように表示されましたので 動作が停止したと判断しました。

>入力エラーあってもその文字は有効とする。
>2つ目の数字入力は数字かどうかは関係ない。

に関しましては

if(err==1){
printf("エラー入力");
return 0;
}

この記述でプログラムを終了することができると私が思っているので気にしていませんでした。

>除算だったらOSが止めてくれる。

また見返しましたところ分数において分母が0の場合を考えておりませんでした。申し訳ありません

補足日時:2014/09/15 10:57
    • good
    • 0

動作確認時のネタとしてひとつ。



・最初の文字で「+」「-」「/」「*」のいずれかを入力したときにどうしたい?
 で、現状のコードで何が起こる?
・最初の入力で「a」「Backspace」「b」「Enter」とした場合にどうなる?
 str[0][]に入っている文字列はどうなってる?
 iの値はそれぞれの入力後にどうなってる?
・最初の入力で「a」「1」「/」とした場合にどうなる?
・最初の入力で「1」「z」「/」として、次に「a」「Enter」とした場合にどうなる?
などなど。

デバッガ使ってステップ実行やらしてみることを勧めますよ。
# printf()デバッグでもいいんだけど…標準入力使っている場合ちょっと…ね。
    • good
    • 0
この回答へのお礼

ご回答ありがとうございます。

デバッカを使ったことがなかったので調べて実行してみます。
ありがとうございました

お礼日時:2014/09/15 11:30

もう少しちゃんと見てみましたが、やはり不思議な仕様のコードですね。



> //被加数,被減数,被乗数,被除数と演算の判定
> i=0;
> while((str[0][i]=getchar())!='\n'){

と書いているので'\n'で入力が終了することを期待しているのでしょうが、実際に四則演算を入力しようとすると、次のところでループから脱出します。

> if(str[0][i]=='+' || str[0][i]=='*' || str[0][i]=='/' || str[0][i]=='-'){
> cont=1; //2つ目の数字を読み込むか否かの判定
> break;

例えば、"1+2"という入力をしたら、"1+"(ただし、NULL文字では終わっているとは限らない)がstr[0]に入った状態でループを出ます。

その後、次のコードを実行します。

> //加数,減数,乗数,除数の判定
> if(cont==1 && err!=1){
> j=0;
> while(str[1][j]!='\n'){

なぜか、str[1]となっているので、中に何が入っているか不明なところを\nが出てくるまで探し続けます。

> if(isdigit(str[0][i])==0){
> err=1; //数字以外の入力があればエラー
> }
> j++;
> }

と、数値以外が来てもお構いなしにstr[1]の中身を読み続け、str[1]に運良く'\n'が入っていない限り、OSがメモリーへのアクセス違反を検出して強制終了となるまでメモリーを読み続けるでしょう。

ちなみに、whileの条件を while(str[1][j]!='\n'){としてもダメです。なぜなら、'\n'が入っていないから。

他にも色々と不思議なところはありますが、
多分、2項の自然数の四則演算をするプログラムだと思ったので、質問文にあるコードを元に、プログラムを読みにくくするところを省いて多分やりたいことだろうというコードに書き換えてみました。

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

int
main(void)
{

char str [128] = {0};
int i , j, err = 0, op_pos = 0, wari = 0;
double dans;
long a , b, lans;


puts("自然数の四則演算('+''-''*''/')を計算");

//被加数, 被減数, 被乗数, 被除数と演算の判定
i = 0;
while ((str[i] = getchar()) != '\n') {
if (str[0] == '+' || str[0] == '*' || str[0] == '/' || str[0] == '-') {
err = 1;
}
if (str[i] == '+' || str[i] == '*' || str[i] == '/' || str[i] == '-') {
//オペレーターの位置を保存
if (op_pos != 0) {
err = 1;
break;
}
op_pos = i;
} else if (!isdigit(str[i])) {
err = 1;
//数字と演算方法以外の入力はエラー
}
i++;
}

if (err == 1) {
printf("エラー入力");
return 0;
}
a = atol(&str[0]);
b = atol(&str[op_pos + 1]);
switch (str[op_pos]) {
case '+':
lans = a + b;
break;
case '-':
lans = a - b;
break;
case '*':
lans = a * b;
break;
case '/':
if (b == 0) {
// devided by 0.
err = 1;
} else {
dans = (double)(a) / (double)(b);
wari = 1;
}
break;
default:
break;
}

if (err == 0) {
if (wari == 0) {
printf("計算結果:%ld\n", lans);
} else if (wari == 1) {
printf("計算結果:%f\n", dans);
}
} else {
printf("エラー入力です\n");
}
return 0;
}

今後、1+2*3に7と答えるようにするなら、字句解析や演算子順序解析に付いて勉強したほうがよいかもしれません。
    • good
    • 0
この回答へのお礼

ご回答ありがとうございます。
多々ツッコミたいところがあると存じますが自分の力不足であります。大変申し訳ありません。

> //被加数,被減数,被乗数,被除数と演算の判定
> i=0;
> while((str[0][i]=getchar())!='\n'){

この部分に関しまして算術演算子が入力されない場合を考慮して書きました。

> //加数,減数,乗数,除数の判定
> if(cont==1 && err!=1){
> j=0;
> while(str[1][j]!='\n'){

この部分も改めて見直してみると確かにおかしく感じました。
自分の見落としです。

while((str[1][j]=getchar())!='\n')

このように書いた気になっていました。
申し訳ありません。



そうです!私がやりたかったことは書き換えていただいたコードのようなプログラムです。
私のつたない文章と見るにも耐えないコードで書き換えていただいて本当にありがとうございました。

後々やりたく思っていましたの字句解析や演算子順序解析についても勉強していきたいと思います。

お礼日時:2014/09/15 11:52

>例えば、"1+2"という入力をしたら、"1+"(ただし、NULL文字では終わっているとは限らない)がstr[0]に入った状態でループを出ます。



'\0'ターミネートは…おそらく保証されていると考えよいかと思われます。
>char str[2][128]={{0}};
で、全域0x00でクリアされていますので。

よって…
>なぜか、str[1]となっているので、中に何が入っているか不明なところを\nが出てくるまで探し続けます。
で、バッファオーバーランも保証されていますね。
str[1]には0x00しかないのですから。
その結果が、
>・123*aiueo
>のような入力をしましたところ
>・(省略).exeは動作を停止しました
>問題が発生したため,プログラムが正しく動作しなくなりました。プログラムは閉じられ、解決策がある場合はWindowsから通知されます。

なのでしょう。
# str[1]にもstr[0]の時の入力があると読み違えてました。
# まぁどちらにしろ正しく動作しないのですが。
    • good
    • 0

演算子順位構文解析ですね。

演算子の優先順位に基づいた構文解析なので。
なんで順序と書いたんだろう。

>>char str[2][128]={{0}};
>で、全域0x00でクリアされていますので。

たしかにそうですね。

あと、入力でバッファを溢れさせられるというのも気になりますよね。
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!