LoginSignup
12
3

More than 5 years have passed since last update.

C言語 ライフを別にハッピーしない 14の小ネタ

Last updated at Posted at 2018-12-15

C アドベントカレンダー 13日目。
13日目を過ぎた時点で未登録だったので登録してみた。

12日目は C言語の初歩的なトピックの理解を確認する10の問題
14日目は 平成30年度秋季基本情報午後問9を解いてみました。
です。

気楽に書こうと思う。
順序に意味はない。思いついた順。

基本的に C99 の話。動作確認は

  • clang ( Apple LLVM version 10.0.0 (clang-1000.11.45.5) )
  • gcc-8 (Homebrew GCC 8.2.0) 8.2.0

-std=c99 をつけて行っている。

int の初期化子リストに複数の値を書けるかも

まずはこんなの:

c99
int x = {3,4,5,}; // warning: excess elements in scalar initializer
printf( "x=%d\n", x ); //=> x=3

出力結果から分かる通り、これはコンマ演算子ではない。
というかそもそも { } を書いてもいいというところが意外なんだけど、この点に関しては合法らしい。

複数の値を書けるのは C言語 として違法なんじゃないかと思うんだけど、clang と gcc ではエラーにならない。警告は出る。

??/ で終わる行は次の行を巻き込む

こんなの:

c99
int x = 3; //??/
x+=10000;
printf( "x = %d??/n", x ); //=> x = 3[改行]

??/ はバックスラッシュと同じ意味になる。
これは trigraph というもので、文字列の中でも有効。
上記のソースコードにある「int x = 3; //??/」は、末尾にバックスラッシュがあるのと同じ効果になるので、次の行を巻き込む。
上記のソースコード内にある「??/n」は trigraph 処理により「\n」になり、改行になる。

trigraph は「??」+ 「なんか記号」なので、CやC++ で正規表現を書いているとたまに引っかかってひどい目にあうかもね。あー正規表現にはあまり ?? は登場しないか。

