@ayukawa2231

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

【C言語】配列に分割した文字を代入しようとしたら何故かすべての配列の要素が代入した値で上書されてしまう

Q&A

Closed

解決したいこと

C言語でCSVファイルから俳句のデータをカンマ区切りで取り出してその中からランダムな語句を組み合わせて俳句を作成するというプログラムを作っています。
配列に分割した文字を代入しようとしたら何故かすべての配列の要素が代入した値で上書されてしまいます。
指定した配列だけに値が代入されるようにするにはどうすればいいか解決方法を教えていただけないでしょうか?
よろしくお願いいたします。

該当するソースコード

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

void main() {

    FILE* file;
    char line[256];
    line[0] = '\0';
    char haiku[256];
    haiku[0] = '\0';
    char* onePhrase[3][256];
    char* twoPhrase[3][256];
    char* threePhrase[3][256];
    char* ptr;
    int i = 0;
    int j = 0;
    int rnd = 0;
    file = fopen("c:\\test\\test.csv", "r");
    if (file == NULL) {
        printf("ファイルが開けません。¥n");
        exit(1);
    }

    //  ファイルのデータ読み込む
    while (fgets(line, 256, file) != NULL)
    {
        // カンマを区切りに文字列を分割
        // 1回目
        ptr = strtok(line, ",");
        *onePhrase[i] = ptr;
        printf("%s\n", *onePhrase[i]);
        // 2回目以降
        while (ptr != NULL) {
            // strtok関数により変更されたNULLのポインタが先頭
            ptr = strtok(NULL, ",");
            // ptrがNULLの場合エラーが発生するので対処
            if (ptr != NULL && j == 0) {
                *twoPhrase[i] = ptr;
                printf("%s\n", *twoPhrase[i]);
            }
            if (ptr != NULL && j == 1) {
                *threePhrase[i] = ptr;
                printf("%s\n", *threePhrase[i]);
            }
            j++;
        }
        j = 0;
        i++;
    }

    srand((unsigned int)time(NULL));
    rnd = rand() % 3;
    strcat(haiku, *onePhrase[rnd]);
    srand((unsigned int)time(NULL));
    rnd = rand() % 3;
    strcat(haiku, *twoPhrase[rnd]);
    srand((unsigned int)time(NULL));
    rnd = rand() % 3;
    strcat(haiku, *threePhrase[rnd]);

    printf("onePhrase[0] = %s\n", *onePhrase[0]);
    printf("onePhrase[1] = %s\n", *onePhrase[1]);
    printf("onePhrase[2] = %s\n", *onePhrase[2]);

    printf("俳句:%s\n", haiku);
}


### 実行結果
aaaaa
bbbbbbb
ccccc

ddddd
eeeeeee
fffff

ggggg
hhhhhhh
iiiii

onePhrase[0] = ggggg
onePhrase[1] = ggggg
onePhrase[2] = ggggg
俳句:ggggghhhhhhhiiiii
0 likes

3Answer

変数lineが参照している場所を使いまわしてるからだと思います。
このケースでは、

strdup

を使うことが多い気がしますが、
malloc, free
などの挙動を理解した上で使いましょう。

malloc, freeがわからない場合は、

char line[3][256];//csvが3行の場合

とでも宣言して、参照している場所を使いまわさないように工夫してみると良いかもしれません。

1Like

Comments

  1. @ayukawa2231

    Questioner

    返信ありがとうございます。
    lineを使いまわさないように工夫してみます。
    もし難しかったらstrdupを勉強して使ってみようと思います。

  2. @ayukawa2231

    Questioner

    lineを使いまわさないようにするとちゃんと上書きされずに代入することができました!
    ありがとうございました。

  3. 解決したようでしたら、質問をクローズしましょう。

  4. @ayukawa2231

    Questioner

    返信ありがとうございます。
    ご指摘いただきありがとうございます。
    質問をクローズさせていただきます!
    皆さんありがとうございました。

変数iは一方的に増加しますが、次の配列の定義の[3]は、どういう意味で使っているのでしょうか?
(char型ポインタの3行 x 256列 の二次元配列です)

    char* onePhrase[3][256];
    char* twoPhrase[3][256];
    char* threePhrase[3][256];
1Like

Comments

  1. @ayukawa2231

    Questioner

    返信ありがとうございます。
    仮で3行のCSVファイルの俳句を読み込んで配列に入れたいので、3行分の俳句を入れたいので[3]と定義しています。

  2. 3行限定でしたか。
    いずれにしても、変数lineのアドレスがコピーされるだけなので、最後に読み込んだ内容になってしまいます。
    すでにクローズされていました。遅レスですみません。

onePhrase[0] = aaaaa
onePhrase[1] = ddddd
onePhrase[2] = ggggg

の結果が欲しいのだが

onePhrase[0] = ggggg
onePhrase[1] = ggggg
onePhrase[2] = ggggg

となってしまうのですね。

まず

    char* onePhrase[3][256];
    char* twoPhrase[3][256];
    char* threePhrase[3][256];

