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

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

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

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

キャスト

キャストとは、オブジェクトの型の変換が許可された場合に、明白に別の型への変換を行うプロセスのことです。

Q&A

解決済

1回答

1551閲覧

(char(*)[]) というキャストの意味

rubato6809

総合スコア1380

C

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

キャスト

キャストとは、オブジェクトの型の変換が許可された場合に、明白に別の型への変換を行うプロセスのことです。

2グッド

5クリップ

投稿2020/10/08 02:50

今更ですが qsort() を使って char *array[] = { "foo", "baa", ...
な配列をソートしてみました。qsort() に渡す比較関数の型は
int (*__compar_fn_t) (const void *, const void *); なので

C

1int cmpword(const void *word1, const void *word2) { 2 return strcmp(*(char**)word1, *(char**)word2); // (A) 3}

という関数を qsort() に渡すことで問題なく動作しました。
ここの「(char**)」というキャストは「word1 というポインタは
const void * 型として渡されるけど、実は char へのポインタを指している。即ち、word1はダブルポインタである」といった意味になるでしょう。

ところで、 char *array[]; はポインタの配列であり、word1 はその中の一要素を指すので、たいていはその前後にもポインタが並んでいます。なので、「実は word1 はポインタの配列を指している」というキャストもあるのでは?と考え、しばし試行錯誤したところ、

C

1int cmpword(const void * word1, const void * word2) { 2 return strcmp(*(char(*)[])word1, *(char(*)[])word2); // (B) 3}

が警告なくコンパイルが通り実行できることがわかりました。
なんとなくそれっぽい(笑)と思ったのですが、残念ながら実行結果がおかしいことに気づいて、調べてみたら違う事がわかりました。
次は (char**) と (char(*)[]) が違う事を確認した最終的なコードです。

C

1#include <stdio.h> 2#include <string.h> 3 4// 引数の値を表示する疑似 strcmp() 関数 5int pseudo_strcmp(const char *s1, const char *s2) { 6 printf("(%p, %p)", s1, s2); 7 return strcmp(s1, s2); 8} 9int cmpword1(const void *word1, const void *word2) { 10 printf("cmpword1() call strcmp"); 11 return pseudo_strcmp(*(char**)word1, *(char**)word2); // (A) 正しい 12} 13int cmpword2(const void *word1, const void *word2) { 14 printf("cmpword2() call strcmp"); 15 return pseudo_strcmp(*(char(*)[])word1, *(char(*)[])word2); // (B) ? 16} 17 18int main(void) 19{ 20 char *arr[] = { "the", "main", "story", "arc", "concerns" }; 21 22 printf("まず arr[] の配置を確認\n"); 23 for (int i = 0; i < 4; i++) 24 printf("[%d] %p: %p -> \"%s\"\n", i, &arr[i], arr[i], arr[i]); 25 printf("\n"); 26 27 printf("cmpword1() と cmpword2() は違う?\n"); 28 printf(" => %d\n", cmpword1(&arr[2], &arr[3])); 29 printf(" => %d\n", cmpword2(&arr[2], &arr[3])); 30 return 0; 31}

二つのキャストが同等なら (A) も (B) も strcmp() に同じ値を引数として渡すはずです。それを確認するため、疑似strcmp()関数の中で引数の値を表示させました。実行結果は一目瞭然。

$ ./a.out まず arr[] の配置を確認 [0] 0x7ffc87881250: 0x562b8bd6ea3f -> "the" [1] 0x7ffc87881258: 0x562b8bd6ea43 -> "main" [2] 0x7ffc87881260: 0x562b8bd6ea48 -> "story" [3] 0x7ffc87881268: 0x562b8bd6ea4e -> "arc" cmpword1() と cmpword2() は違う? cmpword1() call strcmp(0x562b8bd6ea48, 0x562b8bd6ea4e) => 18 cmpword2() call strcmp(0x7ffc87881260, 0x7ffc87881268) => -6

(B) が strcmp() に渡したのは cmpword2() が受け取った引数 word1 の値そのものでした。
しかし「*(char(*)[])word1,」という引数は、キャストを外してしまえば「*word1,」ですから、ポインタ word1 を使った間接参照の格好なのに、word1 の値がそのまま strcmp() に渡る・・・これは奇妙に思えます。

(char(*)[]) というキャストはどういう意味でしょうか。なぜ奇妙なことになるのでしょうか。

以上は gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0 を使いました。

thkana, ozwk👍を押しています

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

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

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

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

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

guest

回答1

0

ベストアンサー

char の配列の型は char[] です。 しかし、一部の例外を除けば「配列はその配列の先頭要素を指すポインタに暗黙に型変換される」というルールがあります。 このルールによって char[] 型の式は char* に変換されます。

さて、 char(*)[] というのは (配列の先頭要素ではなく) 配列を指すポインタを意味します。 この型のポインタが指す先が (char* ではなく) char[] であるという意味です。

ですから char(*)[] 型の式に * を付けると参照先の char[] 型の値を参照したことになり、これは上記の暗黙の型変換のルールによってその配列の先頭要素を指すポインタ、型で言えば char* になるということなのです。

このような段階を経て *(char(*)[])word1char* として解釈されます。 明示したキャストの後に暗黙の型変換が入っているのです。


C では関数ポインタ以外のポインタは void* へ変換可能であり、元の型へキャストしなおしたときには変換前の値と等しいことが保証されます。 しかし元の型でない型に変換したときにどのような意味を持つかは処理系定義ですので具体的に何が起こるかというのは言語としての理屈を付けることは出来ません。

つまり、 char**void* に変換されているのでこれを char ** 以外に変換して扱おうとするのは C としてははっきりした保証のないことをしていることになります。 (低レイヤではどうしても必要なこともあるかもしれませんが、避けるのが好ましいですね。)

ただ、常識的には「配列の先頭要素を指すポインタ」と「配列を指すポインタ」は型が違っていても実体としての値が同じになることはわかると思います。 ですから、配列を指すポインタに * を付けて配列の先頭要素を指すポインタにしたら値 (アドレス) としては変わらないという結果が生まれます。

投稿2020/10/08 06:13

編集2020/10/08 08:28
SaitoAtsushi

総合スコア5444

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

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

rubato6809

2020/10/08 07:33 編集

キャストを外した後に残る「*」で間接参照したものは配列である、その配列の値は配列の先頭アドレスになる、それは即ちword1 ポインタの値そのものである、という理解で合ってますか?
SaitoAtsushi

2020/10/08 08:32 編集

肝心な説明を忘れていたので追記します。 結果的に word1 と *word1 が (型は異なるが) 同じ値を持つという現象になります。
rubato6809

2020/10/08 08:44

大体合ってそうですね(笑) ありがとうございます。3回位読み返して、ポインタと配列の図を描いてみて、事情が見えてきました。 > 元の型でない型に変換したときにどのような意味を持つかは処理系定義 そうですね。認識を新たにしました。
thkana

2020/10/08 11:12

へえ~、と思って、やってみました。 int (*p)[]; printf("%ld\n",sizeof(*p)); //int[]は不完全型でエラー printf("%ld\n",sizeof(*(*p))); //エラーではない。intのサイズが得られる そういうもんなんですね。
rubato6809

2020/10/08 11:21

> char(*)[] というのは (配列の先頭要素ではなく) 配列を指すポインタを意味します 以前「char (*p)[10]の使い道」という質問に答えたことを思い出し、(*)[] という書き方と意味がつながり、腑に落ちました。 https://teratail.com/questions/63068
SaitoAtsushi

2020/10/08 11:22

式が配列型を持つときには配列の先頭要素を指すポインタに型変換されますが、 sizeof か単項 & を適用するときは暗黙の型変換が適用されないというルールなので p の型が int(*)[] であるときには配列のままで扱おうとして、しかし不完全型なので大きさがわからないということになるわけです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問