質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

91.45%

  • C

    2455questions

    C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

  • Visual Studio

    1171questions

    Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

  • Windows 7

    282questions

    Microsoft Windows 7は過去にリリースされたMicrosoft WindowsのOSであり、Windows8の1代前です。2009年の7月にリリースされ販売されました。Windows7の前はWindowsVistaで、その更に3年前にリリースされました。

C言語でカレンダーを作成しているが国民の休日の判定の部分でstack overflowになってしまう

解決済

回答 2

投稿 2017/06/19 01:54

  • 評価
  • クリップ 0
  • VIEW 246

sonozaki_SZ

score 7

OS:               Windows 7 64bit
開発環境:        Visual Studio 2017
ソリューション:空のプロジェクト
デバッガー:      ローカル Windowsデバッガー x86

**calendar.h**

#ifndef CALENDAR_H
#define CALENDAR_H

#define STR_LEN        8    /* 文字列の長さ */
#define P_HOLIDAY    1    /* 祝日 */
#define O_HOLIDAY    2    /* 振替休日 */
#define N_HOLIDAY    3    /* 国民の休日 */

void read_year_month(int *y, int *m);        /* 年と月の入力と入力値の判定後に適正値を引数に代入 */
void print_calendar(int *y, int *m);        /* カレンダーの表示 */

#endif
**main.c**

#include <stdio.h>
#include "calendar.h"

int main(void)
{
    int year;    /* 年 */
    int month;    /* 月 */

    puts("年、月を入力後にその月のカレンダーを表示します");
    read_year_month(&year, &month);    /* 年と月の入力と入力値の判定後に適正値を引数に代入 */
    print_calendar(&year, &month);    /* カレンダーの表示 */
}
**read_year_month.c**

#include <stdio.h>
#include <stdbool.h>
#include "calendar.h"

void read_year_month(int *y, int *m)
{
    char str[STR_LEN];
    int i;
    bool err_flg;
    err_flg = false;

    fflush(stdin);    /* 入力バッファのフラッシュ */

    puts("年を2016~2099の範囲、月を1~12の範囲で入力して下さい");
    puts("年と月の間を半角スペースで区切って下さい");
    puts("例:2016 1/2099 12");
    printf("年と月の入力:");
    fgets(str, sizeof(str), stdin);
    fflush(stdin);

    /* 数字,半角スペース,改行文字の値以外が入力された場合にエラーフラグを立てる */
    for (i = 0; i < STR_LEN - 1; i++)
    {
        if (('0' <= str[i] && str[i] <= '9') || (str[i] == ' ' || str[i] == '\n'))
        {
            /* NOP */
        }
        else
        {
            err_flg = true;
        }
    }
    sscanf_s(str, "%d %d", y, m);    /* 文字型を整数型に変換して引数y, mに値を代入 */
    /* 範囲外の値が入力されたらエラーフラグを立てる */
    if ((*y < 2016 || 2099 < *y) || (*m < 1 || 12 < *m))
    {
        err_flg = true;
    }
    else
    {
        /* NOP */
    }

    /* エラー処理 */
    if (err_flg == true)
    {
        puts("入力された値は不正です");
        puts("入力し直して下さい");
        read_year_month(y, m);
    }
    else
    {
        /* NOP */
    }
}
**print_calendar.c**

#include <stdio.h>
#include "calendar.h"

int c_lday(int y, int m);                        /* 末日を求める関数 */
int c_dow(int y, int m, int d);                    /* 任意の日付の曜日を求める関数 */
int d_holiday(int y, int m, int d, int w);        /* 国民の祝日、振替休日、国民の休日の判定をする関数 */
void print_holiday(int y, int m, int d, int w);    /* 国民の祝日を表示する関数 */
void day_before(int *y, int *m, int *d, int *w);/* 前日を返す関数 */
void day_next(int *y, int *m, int *d, int *w);    /* 翌日を返す関数 */

