日本語版
最新ニュース
科学&テクノロジー

週末プロジェクト: C でバカなことをする

欠点はあるものの、C言語は素晴らしいものです。驚くほどシンプルでありながら、オペレーティングシステム全体を簡単に記述できるほど表現力に富んでいます。同様に奇妙なことに、その簡潔でミニマルな構文は の コードを構造化する方法として、Java から Go まで、その主流の後継言語のほぼすべてにコピーされています。オタクの間では、この構文はコードの難読化を芸術として生み出したとも言われています。 IOCCCコンテスト おそらくこの技術の最もよく知られた発表の場です。IOCCC への典型的な投稿は次のようになります。#define q [v+a] #define c b[1] #define O 1 q #define o 0 q #define r(v,a )vIOCCC の受賞作品には賞賛すべき点がたくさんありますが、研究するのは通常楽しいものではありません。わかりにくいプリプロセッサ マクロ、意味不明なフォーマット、役に立たない変数名、そして通常のコードにリバース エンジニアリングする必要がある難解な算術式としてエンコードされた単純なロジックに依存する傾向があります。これは残念なことです。C言語は読みにくいだけでなく、熟練した開発者を簡単に困惑させてしまう可能性があります。例として、 スイッチ(…) 声明: switch (i) { case 0: puts("i = 0");…

週末プロジェクト: C でバカなことをする

1719744343
2024-06-30 05:07:53

欠点はあるものの、C言語は素晴らしいものです。驚くほどシンプルでありながら、オペレーティングシステム全体を簡単に記述できるほど表現力に富んでいます。同様に奇妙なことに、その簡潔でミニマルな構文は コードを構造化する方法として、Java から Go まで、その主流の後継言語のほぼすべてにコピーされています。

オタクの間では、この構文はコードの難読化を芸術として生み出したとも言われています。 IOCCCコンテスト おそらくこの技術の最もよく知られた発表の場です。IOCCC への典型的な投稿は次のようになります。

#define			      q [v+a]
#define			     c b[1]
#define			    O 1 q
#define			   o 0 q
#define			  r(v,a
)v

IOCCC の受賞作品には賞賛すべき点がたくさんありますが、研究するのは通常楽しいものではありません。わかりにくいプリプロセッサ マクロ、意味不明なフォーマット、役に立たない変数名、そして通常のコードにリバース エンジニアリングする必要がある難解な算術式としてエンコードされた単純なロジックに依存する傾向があります。

これは残念なことです。C言語は読みにくいだけでなく、熟練した開発者を簡単に困惑させてしまう可能性があります。例として、 スイッチ(…) 声明:

  switch (i) {
    case 0: puts("i = 0"); break;
    case 1: puts("i = 1"); break;
    case 2: puts("i = 2"); break;
  }

それを理解しているC開発者はほとんどいない。 スイッチ(…) は、 もし (…) または のために (…) 実際には中括弧は必要ありません。これは問題なくコンパイルされます:

  switch (i) case 1: puts("i = 1");

そのような スイッチ(…) という表記法は聞いたことがなく、実生活で遭遇したこともありません。単に目的に反するからです。山括弧がなければ、1 つの文しか使えません。言い換えると、これは機能しません。

   switch (i)
     case 1: puts("i = 1");
     case 2: puts("i = 2"); ← ERROR: no longer in switch (...)

しかたがない!

一見関係ない話だが、実際の仕組みについて考えてみよう。 スイッチ(…):本質的には、それは栄光ある 行く一致する項目にジャンプします 場合 ラベルですが、中括弧の間で何が起こっているかは関係ありません。他のコード ブロックと同じように、コード ブロックです。

  switch (i) {
    int a = 123;
    puts("This code is unreachable!");
    default: printf("a = %dn", a);
  }

上記の例では、 1つの、123に初期化されません!信じられないなら試してみてください ここ

予想外にも、 場合 ラベルは、関連するラベルのトップレベルに表示される必要はありません。 スイッチ(…) ブロック。特に、このコードは完全に正常に動作します(リンク):

  switch (i) {
    if (0) case 0: puts("i = 0");
    if (0) case 1: puts("i = 1");
    if (0) case 2: puts("i = 2");
  }

この例では、 壊す ステートメントはフォールスルーを回避するため、コードは無条件に適切な 場合 ラベル、前の (0の場合)ですが、関連する 置く(…) が実行されると、その後のすべての呼び出しは、残りの永久に偽の もし(0) 条件文。

でも、まだあります!思い出してください もし 連鎖できる それ以外 — 構文的には、blob 全体が単一のトップレベルのステートメントとして機能します。

if (one_thing) do_one_thing; else do_another_thing;

では、これ以上前置きせずに、これまで議論してきたすべての癖を組み合わせた、中括弧のない怪物を紹介します(リンク):

#include 

int main() {
 
  int i = 1;

  switch (i)

         if (0) case 0:        puts("i = 0");
    else if (0) case 1 ... 10: puts("i = 1 ... 10");
    else if (0) case 11:       puts("i = 11");
    else if (0) default:       puts("i = something else");

  return 0;

}

そして、誰が必要とするのか スイッチ(…)とにかく?&&演算子は、ラベルのアドレスを取得できる長年のGNU拡張機能です。 行く この知識があれば、あなた自身の スイッチ(…) —ブラックジャックなど(リンク):

#include 

int main() {
 
  int i = 1;

  goto *(void*[]){ &&case_0, &&case_1, &&case_2 }[i];

  if (0) case_0: puts("i = 0");
  if (0) case_1: puts("i = 1");
  if (0) case_2: puts("i = 2");

  return 0;

}

ああ、&&のもう一つの素晴らしい使い方は、 のために (…) 変数宣言内で直接ループを実装するためにラベルを使用できるかどうかを確認してください(リンク):

#include 

int main() {

  /* Iterate from i = 0 to i = 5: */

  int i = (i = 0) & ({_:0;}) | printf("i = %dn", i) * 
          (++i > 5) ?: ({goto *&&_;0;});

  return 0;

}

この最後のスニペットはおそらく UB セーフではなく、GCC 固有のものです。しかし、要点は、読みにくくすることなく、C で完全に異質で混乱を招くコードを書くことができるということです。

この記事が気に入ったら、ぜひ購読してください。他のほとんどのソーシャル メディアとは異なり、Substack は壁で囲まれた庭園ではなく、中毒性のあるドゥームスクロール体験でもありません。これは、好きな作家と連絡を取り合うための手段にすぎません。

このサイトの記事のテーマ別カタログについては、 ここをクリック

#週末プロジェクト #でバカなことをする

執筆者について: nipponese

Nipponese News編集部は、国内外のニュースを日本語で分かりやすくお届けします。