行列の積を戻り値として返す関数について

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
simeone
記事: 2
登録日時: 8年前

行列の積を戻り値として返す関数について

#1

投稿記事 by simeone » 8年前

学校の課題で出た3行3列の行列の積を返す関数を作るため、以下のように作りました

コード:

#include<stdio.h> 
double (*func(double x[][3],double y[][3]));
int main(){
 int i,j;
 double a[3][3]={1,2,3,4,5,6,7,8,9};
 double b[3][3]={9,8,7,6,5,4,3,2,1};
 double (*c)[3];
 c=func(a,b);
 
 for(i=0;i<3;i++){
  for(j=0;j<3;j++){
   printf("c[%d][%d]=%f\n",i,j,c[i][j]);
  }
 } 
 return 0;
}
double (*func(double a[][3],double b[][3])){
 int i,j;
 double (*c)[3];
 for(i=0;i<3;i++){
  for(j=0;j<3;j++){
   c[i][j]=a[i][0]*b[0][j]+a[i][1]*b[1][j]+a[i][2]*b[2][j];
  }
 }
 
 return c;
}
しかし、コンパイルはできるのですが、実行しようとするとエラーが生じます。
どこが間違っているのか正直見当がついていません。
どうすれば上手く実行できるようになろのか教えてください。
数列とポインタの関係については何となくしか分かっていないレベルの理解度です。

djann
記事: 27
登録日時: 11年前

Re: 行列の積を戻り値として返す関数について

#2

投稿記事 by djann » 8年前

正直、何をどうして「配列のポインタ」という魔境にたどり着いてしまったのかが疑問で仕方がないのですが・・・。
更に、このコードがコンパイルは通るというのが若干謎です。なぜならc(main()関数内の方もfunc()関数内の方も)とfunc()関数が戻す型が合っていないからです。コンパイルしている環境はなんでしょう?(私はVisual C++とClang++にて検証)


さて、配列のポインタを使用するという形を持ったまま正しく実装を行うのであれば、以下のように変更すべきです。
1. func()関数内で宣言している double (*c)[ 3 ]という変数に、正しい配列のポインタを設定する。
2. func()関数の戻り値の型を、正しく配列のポインタを戻すように書き換える。

なぜエラーが出るかというと、配列のポインタの「指し示す先」が設定されていないからです。「意味不明」な場所へアクセスしようとしても、それはアクセスできる訳がありませんからね。
そして、もし上記の「正しく書き換える」方法が一切分からない、不明というのであれば、多分配列のポインタを使うべきではないと思われます(そもそも配列のポインタの正しい使い時が私には分かりませんが)。


学校の課題ということですが、貴方は今現在どの程度まで学校で教わっておりますか?
例えば「構造体」という言葉を聞いたことはありますか?あるのであれば、そちらを使う方が簡単であり、かつ有用だと思われます。
聞いたことがあるが使い方が分からない or 聞いたことがなく、配列を用いなければならない(そもそも課題等で「教えた知識だけで何とかすべし」というのはナンセンスだと思いますが)というのであれば、再度ご返答ください。

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: 行列の積を戻り値として返す関数について

#3

投稿記事 by みけCAT » 8年前

djann さんが書きました:更に、このコードがコンパイルは通るというのが若干謎です。なぜならc(main()関数内の方もfunc()関数内の方も)とfunc()関数が戻す型が合っていないからです。コンパイルしている環境はなんでしょう?(私はVisual C++とClang++にて検証)
GCCでC言語としてコンパイルするとコンパイルが通った(これは不思議ではない)上、
なぜかランタイムエラーにもなりませんでした。不思議ですね。(undefined behaviorなので何があってもおかしくない)
(最適化をかけたら落ちました)

ちなみに、

コード:

double (*func(double x[][3],double y[][3]));
はdouble*型の値を返す関数の宣言に「普通」は付けない括弧をつけただけで、関数ポインタの宣言ではないですね。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

djann
記事: 27
登録日時: 11年前

Re: 行列の積を戻り値として返す関数について

#4

投稿記事 by djann » 8年前

関数ポインタの宣言とは書いてませんよ~。
func()関数の戻り値を受け取る部分、またreturnしようとしている部分を見て「ああ、配列のポインタを使用しようとしているのだな」と判断したまでです。

C言語はC++比べて型の認識が緩い故にコンパイルエラーにならない。なので、確かにこちらの認識ミスですね。そこは申し訳ありませんでした。
ただ「型としての整合性が取れていない」という部分には変わりないので、コンパイルエラーでなくても正しいコードではなく、また「とある環境で動いた」というのは全く無意味な情報かと。

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: 行列の積を戻り値として返す関数について

#5

投稿記事 by みけCAT » 8年前