void print_calendar(int *y, int *m)
{
    int day;                            /* 日にち */
    int dow;                            /* 曜日 */
    int const lday = c_lday(*y, *m);    /* 末日 */
    const char p_dow[7][3] = { "日", "月", "火", "水", "木", "金", "土" };    /* 曜日表示用 */
    int hol_flg;                        /* 祝日、振替休日、国民の休日フラグ */
    hol_flg = 0;

    /* 初日の曜日を求める */
    day = 1;
    dow = c_dow(*y, *m, day);
    /* カレンダーの表示 */
    puts("****************************");
    printf("%7d年%2d月のカレンダー\n", *y, *m);
    puts("****************************");
    for (day ; day <= lday; day++)    /* 初日から末日までループ */
    {
        hol_flg = d_holiday(*y, *m, day, dow);        /* 国民の祝日、振替休日、国民の休日の判定 */
        printf("%2d日 (%s) ", day, p_dow[dow]);
        if (hol_flg == P_HOLIDAY)
        {
            print_holiday(*y, *m, day, dow);
        }
        else if (hol_flg == O_HOLIDAY)
        {
            puts("振替休日");
        }
        else if (hol_flg == N_HOLIDAY)
        {
            puts("国民の休日");
        }
        else
        {
            putchar('\n');
        }
        dow++;
        if (dow == 7)
        {
            dow = 0;
        }
        else
        {
            /* NOP */
        }

    }
    getchar();    /* コンソールの終了待機処理 */
}

int c_lday(int y, int m)
{
    if (m == 2)    /* 2月なら */
    {
        /* うるう年の判定 */
        if (y % 400 == 0 || (y % 4 == 0 && y % 100 != 0))    /* 400で割り切れるか、4で割り切れるが100で割り切れない年だったら */
        {
            return 29;
        }
        else
        {
            return 28;
        }
    }
    else if (m == 4 || m == 6 || m == 9 || m == 11)    /* 直前までの条件を除く4月、6月、9月、11月だったら */
    {
        return 30;
    }
    else
    {
        return 31;
    }
}

int c_dow(int y, int m, int d)
{
    if (m == 1 || m == 2)
    {
        y--;
        m += 12;
        return (y + y / 4 - y / 100 + y / 400 + (13 * m + 8) / 5 + d) % 7;
    }
    else
    {
        return (y + y / 4 - y / 100 + y / 400 + (13 * m + 8) / 5 + d) % 7;
    }
}