{[ などには代わりの書き方がある

こんなの:

c99
%:include <stdio.h>

int main(void)<%
  int x<::> = <% 123 %>;
  printf( "x<:0:> = %d\n", x<:0:> ); //=> x<:0:> = 123
%>

clang も gcc も、警告すら出さずに上記のソースをコンパイルする。

上記の例の通り、こちらは文字列内では機能しない。
「代替綴り」と呼ばれるらしい。
こちらはひどい目に遭うパターンは思いつかない。なんかあるかなぁ。

関数の宣言の中で型を宣言してもよい。

こんなの:

c99
enum X{ a, b, c } foo( struct T{ enum X u; } x ){
  struct T tval = (struct T){.u=a};
  return tval.u;
}

返戻値での型宣言は関数の外からも見える。
関数の外からも見えるので、無理に使うことはできる。

引数での型宣言は、関数の中からしか見えない。
引数なのに関数の中からしか見えないので、現実的な使いみちはない。

sizeof 演算子の中でも型を定義できる。

こんなの:

c99
int x = sizeof( struct T{ int n; } );
struct T tval = {x}; // 上の行で定義済みなので struct T が使える。

sizeof 演算子の外からも定義が見えるので、無理に使うことはできる。

sizeof 演算子の中で関数を呼べだり呼べなかったりする

こんなの:

c99
int f(char const * p){
  puts(p);
  return 1;
}

int func(int x){}

sizeof(f("call")); //=> 何も出ない
sizeof(int [f("array")] ); //=> "array" と出る
sizeof(func(f("func"))); //=> 何も出ない

可変長配列の都合で、配列のサイズのところに関数があると、呼ばざるを得ない。
かなり気持ち悪い感じがする。

構造体の最後のメンバのあとのセミコロンは省略可能かも。

こんなの:

c99
struct foo{
  int bar;
  int baz // セミコロンがない
};

違法なような気がしてならないけど、gcc も clang もエラーにならずにコンパイルしてくれる。
警告は出るけどね。

関数にはいくらでも * をつけられる

こんなの:

c99
#include <stdio.h>

int main(void){
  (**************puts)("hello, many stars.");
}

たぶん合法。
つけてもいいことないけどね。

ちなみに & は一個までならつけられる。

ヌルターミネータのない文字列のようなもの

こんなの:

c99
int a[5]="qiita";

a は、'q', 'i', 'i', 't', 'a' の 5要素で、ヌルターミネータがつかない。
合法である。
危険。
C++ では違法。

文字定数の中に複数文字書けるかもしれない。

こんなの:

c99
long long x = 'oh be a fine girl, kiss me, right now';
printf( "x=%llx\n", x ); //=> 手元では x=206e6f77 と表示された。

処理系によってはエラーらしいけど clang も gcc もコンパイルしてくれる。

long long で受けてみたけど、32bit しかないらしい。残念。
ちなみに文字列と違って連接できない。残念。

型名をメンバ名に使って良い

こんなの:

c99
typedef int S;
int foo(void){
  typedef struct S{ S S; }S;
  // 以下略
}

typedef struct S{ S S; }S; の S は順に、

  • 構造体タグ名
  • main の外にある int の typedef 名
  • 構造体のメンバ名
  • struct S の typedef 名

になる。
C++ では違法。

static な配列引数

こんなの:

c99
#include <stdio.h>

void foo(int a[static 3]){
  printf( "%d %d %d\n", a[0], a[1], a[2] );
}

int main(void){
  int a3[3] = {11,22,33};
  int a2[2] = {111,222};
  foo(a3); // okay
  foo(a2); // clang のみ警告。gcc は通してしまう。
  foo(0); // clang のみ警告。gcc は通してしまう。
}

どうも、この見慣れない staitc

Cの実装に対するヒントで、配列引数(ポインタ)がナルではなく、宣言されたとおりの大きさと型を持っている、ということを表す。

( 「S・P・ハービソン3世とG・L・スティール・ジュニアのCリファレンスマニュアル」第5版 p324 )

というものらしい。知らなかった。

clang を使っているなら有意義っぽい。
「別にハッピーにしない」と題しているのに有意義なことを書いてしまったかも。

#include は、自分自身を include してもよい

やや長いけどこんなの:

recursive.c
#if defined FN
static RT FN(RT a, RT b){  return a<b ? b : a; }
#undef RT
#undef FN
#else
#include <stdio.h>

#define RT int
#define FN max_int
#include "recursive.c"

#define RT double
#define FN max_double
#include "recursive.c"

int main(void){
  printf( "max_int: %d, max_double: %f\n", 
    max_int(123,456),
    max_double( 1.23, 4.56) ); // => max_int: 456, max_double: 4.560000
}
#endif

C言語でテンプレートごっこをしたいときに便利かもね。

typedef名 を変数代わりにできる。

こんなの:

c99
#include <stdio.h>

int foo(int x){  return 123*x;  }
int bar(int x){  return 456*x;  }

int baz(int x){
  typedef char a[foo(x)];
  typedef char b[bar(x)];
  typedef char sum[ sizeof(a) + sizeof(b) ];
  return sizeof(sum);
}

int main(void){
  printf( "baz(1) = %d\n", baz(1) ); // baz(1) = 579
}

typedef の中で関数呼び出しもできるし、typedef 名に値を保持することもできる。
便利。

以上なんだけど

以上なんだけど、ここに書いたことのうち、合法なものはだいたい全部「S・P・ハービソン3世とG・L・スティール・ジュニアのCリファレンスマニュアル」に書いてあるよ。

C言語の本を何冊か読んだけど、圧倒的に役に立つのは「S・P・ハービソン3世とG・L・スティール・ジュニアのCリファレンスマニュアル」
C言語のことなんてわかってると思っていたけど、この本を読むと知らないことがたくさん書いてあって面白かった。
C11版出ないかなぁ。

あと。

この記事はC++ ライフをよりハッピーにするための 14の小ネタ のセルフパロディです。

12
3
3

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
3