質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

解決済

3回答

3137閲覧

ポインタのポインタについて教えてください

退会済みユーザー

退会済みユーザー

総合スコア0

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

1クリップ

投稿2015/12/25 01:55

編集2015/12/25 01:59

http://ppp-lab.sakura.ne.jp/ProgrammingPlacePlus/c/037.htmlの中の
int compareStringFor_qsort(const void* a, const void* b)
{ //引数は const void型の状態で渡されます
//これの正体は要素(char
型)1つを指すポインタですから
//今回の場合、char型を指すポインタ、つまり char型です。
char
s1 = (char*)a; // aはchar
型です。それを1つ戻すとchar型になる。それをchar型 s1に代入する
char* s2 = (char*)b; // bはchar**型です。それを1つ戻すとchar型になる。それをchar型 s2に代入する

return strcmp( s1, s2 );

}

int compareStringFor_bsearch(const void* a, const void* b)
{ //bsearch関数の第1引数の型は char
char
s1 = (char*)a;
char* s2 = (char*)b;

return strcmp( s1, s2 );

}
自分で付けたコメントで間違っているところ、できるだけ詳しくおねがいします。ポインタについては大体理解していると思います。char* s2 = (char*)b; ここが良く分かりません。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

ベストアンサー

ポインタまわりはC言語の良い所でもあり,悪い所でもあると思います.
ポインタのキャストについての理解が明暗を分けると思いますので,
いくつか検証用のコードを実行していただきたいと思います.

C言語では,型の違うもののアドレスもポインタ変数に格納することができます.
そのことは,次のコードでわかります.

c

1#include <stdio.h> 2int main(int argc, char *argv[]) 3{ 4 printf("%lu %lu %lu %lu\n", sizeof (char), sizeof(char *), sizeof(char **), sizeof(char ***)); 5 printf("sizeof(unsigned int):%lu sizeof(unsigned long):%lu\n", sizeof(unsigned int), sizeof(unsigned long)); 6 return 0; 7}

char型は1バイトですが,char*は8バイト,char**も8バイト,char***も8バイト…です.
そしてvoid*も8バイトです.(voidは1バイトですが,void型の変数は宣言できません)
バイト数が一緒という点では,char*型のポインタ変数に,char***型の値も格納できるということです.
さらに,私の環境ではunsigned longも8バイトであるため,ポインタ変数を格納するために十分です.

ポインタ変数に関連したキャストが適当でもプログラムが動いてしまうのは,
ポインタ変数とは8バイトの値を格納する変数であるからです.
次は実際にchar*int*を混ぜます.

c

1#include <stdio.h> 2int main(int argc, char *argv[]) 3{ 4 int i; 5 int data[] = {0x00112233, 0x44556677}; 6 int *iptr = data; 7 char *cptr = data; 8 printf("iptr = "); 9 for (i = 0; i < 2; i++) 10 printf("%08x ", iptr[i]); 11 printf("\ncptr = "); 12 for (i = 0; i < 8; i++) 13 printf("%02x ", cptr[i]); 14 15 printf("\niptr([]) = "); 16 for (i = 0; i < 2; i++) 17 printf("%lx ", &iptr[i]); 18 printf("\niptr(+) = "); 19 for (i = 0; i < 2; i++) 20 printf("%lx ", iptr + i); 21 printf("\ncptr = "); 22 for (i = 0; i < 8; i++) 23 printf("%lx ", &cptr[i]); 24 return 0; 25}

本来int型のデータを,無理矢理char型のポインタで参照し,中身を表示させています.
Intel x86であればリトルエンディアンなので,
データ部分は「33 22 11 00 77 66 55 44」と表示されると思います.
char*int*の違いは,添字を使ったり加減算をした時に出てきます.
試していただければ,&iptr[i]とiptr+iは同じであることがわかると思います.
そして,&iptr[0]と&iptr[1]は4バイト離れていて,&cptr[0]と&cptr[1]は1バイト離れています.
ポインタ変数の型とは,このように添字を使ったり加減算をした時に,
アドレスを何バイト分ずらすかという所に違いがあります.