int d_holiday(int y, int m, int d, int w)
{
    /* 引数で受け取った値をローカルにコピー */
    int buf_y, buf_y2;
    int buf_m, buf_m2;
    int buf_d, buf_d2;
    int buf_w, buf_w2;
    int flg;
    buf_y = y;
    buf_m = m;
    buf_d = d;
    buf_w = w;
    flg = 0;
    /* 国民の祝日の判定 */
    if ((buf_m == 1 && buf_d == 1)                                        /* 1/1            元日 */
        || (buf_m == 1 && buf_w == 1 && 8 <= buf_d && buf_d <= 14)        /* 1月第2月曜    成人の日 */
        || (buf_m == 2 && buf_d == 11)                                    /* 2/11            建国記念の日 */
        || (buf_m == 3 && buf_d == (int)(20.8431 + 0.242194
            * (buf_y - 1980) - (buf_y - 1980) / 4))                        /* 3/20付近        春分の日 */
        || (buf_m == 4 && buf_d == 29)                                    /* 4/29            昭和の日 */
        || (buf_m == 5 && buf_d == 3)                                    /* 5/3            憲法記念日 */
        || (buf_m == 5 && buf_d == 4)                                    /* 5/4            みどりの日 */
        || (buf_m == 5 && buf_d == 5)                                    /* 5/5            こどもの日 */
        || (buf_m == 7 && buf_w == 1 && 15 <= buf_d && buf_d <= 21)        /* 7月第3月曜    海の日 */
        || (buf_m == 8 && buf_d == 11)                                    /* 8/11            山の日 */
        || (buf_m == 9 && buf_w == 1 && 15 <= buf_d && buf_d <= 21)        /* 9月第3月曜    敬老の日 */
        || (buf_m == 9 && buf_d == (int)(23.2488 + 0.242194
            * (buf_y - 1980) - (buf_y - 1980) / 4))                        /* 9/23付近        秋分の日 */
        || (buf_m == 10 && buf_w == 1 && 8 <= buf_d && buf_d <= 14)        /* 10月第2月曜    体育の日 */
        || (buf_m == 11 && buf_d == 3)                                    /* 11/3            文化の日 */
        || (buf_m == 11 && buf_d == 23)                                    /* 11/23        勤労感謝の日 */
        || (buf_m == 12 && buf_d == 23))                                /* 12/23        天皇誕生日 */
    {
        flg = P_HOLIDAY;    /* 国民の祝日が確定 */
    }
    /* 振替休日の判定 */
    /* 前日に祝日が続く限り前日を調べ祝日と日曜日が重なったら当日を振替休日にする */
    else if (buf_w != 0 && buf_w != 6)    /* 当日が祝日でも土日でもなかったら */
    {
        do
        {
            day_before(&buf_y, &buf_m, &buf_d, &buf_w);    /* 前日 */
            if (d_holiday(buf_y, buf_m, buf_d, buf_w) == P_HOLIDAY && buf_w == 0)    /* 国民の祝日かつ日曜日だったら */
            {
                flg = O_HOLIDAY;    /* 振替休日が確定 */
            }
            else
            {
                /* NOP */
            }
        } while (d_holiday(buf_y, buf_m, buf_d, buf_w) == P_HOLIDAY);    /* 前日が祝日の間ループ */
        /* 国民の休日の判定 */
        if (flg == 0)
        {
            buf_y = buf_y2 = y;
            buf_m = buf_m2 = m;
            buf_d = buf_d2 = d;
            buf_w = buf_w2 = w;
            day_before(&buf_y, &buf_m, &buf_d, &buf_w);        /* 前日 */
            day_next(&buf_y2, &buf_m2, &buf_d2, &buf_w2);    /* 翌日 */
            if (d_holiday(buf_y, buf_m, buf_d, buf_w) == P_HOLIDAY
                && d_holiday(buf_y2, buf_m2, buf_d2, buf_w2) == P_HOLIDAY)
            {
                flg = N_HOLIDAY;    /* 国民の休日が確定 */
            }
            else
            {
                /* NOP */
            }
        }
        else
        {
            /* NOP */
        }
    }
    return flg;
}

void print_holiday(int y, int m, int d, int w)
{
    if (m == 1 && d == 1)
    {
        puts("元日");
    }
    else if (m == 1 && w == 1 && 8 <= d && d <= 14)
    {
        puts("成人の日");
    }
    else if (m == 2 && d == 11)
    {
        puts("建国記念の日");
    }
    else if (m == 3 && d == (int)(20.8431 + 0.242194
        * (y - 1980) - (y - 1980) / 4))
    {
        puts("春分の日");
    }
    else if (m == 4 && d == 29)
    {
        puts("昭和の日");
    }
    else if (m == 5 && d == 3)
    {
        puts("憲法記念日");
    }
    else if (m == 5 && d == 4)
    {
        puts("みどりの日");
    }
    else if (m == 5 && d == 5)
    {
        puts("こどもの日");
    }
    else if (m == 7 && w == 1 && 15 <= d && d <= 21)
    {
        puts("海の日");
    }
    else if (m == 8 && d == 11)
    {
        puts("山の日");
    }
    else if (m == 9 && w == 1 && 15 <= d && d <= 21)
    {
        puts("敬老の日");
    }
    else if (m == 9 && d == (int)(23.2488 + 0.242194
        * (y - 1980) - (y - 1980) / 4))
    {
        puts("秋分の日");
    }
    else if (m == 10 && w == 1 && 8 <= d && d <= 14)
    {
        puts("体育の日");
    }
    else if (m == 11 && d == 3)
    {
        puts("文化の日");
    }
    else if (m == 11 && d == 23)
    {
        puts("勤労感謝の日");
    }
    else if (m == 12 && d == 23)
    {
        puts("天皇誕生日");
    }
    else
    {
        puts("エラー");
    }
}

