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

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

ただいまの
回答率

90.84%

  • C

    3214questions

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

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

受付中

回答 6

投稿

  • 評価
  • クリップ 1
  • VIEW 230

olee46

score 21

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

/*--- xおよびyが指す文字列の比較関数 ---*/
static int pstrcmp(const void *x, const void *y)
{
    return strcmp(*(const char **)x, *(const char **)y);
}

/*--- 文字列を指すポインタの配列pを昇順にソート ---*/
void sort_pvstr(char *p[], int n)
{
    qsort(p, n, sizeof(char *), pstrcmp);
}

ここの、

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


の意味がわかりません。

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

よろしくお願いします。

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • kawakawa2018

    2018/05/03 23:15

    const がキーワード

    キャンセル

回答 6

+2

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() 関数は書き変えないという意味です。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

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

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/05/03 23:19

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

    キャンセル

0

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

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) となります。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

-1

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/05/05 22:46

    これは文字列比較をする関数定義としては自然な記述です。

    仮引数の型がconst void*であることはqsort関数の仕様上当然です。また、実体は「文字列へのポインタ」ですからconst char**にキャストすることも当然です。そして、strcmpの実引数をchar*とすることも当然です。

    もちろん,型を合わせるというだけならば、
    return strcmp( (const char *)x, (const char *)y);
    でも成立はします(おそらくコンパイルを通過する)。ただし、これでは意図通りに動かないはずです。xやyは仮引数ですので中身を参照しません。関数の中で一時的に用意したローカル変数同士のアドレス比較をするだけです。

    キャンセル

  • 2018/05/06 00:00

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

    キャンセル

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

  • ただいまの回答率 90.84%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

  • 解決済

    sscanf()について

    C言語について質問です。 sscanf()でバッファオーバーフローを起こすことは可能ですか?? scanf()だと普通にできてしまうのですが・・・ どなたかおしえてく

  • 解決済

    char型の変数を引数にする関数

    char型の変数の値をmain関数の中で定義して関数渡し、その度に関数の中で変数の値を再定義したいです。 下記のようなサンプルプログラムを作成したのですが、1回目に関数を呼び出し

  • 解決済

    配列への格納

    文字列を配列に格納し、出力したいのですが 上手くいきません。 ご指摘お願いいたします。。 include <stdio.h> include <stdlib.h> i

  • 解決済

    文字列のなかで指定された文字を数える。

    文字列のなかで指定された文字を数えるプログラムを作りたいのですが、どのように書けばいいのかわからなくなりました。 実行例 $ ./a.out 文字列を入力してく

  • 解決済

    c言語学習中の学生です。

    前提・実現したいこと c言語を学んでいる学生です。 非常に低レベルな質問ですがお願いします。 Windows10でmingwを使っています。 発生している問題・エラーメッセージ

  • 解決済

    conversion from string constant to 'char*' について

    display_thread_sched_attr()に対して、下記の警告がでます。 その警告が出ないようなコードはありますか? display_thread_sched_at

  • 解決済

    実行日時、実行時間を表示して、実行時間の短い順に表示したい

    実行日時、実行時間を、実行時間の短い順に表示したいのですが、 コンパイルはできる状態です。コードの下に実行結果を張り付けています。 実行時間時間の短い順に表示されない。 コード

  • 受付中

    c言語 文字列を逆順にして出力する

    発生している問題・エラーメッセージ 文字列を逆順にする関数を作成したのですが最後の文字の入れ替えができません。少しいじるとコアダンプと出てしまいます。どなたか解決法を教えてください

同じタグがついた質問を見る

  • C

    3214questions

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