LoginSignup
10
3

More than 5 years have passed since last update.

C11 の _Generic の罠

Last updated at Posted at 2018-12-18

C言語アドベントカレンダー の 12/18 が空いていたので、あわてて小さな記事を書いてみた。

正直いって、仕事で C11 を使ったことはないし、趣味で書くことも殆どない。
でも、_Generic は面白そうだと思っていたので軽く調べてみたら、罠に気づいたので記しておく。

まずはソースコード。

c11
#include <stdio.h>

static inline void
show_int( int x ){  printf( "int: %d\n", x );  }

static inline void
show_str( char const * x ){  printf( "str: %s\n", x );  }

static inline void
show_char( char x ){  printf( "char: %c\n", x );  }

static inline void
show_bool( _Bool x ){  printf( "bool: %s\n", (x ? "true" : "false" ));  }

#define show(X) _Generic((X), \
  int: show_int, \
  char *: show_str, \
  char: show_char )(X)

int main(){
  show( 123 ); //=> int: 123
  show( "foo" ); //=> str: foo (合法?)
  show( 'X' ); //=> int:88 ( 'X' は、char ではなくて int )
  show(  "X"[0] ); //=> char: X ( "X"[0] なら char になる )
  show( 0==0 ); //=> int: 1 ( 0==0 は、_Bool ではなくて int )
  enum{ hoge = 456 };
  show( hoge ); // int: 456 ( 意外と int になる。合法? )
  struct S{ int i:2; char ch:7 };
  struct S s={1,33};
  show( s.i ); // clang だと「int: 1」。gcc-8 だとエラー
  show( s.ch ); // clang だと「char: !」。gcc-8 だとエラー
  return 0;
}

確認は、clang と gcc で行った。バージョンは下表の通り:

コンパイラ バージョン
clang Apple LLVM version 10.0.0 (clang-1000.11.45.5)
gcc gcc-8 (Homebrew GCC 8.2.0) 8.2.0

いずれも macOS Mojave 上。

文字列

"foo"char * ではなく char[4] だと思うんだけど、char * を選択してくれる。便利だとは思うけど、正しいのかどうかはよくわからない。

詳しくは コメント参照。

文字

'X' は、C言語では char ではなく、int である。だから show('X')show_int を選ぶ。show_char にするためには show((char)'X') などとする必要がある。

比較演算

比較演算の結果は _Bool ではなく、int になる。だから、show(0==0) は、show_int を選ぶ。show_bool にするためには show((_Bool)(0==0)) などとする必要がある。

enum

enum{ hoge=456 };hoge を渡すと意外とエラーにならず、 show_int が選択される。合法なのかなぁ。

ビットフィールド

ビットフィールドを渡したときの動作は clang と gcc-8 で異なっている。

clang は int i:2 で定義されているものは int に。 char ch:7 で定義されているものは char に割り振られる。正しそうな感じ。

gcc-8 の場合、show( s.i ) の行は

'_Generic' selector of type 'signed char:2' is not compatible with any association

というエラーに。
show( s.ch ) の行は

'_Generic' selector of type 'signed char:7' is not compatible with any association

というエラーになる。
両方とも、いやそんな型書いてないよ、という気分になる。あんまり正しそうな気がしない。clang の勝ちかなぁ。

そんなわけで

そんなわけで、もう時間もないのでこんなところで。

_Generic は割と楽しそうだと思うけど、型に無頓着な言語仕様との相性が微妙に悪くて心配な感じ。二項変換との組み合わせとか、16進数が int になるのか unsigned int になるのかとか、難しいよね。

多重 _Generic とかをキメてへんなライブラリを作りたいような気もするけど、そもそも C11 を書く機会がないんだよなぁ。

10
3
1

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
10
3