Cプログラマーの初心者によるポインタの問題

このQ&Aのポイント
  • Cプログラマー初心者がポインタの問題について質問します。
  • コンパイル時にポインタの型が矛盾しているというwarningが出ています。
  • ポインタを正しく交換するためにはどのように修正すればいいのでしょうか?
回答を見る
  • ベストアンサー

まだまだCプログラマーの初心者です、よろしくお願いします。

まだまだCプログラマーの初心者です、よろしくお願いします。 次のコードについての質問です。 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); swap_str(&a1, &a2); puts("【2つの文字列の交換完了】"); printf("文字列a1: %s\n", a1); printf("文字列a2: %s\n", a2); return 0; } このコードをコンパイルすると以下のwarningが出ました。 xxxxx.c:26: warning: passing arg 1 of `swap_str' from incompatible pointer type xxxxx.c:26: warning: passing arg 2 of `swap_str' from incompatible pointer type (26行目は "swap_str(&a1, &a2);" になります) 関数swap_strの仮引数部分でポインタの型が矛盾した型を受け取っているのが原因だと思うのですがどうなのでしょうか? 一応実行してみると、1byte分は交換されていましたが、それ以上の部分は交換されてませんでした。 正しく交換するためにはどのように修正すればよろしいでしょうか? 実行環境:WindowsVista(32)、コンパイラーはgccです。 以上です、よろしくお願いします。

質問者が選んだベストアンサー

  • ベストアンサー
回答No.7

すみません。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)

回答No.9

 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)
回答No.8

あなたのやりたいことは、もしかしたら次のコードが正しいのかもしれません。 (間違っていたらすいません) 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]; と同じ意味*/ 配列の変数名に&をつけても、多分、思っているようなものにはならないし、 プログラムを組む際も配列へのポインタは使用する機会はほとんどないと思います。

回答No.6

あなたのプログラムをコピーしてコンパイルして実行したら、 普通に動きましたよ。

回答No.5

>>呼び出し側は「配列の先頭アドレス」を引数にしているためにワーニングが出ているのです。 >「配列の先頭アドレス」ではなく、「配列全体のアドレス」ではないでしょうか? いえ、「先頭」であっています。 「配列」とは特定の型が(論理的に)「連続している状態」であって、別に物理的に連ぞしている必要はありません。 (もっとも、処理的には連続している方が都合がよいので、通常は物理的にも連続している状態を指していますが。) また、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)
回答No.4

#2 の最後のところに勝手に補足: しばしば「配列名は (その配列の) 先頭要素へのポインタに変換される」といわれるのですが, これは正確ではありません. 正しくは, 「sizeof のオペランドになったときと (アドレスを求める演算子である) 単項 & のオペランドになったときを除いて」変換される, となります. 今の例ではまさにこの「除外される場合」なので, 変換されません... というか, 「a1」とか「a2」とかはプログラム上のみ存在する (つまり実行するときにはこの世のどこにも存在しない) ものなので, この関数で入れ替えることができたら逆に大変なことになってしまいます. B だったらよかったのにね.

回答No.3

#2です。 >char**は2次元配列とみなされます。 と言う表現は誤解を生むかもしれません。正確な言い回し見つからなくてごめんなさい。

回答No.2

配列名だけを引数にすれば、アドレス渡しになりますので、わざわざポインタのポインタを使用する必要はありません。 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次元配列の添え字計算は 行番号×横幅+列番号 となり、どうしても横幅が必要になります。図に描くと解りやすいです。もし、ポインタのポインタを使用するのであれば、横幅を明示してください。

noname#111181
noname#111181
回答No.1

関数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; }

kerberosMA
質問者

補足

>呼び出し側は「配列の先頭アドレス」を引数にしているためにワーニングが出ているのです。 「配列の先頭アドレス」ではなく、「配列全体のアドレス」ではないでしょうか?