void day_before(int *y, int *m, int *d, int *w)
{
    if (*m == 1 && *d == 1)    /* 1月1日だったら */
    {
        /* 前年の12月31日 */
        (*y)--;
        *m = 12;
        *d = 31;
    }
    else if (*d == 1)    /* 1日だったら */
    {
        /* 前月の末日 */
        (*m)--;
        *d = c_lday(*y, *m);
    }
    else
    {
        (*d)--;
    }
    /* 前日の曜日を求める処理 */
    if (*w == 0)
    {
        *w = 6;
    }
    else
    {
        (*w)--;
    }
}

void day_next(int *y, int *m, int *d, int *w)
{
    if (*m == 12 && *d == 31)    /* 12月31日だったら */
    {
        /* 翌年の1月1日 */
        (*y)++;
        *m = 1;
        *d = 1;
    }
    else if (*d == c_lday(*y, *m))    /* 直前までの条件を除く末日だったら */
    {
        /* 翌月の1日 */
        (*m)++;
        *d = 1;
    }
    else
    {
        (*d)++;
    }
    /* 翌日の曜日を求める処理 */
    if (*w == 6)
    {
        *w = 0;
    }
    else
    {
        (*w)++;
    }
}
**実行結果**

年、月を入力後にその月のカレンダーを表示します
年を2016~2099の範囲、月を1~12の範囲で入力して下さい
年と月の間を半角スペースで区切って下さい
例:2016 1/2099 12
年と月の入力:2020 2
****************************
   2020年 2月のカレンダー
****************************
 1日 (土)
 2日 (日)
 3日 (月)
 4日 (火)
 5日 (水)
 6日 (木)
 7日 (金)
 8日 (土)
 9日 (日)
10日 (月)
11日 (火) 建国記念の日
ここでstack overflow

修正箇所とどのように修正したらいいか教えていただけるとありがたいです。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+1

 if (d_holiday(buf_y, buf_m, buf_d, buf_w) == P_HOLIDAY && d_holiday(buf_y2, buf_m2, buf_d2, buf_w2) == P_HOLIDAY)の部分が問題でしょうか...?

12日の祝日判定をするためには、12日は国民の祝日でもなく振替休日でもないので「国民の休日」判定に進み、それには11日と13日の祝日判定が必要で、11日は祝日(建国記念の日)のため13日の祝日判定が行われるが、13日は「国民の祝日」でないため、次に「振替休日」判定に進み、ここで前日(つまり12日)の祝日判定が必要になり...という風に終わらない再帰が起こってしまうのではないでしょうか?

国民の祝日に関するルールを十分調べたわけではないので参考程度ですが、たとえば「国民の休日」判定であれば、前後が「国民の祝日」であるか分かればよい(つまり「休日」を除く)のですから(この認識で合ってましたっけ?)、「国民の休日」判定の際の前後祝日判定の場合は「国民の祝日」判定だけを行うようにする、などはどうでしょう。

※何だかやたらややこしい回答になってしまいました。すみません...

投稿 2017/06/19 03:56

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/06/19 11:24 編集

    回答ありがとうございます。
    つまり国民の休日判定を行う際は国民の祝日判定のみの関数を作ってそれを呼び出した方が良いということでしょうか。
    もし再帰で出来る方法があればもう少しヒントをくださると嬉しいです。
    国民の休日の認識はそれであってると思います。
    ただ注意しないといけないのは、仮に日曜と火曜が国民の祝日であっても月曜は国民の休日ではなく振替休日になるので先に振替判定を行う必要があるということでしょうか。

    キャンセル

  • 2017/06/19 11:50 編集

    一つの関数(d_holiday)だけで「国民の祝日」以降の判定の要不要に応じて処理を切り替える場合、d_holidayにフラグとなる引数を追加するのが簡単かと思います。
    d_holidayを呼び出す際には、「国民の祝日」以降の判定が必要ならTRUE、不要ならFALSEを付けてやれば、d_holiday内部でフラグを見て異なる動作ができるでしょう。

    判定の順序は、多分現在のまま(先に振替休日判定)でいいかと思います。
    肝になるのは、振替休日判定は注目している日付より過去の日付の情報しか必要としないのに対して、国民の休日判定は未来の日付の情報が必要なため、この時に未来の日付について過去の日付(つまり現在)の情報を得なければならないことになるので、うまくパラドックスを回避するよう実装しなければならないということでしょうね。

    キャンセル

  • 2017/06/20 03:18

    Bongo氏の修正案を実践してみたところ正常に動作するようになりました。

    キャンセル

