- ベストアンサー
文字列配列をサブルーチンにアドレス渡ししてscanf入力
main関数内で char strarray[3][21] という文字列配列を宣言し、 サブルーチンvoid input(・・・)内でscanfを使って strarray[0]~strarray[2]の各行に入力を行うとします。 ここでポインタを引数としてstrarrayをmainとinputで共有する場合、 strarrayに関して以下の項目はどう記述すればよいのでしょうか? 1)inputを宣言する際の仮引数の書式 2)main内でinputを呼び出す際の引数の書式 3)input内でscanfする際の引数の書式 いろいろ組み合わせを試してはみたのですが、 どうにもSegmentationFaultを回避できません。 いい加減混乱してきたので、そろそろすっきりと整理したいと思っています。 よろしくお願いします。 …まあ「構造体使えばいいじゃないか」と言われてしまえばそれまでなのですが…
- みんなの回答 (5)
- 専門家の回答
質問者が選んだベストアンサー
まず、配列とポインタの関係から説明します。 C 言語の場合、配列はコンパイラによって、その「先頭要素を指すポインタ」に読み替えられます。 つまり、int hoge[10]; と定義した配列に対して、式の中で hoge と書くと、これは int*型の変数として扱われて、それは &hoge[0] と同じ意味になります。 # 一部例外はあります。 # たとえば、sizeof(hoge) とした場合、ポインタサイズではなくて、配列のサイズが # 取得できます。 で、これをそのまま配列の配列に適用してみれば良いわけです。 どうなるかというと、char piyo[3][10]; と定義した変数に対して、式の中で piyo と書けば char(*)[10]型の変数として扱われて、それは &piyo[0] と同じ意味になります。 char(*)[10] 型と書きましたが、これは「char型配列(要素数10)を指すポインタ型」のことです。 配列を指すポインタ型の変数を定義する場合には、次のように書きます。 char (*p_piyo)[10]; つまり、piyo はもともと「char型配列(要素数10)の配列(要素数5)」だったわけですよね。 これが、コンパイラによって「char型配列(要素数10)を指すポインタ」として扱われるようになるわけです。 注意点としては、この配列→ポインタの読替え規則は再帰的には行われないことですね。 どういうことかというと、配列の配列は、ポインタのポインタにはならず、配列のポインタにしかならない、ということです。 さて、前置きが長くなりましたが、話を etendard さんのご質問に戻します。 1) これまでの話で、char strarray[3][21] に対してコンパイラが読み替える等価な型が char (*str_ptr)[21] となることはおわかりいただけると思います。 関数の引数に配列を渡す場合は、必ずポインタに読みかえられますので、仮引数の宣言もポインタに読み替えられた方の型で書くことになります。 # というよりも、配列を渡すことができないので、配列の先頭要素を指すポインタを # 渡している、という言い方のほうが正しいのですけどね。 つまり、プロトタイプは void input( char (*str_ptr)[21] ); のようになります。 できれば、配列の要素数も一緒に渡せるようにするのが良いでしょう。 その場合は、こんな感じです。 void input( char (*str_ptr)[21], int num ); 2) 関数コールする場合は次のようにします。 input( strarray ); 要素数付きの場合は、次のようにします。 input( strarray, 3 ); 要素数付きにしたほうが良い理由は、ポインタに読み替えられた方の配列の要素数が、関数に渡らないためです。 標準関数では、gets() なんかはその典型ですよね。 # バッファオーバーランの原因となるということで、WARNING を吐くコンパイラも # あるとか。。。 それに対して fgets() は文字列の数も渡せるので、より安全だといえます。 要するに、それと同じことです。 3) input() の中で scanf() する場合には、次のようにすれば良いです。 for ( i=0; i<3; i++ ) { scanf( "%s", str_ptr[i] ); } # なぜこれで良いのかは、よく考えてみてください。 同様に、要素数付きの場合には次のようにします。 for ( i=0; i<num; i++ ) { scanf( "%s", str_ptr[i] ); } ちなみに、関数の仮引数の場合に限り、ポインタ宣言と配列宣言は同じ物になります。 # というよりも、いずれもポインタ宣言になります、といった方が正しいです。 つまり、以下の表記はいずれも同じ物になります。 void input( char (*array_ptr)[21] ); void input( char array_ptr[][21] ); void input( char array_ptr[3][21] ); ただし、3番目の表記をした場合であっても、[3] の部分の要素数は無視されます。 # コンパイラが勝手にポインタに読み替えてしまいますからね。 宣言や定義について、このような読替えが起こるのは関数の仮引数宣言の場合だけです。 通常の、宣言や定義ではこのような読替えは起こりませんのでご注意ください。 かなり長くなってしまいましたが、こんな感じでいかがでしょうか。
その他の回答 (4)
- koma1000nin
- ベストアンサー率30% (342/1133)
以下のプログラムはVisualC++6.0でコンパイル/テスト済みです。面倒なのでscanfによる入力は行なわず、strarray[0],[1],[2]へ直接文字列"12345"を代入し、呼び出し側でそれらを確認表示しています。 参考にして下さい。 #include <stdio.h> #include <string.h> void input( char y[3][21] ) { int i; for(i=0; i<3; i++){ strcpy( y[i], "12345" ); } } void main( void ) { char strarray[3][21]; input( strarray ); printf("%s\n", strarray[0]); printf("%s\n", strarray[1]); printf("%s\n", strarray[2]); }
- tadare
- ベストアンサー率61% (53/86)
基本的にNo1のanmochiさんのでいいと思います。 が、そのままだと >main関数内で >char strarray[3][21] >という文字列配列を宣言し、 のstrarrayが、inputのパラメータに乗らない=シンタックスエラーを起こすと 思います。 char** strarrayとchar strarray[3][21]では、 前者は、アドレスの配列(の先頭アドレス)で、アドレスのみからなり 領域の実体を一切持ちません。 後者は、charの配列でメモリ実体を持ちます。 また見かけは2次元ですがcharの配列の配列で結局は1次元配列で、 1次元配列として配列先頭のアドレスを持っています。 したがって、char** strarrayとchar strarray[3][21]の整合性をとってやるため char strarray[3][21]; char* lines[3]; int index,max; for(index=0,max=sizeof(lines)/sizeof(char*);index<max;index++) { lines[index] = strarray[index]; } input(lines); な事をやって、char** strarrayがchar strarray[3][21]の各行の領域のアドレスを 持つ配列(ここではlines)をつくってやる必要があると思います。 (いちおう試してみました(笑))
お礼
御回答ありがとうございます。 やはり、行単位で文字列を管理するポインタ配列が別に必要になりますか… どの解説書やサイトをあたっても、二次元配列にポインタを使って ダイレクトに入力・管理する方法が書かれていなかったので、 うすうすそんな気はしていたのですが。 どうもありがとうございました。
- tatsu99
- ベストアンサー率52% (391/751)
以下のようにして下さい。 -------------------------- #include <stdio.h> #include <stdlib.h> #include <string.h> void input(char strarray[3][21]) { scanf("%s",strarray[0]); scanf("%s",strarray[1]); scanf("%s",strarray[2]); } main() { char strarray[3][21]; input(strarray); printf("%s\n",strarray[0]); printf("%s\n",strarray[1]); printf("%s\n",strarray[2]); }
お礼
御回答ありがとうございます。 inputの引数を見たときは面食らいましたが、動くものなんですね。 配列は引数にできないものだと思い込んでいたので、目から鱗が落ちました。 ありがとうございました。
- anmochi
- ベストアンサー率65% (1332/2045)
1) void input(char **strarray); 2) input(strarray); 3) void input(char **strarray) { sscanf(*strarray, "FORMAT", ...); sscanf(*(strarray + 1), "FORMAT", ...); ・・・(1) sscanf(*(strarray + 2), "FORMAT", ...); } これでどぎゃんやろ。 (1):「*strarray」の型は「char *」である。よってsscanfの第一引数として利用できる。*strarrayは*(strarray + 0)と同義なので、二個目と三個目は*(strarray + 1)と*(strarray + 2)になる。 記憶を頼りに書いているので間違ってるかも。とりあえずこれでコーディング試してみてくらはい。
お礼
早速の御回答ありがとうございます。 …で、結果なのですが、 2)の部分でポインタの変換がおかしいとコンパイラ(bcc32)に言われました。 一応コンパイルは通ったのですが、 結果を出力しようとするとSegmentationFaultが出ているので、 やはり正しく入力できていないようです。 なお、3)の部分は int i; for (i=0; i<3; i++) { scanf("%s", *(strarray+i)); } で処理しています。
お礼
御回答ありがとうございます。 ソースと照らしながら順を追って見直していくと、 今まで理解の甘かった部分を整理することができました。 発生していた問題も、見事解決です。 丁寧な御解説、どうもありがとうございました。