関連するQ&A

  • ポインタ

    質問なのですが、このソースのchar *str_copy(char *d, const char *s)関数内のchar *p=d;はなんで、*pにdを入れるか分かりません。それと、このdは、*dなのですか?どうして、while (*d++ = *s++) みたいに*dをつけないんですか?教えてください。宜しくお願いします。 #include <stdio.h> char *str_copy(char *d, const char *s) { char *p=d; while (*d++ = *s++) ; return (p); } int main(void) { char tmp[100]; char st1[100], st2[100],st3[100]; printf("文字列を入力してください:"); scanf("%s",tmp); str_copy(st1,str_copy(st2,tmp)); printf("文字列st1は%sです。\n", st1); printf("文字列st2は%sです。\n", st2); printf("文字列st3は%sです。\n", str_copy(st3,tmp)); return (0); }

  • C言語プログラミング 初心者

    現在2つの文字列を比較するstrcmp()関数と同じ動作をするプログラムを制作しています。 もちろん、strcmp()は一切用いません。 何とか作ってみたものの、文字列が等しいときなどにはうまく作動しません。 分かる方アドバイスお願いします。 以下のプログラムでは2つの50文字以下の文字列を入力することを想定しています。 #include<stdio.h> int main(void) { char str1[50],str2[50]; int a,b,i; printf("第1の文字列を入力してください: "); gets(str1); printf("第2の文字列を入力してください: "); gets(str2); /*文字列の長さを確認します*/ for(a = 0;str[a];a++) ; for(b = 0;str2[b];b++) ; printf("%s ha %d mozinonagasadesu\n",str1,a); printf("%s ha %d mozinonagasadesu\n",str2,b); /*文字列を比較します*/ for(i = 0;;i++){ if(str1[i] > str2[i]) printf("%s は %s より大きい\n"str1,str2); else if(str1[i] < str2[i]) printf("%s は %s より小さい\n"str1,str2); if(str1[i] > str2[i] || str1[i] <str2[i]) break; } for(i=0;str1[i] && str2[i];i++) if(str1[i] == str2[i] ) printf("文字列は等しい\n"); /*上のプログラミングだとstr1がaab、str2がaacのときも文字列が等しいと表示してしまうので、ぴったり等しいときのみ表示するにはどのように条件付けしたらよいでしょうか?*/ return 0; } ちなみにstr1[0]=a ,str1[1]=b, str2[0]=a, str[1]=b, str[2]=cが格納されていた場合、str1 < str2になると思うのですが正しいでしょうか? もし正しければこの場合str1[2]にはヌル文字が入っており、それがstr2[2]のcと比較されたためなのでしょうか? いろいろと質問してしまいましたが、分かる方解答お願いします。

  • 文字列のswap

    文字列をswapするプログラムを作りたいと思っています。 やってみると、最初の一文字しかswapされません。 どうしてこれではいけないのでしょうか? よろしくお願いします。 以下が作ったプログラムです。 #include<stdio.h> void swap(char *a,char *b); int main() { char a[2][5]={"abc","efg"}; printf("befor a[0]:%s a[1]:%s?n",a[0],a[1]); swap(&a[0],&a[1]); printf("after a[0]:%s a[1]:%s?n",a[0],a[1]); } void swap(char *a,char *b) { char *tmp; tmp=*a; *a=*b; *b=tmp; } 実行結果は befor a[0]:abc a[1]:efg after a[0]:ebc a[1]:afg となります。 befor a[0]:abc a[1]:efg after a[0]:efg a[1]:abc となって欲しいです。

  • C言語の問題があと少しでわからないのですが

    学校の課題に取り組んで分からないことろが出てきました。問題は以下のものです。 問題13 任意の文字列を(str)を入力して、削除文字を入力させ、strから削除文字を削除して表示しなさい。 出力例:str = abcd 削除文字:c →結果:abd というものです。 途中まで自分で考え ------------------------------------------------------------------------------------------------------------------- #include <stdio.h> void rmv(char *str, char c); int main(void) { char str[80]; int c; printf("文字列入力 : "); fgets(str, 80, stdin); printf("削除文字入力 : "); c = getchar(); void rmv(str, (char)c); printf("削除後文字列 : %s", str); return 0; } void rmv(char *str, char c) { while(*str) { if(*str == c) { while(*str) { *str = *(str + 1); ++str; } } ++str; } *str = '\0'; } --------------------------------------------------------------------------------------------------------- としましたが削除文字判定と文字つめのループ用に同じポインタを使っているので文字が一文字しか消えないと言われました。 解決方法にrmv関数内で別のポインタを用意し、if文内でコピー(例 p =str;)し次のwhile文内でこのポインタを用いて判定するというヒントを貰いましたが。自分で組んでいて上手く動きません。 どのように処理をしていけばいいでしょうか。 超初心者なので説明も付けていただくと有難いです。

  • C++

    文字を入力して、それを1つ右にずらして出力する問題。 例  aret → tare 関数内でポインタを使用するのが条件です。 ズラしているつもりが、入力した文字がそのまま出力されてしまいます。 自分の方針としては、 関数内で入力された文字の長さを確認。 文字列の最後の文字をtmpに保存して ドンドン右にずらして保存していく。 最後にtmpに保存したものを、文字列の最初に入れる。 というものです。 わかるかた、ご指導お願いします。 #include<stdio.h> #include<string.h> void kaiten(char *t) { int len =0; int i; char tmp; while (*t++){ len++; } tmp = *(t+(len-1)); for(i=len-1;i>0;i--) *(t+i)=*(t+i-1); *t = tmp; } int main() { char s[100]; printf("文字列を入力:"); scanf("%s",s); kaiten(s); printf("一文字ずらすと:%s\n",s); }

  • C言語 初心者です。

    今、英単語帳を作っているのですが、以下のソースではできません。 作ろうとしているプログラムは、a bを登録した場合、次がaabと来たら、 a aab bといったようにしたいのですが、できません。教えてください。 #include <stdio.h> #include <string.h> #define NUMBER 50 /*--- 単語帳の構造体*/ typedef struct { char *word; } words; /*--- 文字列strから文字列wordを検索する ---*/ char *str_chr(const char *str, int w) { for ( ; *str; *str++){ if (*str == w){ return ((char *)str); } } return (NULL); /*検索したが該当しないときはNULLを返す*/ } /*--- 単純交換ソート ---*/ void swap(int *x, int *y) { int temp = *x; *x = *y; *y = temp; } /*--- 配列dataの先頭n個の要素を昇順にソート ---*/ void sort(words data[], int n) { int k = n - 1; while (k >= 0){ int i, j; for (i = 1, j = -1; i <= k; i++) if (data[i - 1].word > data[i].word){ j = i - 1; swap(&data[i], &data[j]); } k = j; } } int main(void) { words word[NUMBER][20] = {{0},{0}}; char str[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; char w[128], *p; int count = 0; do{ printf("単語を入力してください。:"); /*単語を入力する*/ scanf("%s", w); p = str_chr(str, w); }while(p == NULL); count++; if(count >= NUMBER){ /*登録件数を調べる*/ printf("件数いっぱいです。\n"); } return (0); sort(word, NUMBER); return (0); }

  • C言語で分からないところがあるのですが

    すみません。C言語のポインタで分からないことがあって来ました。 ポインタの理屈は理解してはいるのですが、いざソースコードを書いてみようということになると全く手がつけられずにいます。 以下のソースコードですが、strlen()と同じ働きをする関数mystrlen()と、strcmp()と同じ働きをする関数mystrcmpを、ポインタを使って作成するものです。どこをどうすればいいのか教えてくださいませんか。 #include <stdio.h> int main(void) { char str1[80], str2[80]; int i, j; int len1, len2; printf("第1の文字列を入力してください: "); gets(str1); printf("第2の文字列を入力してください: "); gets(str2); /* * 文字列の長さを確認する */ /* NULL文字(文字列の最後)まで読み飛ばす */ for (len1 = 0; len1 < 80 && str1[len1] != '\0'; len1++) ; /* ループ終了後、len1 に文字列の長さが入っている */ if (len1 < 80) { printf("%s は %d 文字の長さです\n", str1, len1); } else { printf ("第1の文字列が80字以上あります\n"); } /* str2 についても同様 */ for (len2 = 0; len2 < 80 && str2[len2] != '\0'; len2++) ; if (len2 < 80) { printf("%s は %d 文字の長さです\n", str2, len2); } else { printf ("第2の文字列が80字以上あります\n"); } if (len1 < 80 && len2 < 80) { for (i = 0; i < 80 && str1[i] != '\0' && str2[i] != '\0' && str1[i] == str2[i]; i++) ; if (str1[i] == str2[i]) { /* 両者同時に == '\0' のはず*/ printf("文字列は等しい\n"); } else if (str1[i] < str2[i]) { /* str1[i] == '\0' のはず*/ printf("%s は %s より小さい\n", str1, str2); } else { /* str2[i] == '\0' のはず*/ printf("%s は %s より大きい\n", str1, str2); } } /* * 十分なスペースがあれば、str2をstr1の最後に連結する */ if (len1 + len2 < 80) { /* str1 の末尾を探す */ for (i = 0; str1[i] != '\0'; i++) ; /* ループを抜けた段階では i は len1 と同じはずなので、 上記のループを作らず、i の代わりに len1 を用いるのも可 */ /* それ以降に str2 の中身をコピーする */ for (j = 0; str2[j] != '\0'; j++) { str1[i+j] = str2[j]; } str1[i+j] = '\0'; printf("%s\n", str1); } else { printf ("文字列をつなげた長さが80字以上あります\n"); } /* * str2をstr1にコピーする */ if (len1 + len2 < 80) { for (i = 0; str1[i] != '\0'; i++) { str1[i] = str2[i]; } str1[i] = '\0'; printf("%s %s\n", str1, str2); } return 0; }

  • C言語

    入力した文字列と文字列'x'を受け取り、'x'の位置のポインタを返すのですが、例えば、saxcvと入力すると、xcvと表示されるのですが、このソースだと、saxcvと全部表示されてしまいます。分からないので、教えてください。宜しくお願いします。 #include <stdio.h> char *a(char *sew) { char *p=sew; while (*sew != 'x') { sew++; } return(p); } int main(void) { char str[21] ={'\0'}; int i=0,no=0; char ch; printf("文字を入力してください:"); while (i<21) { ch=getchar(); if (ch=='\n') {break;} else if (ch != '\0' && ch != '\0') { str[i] =ch; i++; } } for(i=0; i<20; i++) { if(str[i]=='x') { printf("'x'以降は%sです。\n", a(str)); no=1; break; } } if (no==0) printf("'x'は見つかりませんでした。"); return (0); }

  • sscanfでフォーマットに合っているかを調べるには。

    今,Cの関数で0-9,A-Zの4文字以内の文字列かどうか を調べたいのですが, int main() { int i; char str[] = "ABCD" /* 調べたい文字列1 */ char str1[]= "ABC@" /* 調べたい文字列1 */ char str2[]= "ABCDE" /* 調べたい文字列1 */ char *p; p = str; /* ここをstr1,str2に変える */ if (strlen(p) != 4) printf("エラーです\n"); for (i = 0; i < 4; i++) { if (!isupper(p[i]) && !isdigit(p[i])) { printf("えらーです\n"); return EXIT_FAILURE; } } printf("すべてOKです\n"); return EXIT_SUCCESS; } というようにすれば,strとstr1とstr2がフォーマットに合うかわかるのですが,これをsscanfの正規表現を使用して実装したいのですが,可能でしょうか。 int main() { int ret; char str[] = "ABCD" /* 調べたい文字列1 */ char str1[]= "ABC@" /* 調べたい文字列1 */ char str2[]= "ABCDE" /* 調べたい文字列1 */ char *p; chat tmp[30]; p = str; ret = sscanf(p, "%4[0-9A-Z]", tmp); if (ret != 1) { printf("エラーです\n"); return EXIT_FAILURE; } printf("すべてOKです\n"); return EXIT_SUCCESS; } といしょうとするならば,str1のときは,@までのABCの文字列がとれて,retには1が帰ってきてしまいます。 なにか4文字以内ということを実装できる正規表現の使い方はあるのでしょうか? ちなみに sscanf(str, "%1[0-9A-Z]%1[0-9A-Z]%1[0-9A-Z]%1[0-9A-Z]", tmp1, tmp2, tmp3, tmp4) という以外でわかる方,よろしくおねがいします。

  • isdigit

    char s[3] = "5"; if(isdigit(atoi(s)) != 0){ printf("数字"); } としても表示されません。というよりifの中に入らないのですが どうしてでしょうか? それと文字列の最初の文字が数字かどうかの判定 をしたいのですが どうすればいいのか教えて下さい。 char *str; str= s; と配列の先頭にポインタを割り当ててみましたがどうもよくわかりません。

専門家に質問してみよう