まとめると,アドレスとは,64ビット環境では64ビットの値でしかありません.
そのため,ポインタ変数同士の代入は型が違っていてもできます.
またポインタ変数に,ポインタ変数へのアドレスを代入することもできます.

示してくださった例では,qsort関数の仕様上,
比較対象の要素2つへのポインタaとbをもらうことになっています.
しかし比較しなければならないのはポインタ変数aとbの中身(文字列の先頭アドレスを格納する配列の要素のアドレス)ではなく,
文字列の先頭のアドレスをstrcmp()に渡して得られる文字同士のはずです.
例えばこのような例であれば,

c

1char *data[3]; 2data[0] = "a"; 3data[1] = "bb"; 4data[2] = "ccc";

compareStringFor_qsort()が受け取ることができるのは,data[0],data[1],data[2]ではなく,
&data[0],&data[1],&data[2]なのです.
しかもchar**型のはずが,void*型にキャストされてしまっているんです.
そこでまずは,(char **)achar**型にキャストして正しい型(&data[0]と同じ型)にして,
*(char **)aで文字列の先頭アドレスを得ています(data[0]と同じ型).

混乱の原因はchar **がqsortライブラリの中でキャストされてvoid *になってしまう所だと思います.
int配列などのソートならばこれで問題ないのですが,今回は文字列の配列のソートということで少しやっかいです.
C言語は良くも悪くも,ポインタ変数のキャストルールがゆるゆるです.
明示的にキャストすれば何でもOKです.
わかりにくいところがありましたら,コメントください.

投稿2015/12/25 08:17

KenTerada

総合スコア751

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2015/12/25 20:57

わたくしの質問に答えてくださったKentTeradaさん、Chironianさん、naga3さん ありがとうございました。お三方のご回答をよく読み返して、ポインタのポインタ をよく理解して。つまずいている新詳解C言語中級編自由課題9-1に正月取り組みたいと おもいます。またそのときはよろしくお願いします。
guest

0

こんにちは。

コメントは下記タイプミス以外間違っていないように思います。

誤> // aはchar型です。それを1つ戻すとchar型になる。それをchar型 s1に代入する
正> // aはchar**型です。それを1つ戻すとchar型になる。それをchar型 s1に代入する

ところで、*には特別な意味があります。teratailの仕様で*で囲まれた文字列を太文字にするのです。この影響を受けないようにするためには、ここの「コードを入力」や「文章中のコード」を参考にされると良いです。
「コードを入力」については、記入欄の上の方に並んでいるB I A ◯ □ '' </>の◯を押すと入力されるので便利です。

char* s2 = (char*)b; ここが良く分かりません。

「引数は const void型の状態で渡されます これの正体は要素(char型)1つを指すポインタです」を見る限り完全に理解されているように見えるのですが、取り敢えず説明してみます。蛇足でしたらごめんなさい。

int compareStringFor_qsort(const void* a, const void* b)のa, bには、table[i](i=0~ARRAY_SIZE(table)-1)へのポインタが渡されます。
*(char**)b先頭の*は単項演算子の*としてコンパイラは解釈します。
単行演算子*は、被演算子としてポインタをとり、そのポインタの指すところを返します。
従って、*(char**)bは「(table[i]へのポインタb)の指すところ」となりますですので、table[i]そのものです。

投稿2015/12/25 02:59

Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

char* s2 = *(char**)b;

この文の意味ですが、bsearch関数の定義により、bには配列tableの各要素へのポインタが順に入ります。
1回目: "Japan"へのポインタ
2回目: "America"へのポインタ

例えば1回目の"Japan"という文字列はchar*型なので、そのポインタということはchar**型です。
そこで(char**)bとキャストすることによりそのポインタを取得し、さらに
*(char**)bでそのポインタの値、つまり"Japan"という文字列を取得することが出来るわけです。

投稿2015/12/25 02:28

naga3

総合スコア1293

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問