は 区切った文字を格納する領域なのでしょう。たぶん

そこに

               *onePhrase[i] = ptr;

のようにして文字を格納したつもりなのでしょう。これは文字列の先頭アドレスを代入したにすぎません。つまり

最初に aaaaa,bbbbbbb,ccccc  を読み込んだら

char line[256];

に格納されますよね。 lineの領域先頭アドレスを 100番地としたら

*onePhrase[i] = ptr;

では、 aaaaaaが格納されているlineの先頭を指すアドレスつまり 100番地 の番地番号が代入されます。
文字ではなく文字が格納されている番地が *onePhrase[i] ( onePhrase[i][0] )に格納されます。

*twoPhrase[i] = ptr;

では、bbbbbbbが格納されているlineの 6番目のアドレスつまり106番地 の番地番号が代入されます。
同様に

*threePhrase[i] = ptr;

では 114番地 の番地番号が代入されます。

これでは、読み込むデータが ddddd,eeeeeee,fffffになっても代入しているものは line配列の番地ばかりで
文字列が代入されません。onePhrase[i] はいつも lineの先頭を示す(例でいうと100番地)となるので、onePhraseの[0],[1],[2]が同じになるのです。

文字列とポインタ及び領域の勉強されればよろしいかと・・・

参考までに修正内容添付します。
どうでしょうか?

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

void main() {

    FILE* file;
    char line[256];
    line[0] = '\0';
    char haiku[256];
    haiku[0] = '\0';
-    char* onePhrase[3][256];
-    char* twoPhrase[3][256];
-    char* threePhrase[3][256];
+    char onePhrase[3][256];
+    char twoPhrase[3][256];
+    char threePhrase[3][256];
    char* ptr;
    int i = 0;
    int j = 0;
    int rnd = 0;
    file = fopen("c:\\test\\test.csv", "r");
    if (file == NULL) {
        printf("ファイルが開けません。¥n");
        exit(1);
    }

    //  ファイルのデータ読み込む
    while (fgets(line, 256, file) != NULL)
    {
        // カンマを区切りに文字列を分割
        // 1回目
        ptr = strtok(line, ",");
-        *onePhrase[i] = ptr;
-        printf("%s\n", *onePhrase[i]);
+        strcpy(onePhrase[i], ptr);
+        printf("%s\n", onePhrase[i]);

        // 2回目以降
        while (ptr != NULL) {
            // strtok関数により変更されたNULLのポインタが先頭
            ptr = strtok(NULL, ",");
            // ptrがNULLの場合エラーが発生するので対処
            if (ptr != NULL && j == 0) {
-                *twoPhrase[i] = ptr;
-                printf("%s\n", *twoPhrase[i]);
+                strcpy(twoPhrase[i], ptr);
+                printf("%s\n", twoPhrase[i]);
            }
            if (ptr != NULL && j == 1) {
-                *threePhrase[i] = ptr;
-                printf("%s\n", *threePhrase[i]);
                strcpy(threePhrase[i], ptr);
                printf("%s\n", threePhrase[i]);
            }
            j++;
        }
        j = 0;
        i++;
    }

    srand((unsigned int)time(NULL));
    rnd = rand() % 3;
-    strcat(haiku, *onePhrase[rnd]);
+    strcat(haiku, onePhrase[rnd]);
    srand((unsigned int)time(NULL));
    rnd = rand() % 3;
-    strcat(haiku, *twoPhrase[rnd]);
+    strcat(haiku, twoPhrase[rnd]);
    srand((unsigned int)time(NULL));
    rnd = rand() % 3;
-    strcat(haiku, *threePhrase[rnd]);
+    strcat(haiku, threePhrase[rnd]);

-    printf("onePhrase[0] = %s\n", *onePhrase[0]);
-    printf("onePhrase[1] = %s\n", *onePhrase[1]);
-    printf("onePhrase[2] = %s\n", *onePhrase[2]);
+   printf("onePhrase[0] = %s\n", onePhrase[0]);
+    printf("onePhrase[1] = %s\n", onePhrase[1]);
+    printf("onePhrase[2] = %s\n", onePhrase[2]);

    printf("俳句:%s\n", haiku);
}
1Like

Comments

  1. @ayukawa2231

    Questioner

    返信ありがとうございます。
    実行したところ問題なく表示はされたのですが、例外がスローされたみたいで、「Run-Time Check Failure #2 - Stack around the variable 'onePhrase' was corrupted.」
    というものが表示されました。

    まだ自分がポインタと領域についてよく分かっていないまま使用していたので分かった上で使うようにします。
    とても詳しく書いて下さりありがとうございます。分かりやすくて勉強になりました!

  2. @ayukawa2231

    Questioner

    追記です。
    「Run-Time Check Failure #2 - Stack around the variable 'onePhrase' was corrupted.」のエラーが発生していた原因は読み込んでいた、「test.csv」ファイルが3行ではなく4行になっていたことが原因でした。
    何行になってもちゃんと読み込めるようにしたいと思います。
    ありがとうございました。

Your answer might help someone💌