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

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

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

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

Q&A

6回答

2548閲覧

char **を含む式の意味がわからない

olee46

総合スコア32

C

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

1グッド

2クリップ

投稿2018/05/03 13:55

qsortに関する演習問題の解答で、以下のようなコードがあります。

C

1/*--- xおよびyが指す文字列の比較関数 ---*/ 2static int pstrcmp(const void *x, const void *y) 3{ 4 return strcmp(*(const char **)x, *(const char **)y); 5} 6 7/*--- 文字列を指すポインタの配列pを昇順にソート ---*/ 8void sort_pvstr(char *p[], int n) 9{ 10 qsort(p, n, sizeof(char *), pstrcmp); 11}

ここの、

C

1(*(const char **)x, *(const char **)y)

の意味がわかりません。

char * → 文字列
char ** → 文字列を格納したアドレス
というのはわかりますが、その後どのように式を解釈したらいいかわかりません。

よろしくお願いします。

yohhoy👍を押しています

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

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

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

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

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

guest

回答6

0

const というキーワードがありますが、ひとまず const は無いものと思ってください。大枠を理解してから const の意味を付け加えれば十分だと思います。すると質問者の問いはこうなります。

(*(char **)x, *(char **)y) の意味がわかりません

この (char **) という部分をキャストと呼びます。変数など(この場合、x や y)の型を、カッコ内の型であると「みなす」のがキャストです。pstrcmp()の引数は void *x, void *y ですが、

  • 実際の x は char ** x であり
  • 実際の y は char ** y である

と見做しています。
結論的に言えば、strcmp(*(char **)x, *(char **)y) は、
「引数 x, y は void 型ポインタとして渡ってきたものであるが、
それぞれchar ** x と char ** y である(それぞれ文字へのポインタへのポインタである)と見做す。
その上で、strcmp(*x, *y) という呼び出しをする」という意味です。

char * → 文字列
char ** → 文字列を格納したアドレス

という書き方が私的には疑問を感じるので、そこをクリアにしたいとも思い、どういうことなのか、メモリのイメージを描いてみました。

メモリのイメージ

(1) 「void *x」とは、変数 x は void 型ポインタである。
即ち、型情報が無い・アドレス値だけを持つポインタ変数である、という意味です。指している先のデータ型を特定できない事情があるので、こうするのですが、

(2) 実際の x はポインタへのポインタ(ダブルポインタとも言う)であると (char **)x は示しています。

今、qsort() がソートしようとする配列は char *p[] という、文字列へのポインタが並ぶ配列です。
pstrcmp() を比較関数と呼びます。比較関数は何のためにあるか。それは qsort() が比較関数 pstrcmp() を呼び出し、配列の中の2つの要素の大小関係を知るためです。

比較関数を呼ぶ時 qsort() は
(3)のように、2つの要素のアドレスを引数にして、比較関数を呼びます
例えば result = pstrcmp(&p[s], &p[t]); のような呼び出し方をすると想像してください(あくまで「のような」です)。

ライブラリ関数 qsort() がソートするデータ型は、文字列のアドレス(char * 型)だけとは限りません。整数や浮動小数点の場合もあれば、構造体の場合もあります。従って qsort() 自身は型を特定できないので、2つの要素のアドレスを void * 型のポインタとして比較関数に渡すことにしています。これが上に書いた「事情」です。

一方、比較関数 pstrcmp() 側。
渡ってきた引数 x, y から、それぞれ x がポイントする p[s] の値(== X)と、 y がポイントする p[t] の値(== Y)を引数にして、strcmp(*x, *y) と呼び出したいわけです。
しかし、void型ポインタにはメモリを操作できないという制約があり、p[s], p[t] を読みだすことができません。そこで ** x, y を char ** 型であると見做す必要がある**のです。

最後に const ですが、 const で修飾された場所を書き変えない(読むだけ)、という意味になります。

  • const void *x は、図中 (1) で、ポインタ変数 x が指す void 型?のメモリを書き変えないという意味。
  • const char **x は、図中 (3) で、X番地の文字列、Y番地の文字列を、strcmp() 関数は書き変えないという意味です。

投稿2018/05/05 09:02

編集2018/05/05 09:44
rubato6809

総合スコア1380

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

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

0

ぶっちゃけ、strcmp関数のところで、ワーニング(もしかしたらエラーかも)を出さないようにワルアガキをしているだけですねー

投稿2018/05/05 09:50

y_waiwai

総合スコア87719

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

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

HogeAnimalLover

2018/05/05 13:46