ちなみに、double型を要素とする3要素の配列へのポインタを返す関数funcを宣言するには

3要素の配列

コード:

declarator[3]
へのポインタ

コード:

*declarator
を返す関数

コード:

declarator()
を合成して

コード:

double (*func(double x[][3],double y[][3]))[3];
と書くといいでしょう。

さらに、cにきちんと領域を割り当てるようにすると

コード:

#include<stdio.h> 
#include<stdlib.h>
double (*func(double x[][3],double y[][3]))[3];
int main(void){
 int i,j;
 double a[3][3]={1,2,3,4,5,6,7,8,9};
 double b[3][3]={9,8,7,6,5,4,3,2,1};
 double (*c)[3];
 c=func(a,b);
 
 for(i=0;i<3;i++){
  for(j=0;j<3;j++){
   printf("c[%d][%d]=%f\n",i,j,c[i][j]);
  }
 }
 free(c);
 return 0;
}
double (*func(double a[][3],double b[][3]))[3]{
 int i,j;
 double (*c)[3]=malloc(sizeof(double[3])*3);
 for(i=0;i<3;i++){
  for(j=0;j<3;j++){
   c[i][j]=a[i][0]*b[0][j]+a[i][1]*b[1][j]+a[i][2]*b[2][j];
  }
 }

 return c;
}
となります。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

超初級者
記事: 56
登録日時: 9年前

Re: 行列の積を戻り値として返す関数について

#6

投稿記事 by 超初級者 » 8年前

コンパイルエラーを取るのは頑張っていただくとして、
元のコードの23行目は
何か大きく間違っているような気がして
仕方がありません。

simeone
記事: 2
登録日時: 8年前

Re: 行列の積を戻り値として返す関数について

#7

投稿記事 by simeone » 8年前

返信遅れてすみません。皆様の回答で少し理解できてきました。
 
課題自体が2次元配列を戻り値として返すように指定されていて、配列自体は戻り値に出来ないことを知ったため色々と参考書やネットで調べて配列のポインタでどうにかできないかということでこうなってしまいました。

理解の甘さから、調べた情報を断片的に繋いでいったため、diannさんの指摘するような型が違ってしまう状況になってしまったのだと思います。

みけCATさんが例示していただいたもので、目標は達成できそうです。
mallocは(*c)[3]に配列のポインタを入れる場所(メモリ)を確保しているという理解で良いでしょうか。

構造体というものがあるのは知っていますが、講義ではまだ出てきておらず、使ったことはありません。また、コンパイルしている環境はBCC Developerです。

問題を指摘し、指南してくださり本当にありがとうございます。
もしまだ指摘すべき部分がある、知っておくべき知識があり、皆様の時間があるのであれば、更なるご指導をお願いします。

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: 行列の積を戻り値として返す関数について

#8

投稿記事 by みけCAT » 8年前

simeone さんが書きました:課題自体が2次元配列を戻り値として返すように指定されていて
C言語では配列を戻り値にすることはできないですね。
そもそもC言語には「配列の配列」はあっても「2次元配列」は無い、と主張する人もいるので、
「2次元配列」を表す構造体を作成してそれを戻り値にするか、言語が指定されていないのであれば言語を切り替えるのがいいでしょう。
simeone さんが書きました:mallocは(*c)[3]に配列のポインタを入れる場所(メモリ)を確保しているという理解で良いでしょうか。
いいえ。
mallocはここでは配列を入れる場所(メモリ)を確保しており、配列のポインタを入れる場所の確保ではありません。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

box
記事: 2002
登録日時: 13年前

Re: 行列の積を戻り値として返す関数について

#9

投稿記事 by box » 8年前

構造体を使った例です。構造体については、そのうち習うことでありましょう。

コード:

#include <stdio.h>

#define N (3)

typedef struct {
    int a[N][N];
} Array;

Array func(int a[][N], int b[][N])
{
    Array arr;
    int i, j, k;

    for (i = 0; i < N; i++) {
        for (j = 0; j < N; j++) {
            arr.a[i][j] = 0;
            for (k = 0; k < N; k++) {
                arr.a[i][j] += a[i][k] * b[k][j];
            }
        }
    }
    return arr;
}

void print(Array arr)
{
    int i, j;

    for (i = 0; i < N; i++) {
        for (j = 0; j < N; j++) {
            printf("%4d", arr.a[i][j]);
        }
        putchar('\n');
    }
}

int main(void)
{
    int a[N][N] = {
        { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 }
    };
    int b[N][N] = {
        { 9, 8, 7 }, { 6, 5, 4 }, { 3, 2, 1 }
    };

    print(func(a, b));
    return 0;
}
バグのないプログラムはない。
プログラムは思ったとおりには動かない。書いたとおりに動く。

閉鎖

“C言語何でも質問掲示板” へ戻る