12
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

明示的型変換には何が許されて何が許されないのか、そろそろハッキリさせておきたい。

Last updated at Posted at 2016-10-22

代入変換が行われる型の組合せ(分かりやすく言うと、代入式の左辺と右辺の型の組合せ)には、許容される組合せと許容されない組合せが存在します。

代入変換が許されない組合せの一部については、キャスト演算子((型名)オペランド)を使用して、明示的に型変換することで対応できます。もちろん、キャスト演算子の使用も許されない型の組合せというのもあります。

そこで、型分類ごとに以下についてまとめてみました。

  • 代入変換が許される相手の型(明示的なキャストが不要)。
  • 代入変換では許されないが、明示的なキャストを使えば変換が許される相手の型。

なお、単純代入演算子のオペランドの型の組合せに関する制約については、以前に書いた記事「単純代入演算子について」を参照下さい。

#結論
結論から先に言うと、こうなりました。

  • 算術型同士の相互変換は、明示的なキャストは原則的に不要。
  • void *と、関数型以外の型へのポインタとの相互変換は、明示的なキャストは原則的に不要。
  • 関数型以外の型を指すポインタについて、ある型へのポインタと、別の型へのポインタとの相互変換は、明示的キャストが必要。

「関数型以外の型」は「オブジェクト型または不完全型」と同義です。

これ以外の型の組合せについては、C90で許容されないか、許容されていても使い道が無いか、または危険性が高いので覚えなくて良いと判断したものです。

例えば、なんとなくできそうに見えて、実はやってはいけないこととしては「関数ポインタとvoid *との相互変換」。これは代入変換でも明示的なキャストでも許されておらず、規格違反となります。

#型変換における関数ポインタの「蚊帳の外」感

型変換における、関数ポインタの「蚊帳の外」感は、代入変換に限らずC言語に一般的な性質でして、例えば条件演算子?:の第2・第3オペランドの組に関数ポインタとvoid *の組を指定することも許されません。

(void *)main;
/* warning: ISO C forbids conversion of function pointer
 to object pointer type [-Wpedantic] */

1 ? main : (void *)argv;
/* warning: ISO C forbids conditional expr between ‘void *’
 and function pointer [-Wpedantic] */

以下表に見るように、関数ポインタは関数ポインタ同士でしか型変換が許されていません。相手型のターゲットが広いvoid *でさえ、関数ポインタとは相性が悪いです。

一方の型 他方の型 明示的キャストの要否
オブジェクト型・
不完全型へのポインタ
非適合なオブジェクト型・
不完全型へのポインタ
必要
オブジェクト型・
不完全型へのポインタ
void * 不要
オブジェクト型・
不完全型へのポインタ
関数型へのポインタ ×型変換不可
void * 関数型へのポインタ ×型変換不可
関数型へのポインタ 非適合な関数型へのポインタ 必要

以上の詳細は、以降を参照下さい。

#算術型の場合

算術型を含む型変換は、算術型同士か、汎整数型⇔ポインタ型の相互変換のみが許されます。

  • 算術型同士の相互変換では、型が適合しない場合でも明示的なキャストは不要です。
  • ポインタ型⇔汎整数型の相互変換は、明示的なキャストで可能です。ただし処理系定義の側面を含むため、推奨されません

以上より、算術型同士の型変換であれば、原則的に明示的キャストは不要です。ポインタ型と汎整数型の相互変換は禁じ手にするのが無難です。

#構造体型・共用体型

構造体型・共用体型は、適合しない型へ/からの変換は一切許されません

なお、構造体型・共用体型の適合条件は、構造体・共用体の型名(typedef struct TT)が同じであり、メンバの数・名前・型・並びがすべて一致していることです。つまり、キャストする意味がありません。

typedef struct A_{int x;} A;
typedef struct A_ A2; 
typedef struct B_{int x;} B;

A a;
A2 a2; 
B b;