0

31日間(あるいはもっと短い日数)を配列で管理した方が何かとトラブルがないと思います。
今回スタックオーバーフローが起きている根本的な原因は、そもそも再帰構造を取り入れていることです。
再帰構造は直感的ですが、実行を追いづらく、またリソースも圧迫しがちです。


コードで書くと、次のような感じでしょうか。突貫で書いてるのでエラーあると思いますが。
あと、デバッグしながらじゃないと作りづらいところはコメントではしょってます。

#define SUNDAY 7

void print_calendar(int *y, int *m) {
   int weekDayOfThisMonth[32] = {0};  // 曜日を管理する変数
   int dayOfThisMonth[32] = {0};
   int day;
   int daysLength = c_lday(*y, *m);

   // 曜日・祝日判定
   for(day=1; day<=daysLength; day++) {
      if(isSunday(*y, *m, day)) {
          weekDayOfThisMonth[day] = SUNDAY;
      }
      if(isHoliday(*y, *m, day)) {
         dayOfThisMonth[day] = P_HOLIDAY;
      }
   }
   // 振替休日判定
   for(day=1; day<=daysLength; day++) {
      int workY = *y;
      int workM = *m;
      int workDay = day;
      getBeforeDay(&workY, &workM, &workDay);

      // バグだらけになって混乱させそうなので手書きで。
      // 1. isHoliday(workY, workM, workDay)がfalseになるまで、
      //    getBeforeDayを用いて前日に戻り続ける。
      // 2. その日から「今日」までの間に日曜があったら、「今日」は振替休日。
      if(判定結果) {
         daysOfThisMonth[day] = O_HOLIDAY;
      }
   }
   // 国民の休日判定
   for(day=1; day<=daysLength; day++) {
      if(day==1) {
         if(daysOfThisMonth[day+1]が祝日) {
            // 前の月も考慮する必要がある
            int beforeY = *y;
            int beforeM = *m;
            int beforeDay = day;
            getBeforeDay(&beforeY, &beforeM, &beforeDay);

            if(isHoliday(beforeY, beforeM, beforeDay)) {
               daysOfThisMonth[day] = N_HOLIDAY;
            }
         }
      }
      else if(day==daysLength) {
         // 次の月も考慮する必要がある
         // 1日のパターンと同様なのでパス
      }
      else {
         if(    daysOfThisMonth[day-1]が祝日
             && daysOfThisMonth[day+1]が祝日 ) {

             daysOfThisMonth[day] = N_HOLIDAY;
         }
      }
   }
}

「今あるエラーを片付ける」ことより、「エラーが出ない構造を考える」ほうが重要です。

投稿 2017/06/19 12:41

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/06/19 16:57

    回答ありがとうございます。
    次に何か作る際にはエラーが出にくく、デバッグしやすい設計を考慮して作ってみたいと思います。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

ただいまの回答率

91.45%

関連した質問

同じタグがついた質問を見る

  • C

    2455questions

    C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

  • Visual Studio

    1171questions

    Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

  • Windows 7

    282questions

    Microsoft Windows 7は過去にリリースされたMicrosoft WindowsのOSであり、Windows8の1代前です。2009年の7月にリリースされ販売されました。Windows7の前はWindowsVistaで、その更に3年前にリリースされました。

  • トップ
  • Cに関する質問
  • C言語でカレンダーを作成しているが国民の休日の判定の部分でstack overflowになってしまう