これは文字列比較をする関数定義としては自然な記述です。 仮引数の型がconst void*であることはqsort関数の仕様上当然です。また、実体は「文字列へのポインタ」ですからconst char**にキャストすることも当然です。そして、strcmpの実引数をchar*とすることも当然です。 もちろん,型を合わせるというだけならば、 return strcmp( (const char *)x, (const char *)y); でも成立はします(おそらくコンパイルを通過する)。ただし、これでは意図通りに動かないはずです。xやyは仮引数ですので中身を参照しません。関数の中で一時的に用意したローカル変数同士のアドレス比較をするだけです。
y_waiwai

2018/05/05 15:00

まあ、キャストを自然な記述だ、とかこれが正当だ、と思う気持ちも必要性もわからんでもないですし、それをあえて否定しようとも思いませんが、 私は、 キャストは変数の型というパラダイムを否定するものでしかない、と考えます。 キャストの使用、キャストを使用せざるを得ないようなデザインの設計に関しては、慎重であるべきもの、と考えます まー、qsortの比較関数はこうするしかないし、いくらあがいたところでキャストを根絶するのは無理な話で、こーゆーえーかげんなCの仕様が大好きだ、ってところもあるんですが、 それでも、 こーゆーキャストを正当化してはいけない、と思うのであります
guest

0

こんにちは。

pstrcmp(const void *x, const void *y)

のx, yが何を指すのかがポイントです。
char *p[]char*型変数の配列です。p[i]の型はchar*型ですので文字列へのポインタです。
そして、pstrcmpのx, yには普通に文字列へのポインタをそのまま渡せば良いように思いますが、qsort()関数の仕様上それはできないのです。

struct tm time_tbl[10];

をソートしたい時、

qsort(time_tbl, 10);

と呼び出します。
この時p[i]の型はstruct tm型です。
そのままpstrcmp()のx, yにstruct tm型変数をコピーするという考え方もありますが、無駄が多いですよね? struct tmへのポインタを渡せば十分なのですから。
従って、pstrcmp()のx, yにはstruct tm型変数を指すポインタが渡されます。

さて、もとに戻ってp[i]の型がchar*型の時はどうなるでしょう?
qsortは上記と全く同じプログラムですから、struct tm*型を渡すのと同じ処理をするしかありません。
p[i]の型がstruct tm型の時、p[i]を指すポインタが渡されるのですから、
p[i]の型がchar*型の時も、p[i]を指すポインタが渡されます。それはchar **型ですね。
それをchar*を受け取るstrcmpにわたすために*演算子でポインタへのポインタをポインタへ変換しているということなのです。

voidの部分はあまり気にしないでください。Cコンパイラは正しい型毎に処理する術を持たないので、全ての型をvoidで一括りにして扱っているだけです。プログラマは正しい型がchar*型であることを知っているのでプログラマがキャストしているだけです。

あとconstはプログラマのミスをコンパイラが検出できる場面を増やす機能です。今回の話題では取り敢えず忘れても良いと思います。

投稿2018/05/03 15:20

Chironian

総合スコア23272

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

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

0

qsort関数は並び替えるデータの型を知らないので、アドレス p から始まるサイズが sizeof(char*) バイトのデータが n 個ある配列、という情報しか持っていません。
そのため、qsortが比較関数(pstrcmp)を呼び出す際に、あるインデックス i のポインタは p + sizeof(char*) * i となり、それが void* 型で渡されます。

今回の場合、元のデータ p は文字列(char*)の配列ですから、それのあるインデックスをポインタで示そうとすると型は char** になります。

ですので、pstrcmpに渡された引数は const char* * のはずですが、qsortは const void* として渡してくるのでconst char* * にキャストする必要があり、文字列の比較をするにはそのポインタが示している先のデータ、つまり char* を取得する必要があるので ((const char *)x) となります。

投稿2018/05/03 14:57

toki_td

総合スコア2850

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

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

0

qsortのcomparに渡されるのは配列の要素そのものでは無く、要素のポインタだからです。
何故なら要素の型によってサイズが異なるため、要素そのものを渡すと要素毎に関数を用意する必要があるからです。

投稿2018/05/03 14:34

hichon

総合スコア5737

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

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

0

キャストした後にアドレスの中身を参照しているわけです。

char * → 文字列
char ** → 文字列を格納したアドレス

に続いているわけですから、
*xは文字列を意味します。

投稿2018/05/03 14:10

HogeAnimalLover

総合スコア4830

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

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

HogeAnimalLover

2018/05/03 14:19

まあ、int型なりで一度作ってみたから考えてみるのもありだと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問