- ベストアンサー
Cプログラマーの初心者によるポインタの問題
- Cプログラマー初心者がポインタの問題について質問します。
- コンパイル時にポインタの型が矛盾しているというwarningが出ています。
- ポインタを正しく交換するためにはどのように修正すればいいのでしょうか?
- みんなの回答 (9)
- 専門家の回答
質問者が選んだベストアンサー
すみません。No.6です。 5文字目以降は変換されませんでした。 次のプログラムを実行してみました。 #include <stdio.h> void swap_str(char **s1, char **s2) { char *tmp; tmp = *s1; *s1 = *s2; *s2 = tmp; } int main(void) { char a1[15], a2[15]; puts("2つの文字列を入力"); printf("文字列a1: "); scanf("%s", a1); printf("文字列a2: "); scanf("%s", a2); printf("%X\n%X\n", a1, a2); //a1, a2それぞれの先頭のアドレスを表示 swap_str(&a1, &a2); puts("【2つの文字列の交換完了】"); printf("%X\n%X\n", a1, a2); //a1, a2それぞれの先頭のアドレスを表示 printf("文字列a1: %s\n", a1); printf("文字列a2: %s\n", a2); return 0; } このプログラムを実行すると、結果は次のようになります。 2つの文字列を入力 文字列a1: abcdefg 文字列a2: hijklmn 13FDCC <-a1の先頭のアドレス 13FDB4 <-a2の先頭のアドレス 【2つの文字列の交換完了】 13FDCC <-a1の先頭のアドレス 13FDB4 <-a2の先頭のアドレス 文字列a1: hijkefg 文字列a2: abcdlmn 見てわかるとおり先頭のアドレスは、a1もa2も変わってません。 これは、 char str[10]; と配列を定義した場合、確かにstrで先頭アドレスをあらわしますが、配列のアドレスは「定数」であるとC言語の仕様で決まっているため、swap_str関数のような処理を行って、後から変更することはできないのです。 四文字目まで変換された理由は、わかりません。
その他の回答 (8)
- 和泉 博(@hiroshi09s)
- ベストアンサー率54% (59/109)
char は1バイトが基本です。事例では a1[15] とのことですから、最大15バイトの文字格納数が確保されています。したがって a2[15] も同様の容量を持っているとの条件下における1バイトづつのバイト列の交換となり、どちらの文字列も文字列がなくなるまで swap することになります。 なお、while (*s1 || *s2) は、while(!(*s1 == '\0' && *s2 == '\0')) のことで、Cならではの書き方に換えてあります。容量が気になる方は何バイト swap したのか文字要素数のチェック(SIZE以下であること)を入れると良いでしょう。 /* String swap program by Gcc on Mac OSX * file name: kerberos.c * compile: gcc kerberos.c * execution: ./a.out */ #include <stdio.h> #include <string.h> #define SIZE 15 void swap_str(char s1[], char s2[]) { char temp; while (*s1 || *s2) { temp = *s1; *s1++ = *s2; *s2++ = temp; } } int main(void) { char a1[SIZE], a2[SIZE]; puts("2つの文字列を入力"); printf("a1: "); scanf("%s", a1); printf("a2: "); scanf("%s", a2); swap_str(a1, a2); //←訂正 swap_str(&a1, &a2); puts("【2つの文字列の交換完了】"); printf("a1: %s\n", a1); printf("a2: %s\n", a2); return 0; }
- a_kwn
- ベストアンサー率34% (8/23)
あなたのやりたいことは、もしかしたら次のコードが正しいのかもしれません。 (間違っていたらすいません) int main(void) { char a1[15], a2[15]; char *str1, *str2; /*追加*/ puts("2つの文字列を入力"); printf("文字列a1: "); scanf("%s", a1); printf("文字列a2: "); scanf("%s", a2); str1 = a1; /*追加*/ str2 = a2; /*追加*/ swap_str(&str1, &str2); /*変更*/ puts("【2つの文字列の交換完了?】"); printf("文字列str1: %s\n", str1); /*変更*/ printf("文字列str2: %s\n", str2); /*変更*/ return 0; } 以下は、混乱するかもしれませんので、参考程度に読んでください。 エラーの意味は、型が一致していないからです。 char a1[15]; で、&a1 の型は、正しくはchar (*)[15]になります。(配列へのポインタ) (例) char a1[15] = "hoge"; char **bad; char (*good)[15]; bad = &a1; /* 間違い */ good = &a1; /* 正しい */ (*good)[1] = 'a'; /* a1の2文字目を書き換える */ 分かりにくければ、こう考えてください。 typedef char strary[15]; /*strary は、char[15] を表す配列の型*/ strary *good; /* char (*good)[15]; と同じ意味*/ 配列の変数名に&をつけても、多分、思っているようなものにはならないし、 プログラムを組む際も配列へのポインタは使用する機会はほとんどないと思います。
- johnsmith_
- ベストアンサー率34% (8/23)
あなたのプログラムをコピーしてコンパイルして実行したら、 普通に動きましたよ。
- SnowShower
- ベストアンサー率40% (140/348)
>>呼び出し側は「配列の先頭アドレス」を引数にしているためにワーニングが出ているのです。 >「配列の先頭アドレス」ではなく、「配列全体のアドレス」ではないでしょうか? いえ、「先頭」であっています。 「配列」とは特定の型が(論理的に)「連続している状態」であって、別に物理的に連ぞしている必要はありません。 (もっとも、処理的には連続している方が都合がよいので、通常は物理的にも連続している状態を指していますが。) また、char a[5]; と定義された場合a[0]が100番地であれば、 a[1]=101番地、a[2]=102番地、a[3]=103番地、a[4]=104番地となり、 この「100~105番地」が「配列全体」のアドレスとなります。 言葉尻をとらえているわけではなく、「ポインタ」が関係している為の説明ですので。 質問者さんのswap_str()は配列の内容全体を交換するものではないところかも「先頭アドレス」という表現は適切です。
- Tacosan
- ベストアンサー率23% (3656/15482)
#2 の最後のところに勝手に補足: しばしば「配列名は (その配列の) 先頭要素へのポインタに変換される」といわれるのですが, これは正確ではありません. 正しくは, 「sizeof のオペランドになったときと (アドレスを求める演算子である) 単項 & のオペランドになったときを除いて」変換される, となります. 今の例ではまさにこの「除外される場合」なので, 変換されません... というか, 「a1」とか「a2」とかはプログラム上のみ存在する (つまり実行するときにはこの世のどこにも存在しない) ものなので, この関数で入れ替えることができたら逆に大変なことになってしまいます. B だったらよかったのにね.
- cyacya2000
- ベストアンサー率54% (39/71)
#2です。 >char**は2次元配列とみなされます。 と言う表現は誤解を生むかもしれません。正確な言い回し見つからなくてごめんなさい。
- cyacya2000
- ベストアンサー率54% (39/71)
配列名だけを引数にすれば、アドレス渡しになりますので、わざわざポインタのポインタを使用する必要はありません。 void swap_str(char **s1, char **s2) を void swap_str(char *s1, char *s2) とし、 swap_str(&a1, &a2); は swap_str(a1, a2); とすれば、実際の領域を書き換えることができます。 これに伴い char *tmp; も char tmp; になります。 ただし、あなたのプログラムでは、最初の1文字しか入れ替えていないので、全ての文字を入れ替えるためには繰り返し処理を行う必要があります。C言語の場合、文字列の最後は\0になるので、このことを利用すればよいでしょう。 つまり、 while(*s1!='\0') などとしてループさせればよいでしょう。 また、ループ中、ポインタをインクリメント(s1++など)することも忘れないでください。 ちなみに あなたのプログラムで、&a1はchar**型ではなく、char(*)[15]型になります。char**は2次元配列とみなされます。2次元配列を受け取る場合、添え字計算の都合上横幅(この場合15)が必要になります。 配列は何次元配列であっても、メモリ上は1次元です。従って、2次元配列の添え字計算は 行番号×横幅+列番号 となり、どうしても横幅が必要になります。図に描くと解りやすいです。もし、ポインタのポインタを使用するのであれば、横幅を明示してください。
関数swap_strの引数が「ポインタへのポインタ」であるにもかかわらず、呼び出し側は「配列の先頭アドレス」を引数にしているためにワーニングが出ているのです。 よって、「ポインタへのポインタ」を渡すためには文字列を格納する領域を配列ではなく、動的メモリで確保してやります。下記にサンプルを示します。 ------------------------------------------ void swap_str(char **s1, char **s2) { char *tmp; tmp = *s1; *s1 = *s2; *s2 = tmp; } int main(void) { char *a1, *a2; a1 = (char *)malloc(15); a2 = (char *)malloc(15); puts("2つの文字列を入力"); printf("文字列a1: "); scanf("%s", a1); printf("文字列a2: "); scanf("%s", a2); swap_str(&a1, &a2); puts("【2つの文字列の交換完了】"); printf("文字列a1: %s\n", a1); printf("文字列a2: %s\n", a2); free(a1); free(a2); return 0; }
補足
>呼び出し側は「配列の先頭アドレス」を引数にしているためにワーニングが出ているのです。 「配列の先頭アドレス」ではなく、「配列全体のアドレス」ではないでしょうか?