a = a2;    /* ○適合する型への代入 */
a = b;     /* ×適合しない型への代入 */
a = (A)b;  /* ×適合しない型への代入 */
コンパイル結果
main.c:15:4: error: incompatible types when assigning to type ‘A’ from type ‘B’
  a = b;  /* 適合しない型への代入 */
    ^
main.c:16:2: error: conversion to non-scalar type requested
  a = (A)b;  /* 適合しない型への代入 */

#配列型
配列型についても、キャストの出番は無しです。

  • 配列型からの変換について、式中の配列型は「配列の先頭要素へのポインタ」に型調整されるため、ポインタ型を参照して下さい。
  • 配列型への型変換については、明示的なキャストでも許されません。
(int[10])42;  /* error: cast specifies array type */

#関数型
関数型についても、キャストの出番は無しです。

  • 関数型からの変換について、式中の関数型は「関数へのポインタ」に型調整されるため、ポインタ型を参照して下さい。
  • 関数型への型変換については、明示的なキャストでも許されません。
typedef void FunctionType(void);
(FunctionType)42;  /* error: cast specifies function type */

#void

  • voidへの明示的な型変換は、任意の型から可能です。
  • voidからの型変換は(voidへを除いて)許されません。
(void)42;       /* OK */
(int)(void)42;  /* invalid use of void expression */
(void)(void)42; /* OK */

voidへのキャストを使用するのは、副作用を起こす関数呼び出しの戻り値を捨てる時に、戻り値を使わないことを明示したい時ぐらいでしょうか。しかし、誰も(void)printf("...");とか、(void)(n = 42);とか書かないように、これも冗長なので覚えなくてよいでしょう。

#不完全型(voidを除く)

  • voidを除く不完全型から/への型変換は、許されません。

#ポインタ型

  • 汎整数型でも述べましたが、ポインタ型⇔汎整数型の相互変換は規格C90では許されていますが、処理系定義の側面をもつため推奨しません。
  • オブジェクト型または不完全型を指すポインタ型について、異なるオブジェクト型または不完全型を指すポインタとの相互変換は、明示的なキャストで許されます(適合する型の場合は明示的キャストは不要)。ただし、ポインタが指す型の境界調整が適切に行われている必要があります。
  • void *と、関数型以外の型を指すポインタとの型変換は許されます(明示的キャストは不要)。よく言われる、malloc(3)の戻り値型であるvoid *ポインタを、オブジェクト型または不完全型へのポインタ型変数に代入する際に、右オペランドに明示的なキャストが不要というのは、上の制約で許容されているからですね。
T *p;
p = malloc(sizeof(*p));
  • 関数へのポインタについて。ある関数型へのポインタを、別の関数型へのポインタに型変換した後、変換前の型の関数を呼び出すこと自体は、規格C90では許されています(ただし変換前の型以外の関数を呼び出した場合、未定義の動作)。しかしまあ、誰もやらんでしょうね…。
#include <stdio.h>

int f(void){ return 0;}

int main(void){
    /* 別の関数型へのポインタ型に型変換後、元の型の関数を呼び出し */
    /* (規格C90で許される) */
    ((double (*)(void))f)();

    return 0;
}

#今後の予定

暗黙の型変換が行われる場面(汎整数拡張を起こす演算子など)について、まとめたいと思います。

余談ですが、僕も最初、C90の規格票をCover to coverで読むことにチャレンジしたのですが、正直、眠くなるわ頭に入ってこないわ、日本語が分かりにくいわで挫折しました。

そこで方針を変えて、調べたいテーマをピンポイントで絞って問題意識を持って読むと、比較的頭に入ってきやすいですね(おかげで、ソースファイルの末尾に改行文字が無いと規格違反だとか、どーでもいいことが気になったり、自分が経験的に獲得したC言語の知識を信用できなくなり、コードを書くときに規格票が手放せなくなる、などはありますが…)。

#参考文献

  • 「プログラム言語C JISX3010-1993 (ISO/IEC 9899:1990)」
  • 「S・P・ハービソン3世とG・L・スティール・ジュニアのCリファレンスマニュアル 第5版」(玉井浩 訳)
12
14
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?