【C言語】アンダースコアではじまる10の予約語

# はじめに
どうも、y-tetsuです。

皆さん、C言語の学習お疲れ様です！筆者ものんびりですが、日々励んでおります。

最近は、本記事のタイトルにもしている**アンダースコアではじまる予約語**というものを学びました。

「アンダースコアではじまる予約語！？なんだそれ？？」

……ってリアクションをいただけた方、大変ありがとうございます！とてもいい人！！

実のところ、調べてみるまでは筆者も「ハトが豆鉄砲を食ったような顔」してました…。ですので、これからはちょっとでも「知ってるよ」と言えるよう、備忘録を残したいと思います。

是非ぜひ、皆さんも読んでいってください。



# 予約語について
まず最初に、一口にC言語で**予約語**といっても、実はいろいろな種類が含まれています。

大まかに、予約語の種類を以下に示します。

|種類 |役割 |例 |
|---|---|---|
|キーワード |構文や文法で特別な意味を持つ単語 |`int`、`return`、`if`、`while` など |
|プリプロセッサディレクティブ |コンパイル前にプリプロセッサが処理するコマンド |`#define`、`#include` など |
|その他識別子 |標準ライブラリやコンパイラ実装で使用されるもの |`__FILE__`、`__LINE__` など |

今回ご紹介するものはその内の1つ、**キーワード**になります。

C言語（[C11](https://ja.wikipedia.org/wiki/%E3%82%AD%E3%83%BC%E3%83%AF%E3%83%BC%E3%83%89_(C%E8%A8%80%E8%AA%9E))）で予約されているキーワードは44個あるそうです。その内、アンダースコアではじまるものは10個あります。当然ですが、予約されているものは識別子（変数名、関数名、型名など）として使ってはいけません。

今回の主役の面々をざっと並べてみます。（アルファベット順）

> `_Alignas`、`_Alignof`、`_Atomic`、`_Bool`、`_Complex`、`_Generic`、`_Imaginary`、`_Noreturn`、`_Static_assert`、`_Thread_local`

いかがでしょうか？ご存じのものはありましたかね。


:::note warn
ところで `_` って、アンダースコア、アンダーバー、アンダーライン、と色々な呼び名がありますが、一体どれが最適なのでしょうか。

試しにC11の最終ドラフトである[N1570](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf)を見てみると、`underscore`との記載がされておりました。やはり、"アンダースコア"と呼ぶのが良さそうかなと思います。
:::



# アンダースコアの意味
つづいて本題へ入る前に、キーワードの先頭にアンダースコア `_` を付ける**意味**について触れておきます。

[N1570](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf)には、以下のように記載されています。

（「7.1.3 Reservedidentifiers 1」より抜粋）
> ・All identifiers that begin with an underscore and either an uppercase letter or another
 underscore are always reserved for anyuse.<br><br>・All identifiers that begin with an underscore are always reserved for use as identifiers
 with file scope in both the ordinary and tag name spaces.

> ・アンダースコアで始まり、その後に大文字のアルファベットまたは別のアンダースコアが続く識別子は、常にあらゆる用途に予約されています<br><br>・アンダースコアで始まるすべての識別子は、通常の名前空間およびタグ名空間において、ファイルスコープの識別子として常に使用するために予約されています。

ここから、以下がわかりますね。
- `_Some_identifier`のような、「アンダースコア + 大文字」は予約されてるので使っちゃダメ
- `__SOME_IDENTIFIER__`のような、「アンダースコア + アンダースコア」も駄目
- `_some_identifier`のような、「アンダースコア」もだめ（関数や `if` 文などブロック内のローカル変数は一応OK）

これは主に、アンダースコアで始まる識別子は、コンパイラや標準ライブラリの実装で内部的に使用される意図だと思われます。

ちなみに、C99やC11でそれぞれ追加されたキーワードは以下のようになっています。

|C標準 |追加されたキーワード |
|---|---|
|C99 |`_Bool`、`_Complex`、`_Imaginary`、`inline`、`restrict` |
|C11 |`_Alignas`、`_Alignof`、`_Atomic`、`_Generic`、`_Noreturn`、`_Static_assert`、`_Thread_local` |

という事で、ほとんどの新しいキーワードには `_` が先頭に付いていました。バッティングする恐れがないので、C規格のバージョンアップ後も既存のコードはすんなり動くはずですよね。

理由は定かではないのですが、`inline` と `restrict` だけは特別のようです。

ChatGPT(GPT-4o)は以下のように言っておりました。

> `inline`や`restrict`のようなアンダースコアの付いていないキーワードは、C言語の標準ライブラリや多くのコードベースで頻繁に使われることが想定されるため、アンダースコアなしの形が選ばれました。これらのキーワードは、高速化や最適化を目的として使われるため、コードの読みやすさと簡潔さが重要視されます。<br><br>さらに、これらのキーワードがC99で追加される以前、`inline`は多くのコンパイラで既に拡張としてサポートされていたため、既存の使用法と整合性を保つためにアンダースコアなしのキーワードとして標準に組み込まれました。

なるほど！と思いましたが、誤っておりましたら申し訳ございません。


# アンダースコアではじまるキーワードたち
さて、準備万端となりました。

いよいよ、アンダースコアではじまる**10個のキーワード**たちを見ていきましょう。

:::note
本記事の実行結果は、GCC 14.1.0のものとなっております。
:::

## 型にまつわるシリーズ
まずは、取っつきやすいものから。

いずれも `int` や `double` の同類となる、変数の「型」を指定するものたちです。

> `_Bool`、`_Complex`、`_Imaginary`

### _Bool
`_Bool` は、符号なしの整数型で、真(1)か偽(0)の2つの値をとる真理値を表すデータ型です。

```bool.c
#include <stdio.h>

int main()
{
    _Bool a;
    int i;

    for (i=0; i<5; i++)
    {
        a = i;
        printf("a = %d -> %d\n", i, a);
    }

    return 0;
}
```

`_Bool` 型の変数 `a` に 0 から 4 までの、5つの値を代入するとどうなるか見てみます。

```md:実行結果
$ gcc ./bool.c -o bool
$ ./bool
a = 0 -> 0
a = 1 -> 1
a = 2 -> 1
a = 3 -> 1
a = 4 -> 1
```

0 は "0" それ以外は "1" となっており、確かに**2値**となっていることが分かりますね。

`stdbool.h` をインクルードすると、C++と同様の、 `bool`、`true`、`false`が使えるようになります。

```stdbool.c
#include <stdio.h>
#include <stdbool.h>

int main()
{
    bool a;

    a = true;
    printf("true  = %d\n", a);

    a = false;
    printf("false = %d\n", a);

    a = 5;
    printf("5     = %d\n", a);

    return 0;
}
```

```md:実行結果
$ gcc ./stdbool.c -o stdbool
$ ./stdbool
true  = 1
false = 0
5     = 1
```

`bool` は `_Bool` の同義語で、`true` は1、`false` は0に等しい記号定数です。

### _Complex
`_Complex` は複素数型とよばれ、複素数を表すためのデータ型です。

複素数は実部と虚部で構成されており、複素数 $z$ は、$z = x + y × i$ で表現されます。($i$は虚数単位)

`_Complex` を使う場合、プログラム中で虚数単位を扱うマクロ `I` や、実部や虚部を取得する関数(`creal` や `cimag`)などを扱うために、`complex.h` のインクルードがほぼ必須となりそうです。

使用例を以下に示します。

```complex.c
#include <stdio.h>
#include <complex.h>

int main()
{
    double _Complex z1 = 1.0 + 2.0 * I;  // 複素数 1 + 2i
    double _Complex z2 = 1.0 - 1.0 * I;  // 複素数 1 - i

    printf("z1 : %.1f + %.1fi\n", creal(z1), cimag(z1));
    printf("z2 : %.1f + %.1fi\n", creal(z2), cimag(z2));

    // 加算
    double _Complex z3 = z1 + z2;
    printf("z3 : %.1f + %.1fi\n", creal(z3), cimag(z3));

    // z3を原点を中心に 90°反時計回りに回転
    double _Complex z4 = z3 * I;
    printf("z4 : %.1f + %.1fi\n", creal(z4), cimag(z4));

    return 0;
}
```

複素数 `z1` と `z2` を加算して `z3` を求め、さらにそれに `I` を掛けて-90°回転させた `z4` を求める計算をしています。

```md:実行結果
$ gcc ./complex.c -o complex
$ ./complex
z1 : 1.0 + 2.0i
z2 : 1.0 + -1.0i
z3 : 2.0 + 1.0i
z4 : -1.0 + 2.0i
```

プログラムの実行結果は、下図のようになります。

![complex.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2653390/ca958188-fd4f-a66e-bcb4-335b142bc243.png)

複素数型には以下の3つがあります。
- `float _Complex`
- `double _Complex`
- `long double _Complex`

単独の `_Complex` は `double _Complex` と解釈されるとのこと。内部的には、実部と虚部の2要素の配列で実現されているそうです。


### _Imaginary
`_Imaginary` は純虚数型とよばれ、先ほどの複素数型の虚数部分のみを表すためのデータ型です。

サンプルコードを示します。

```imaginary.c
#include <stdio.h>
#include <complex.h>

int main()
{
    double _Imaginary z = 3.0 * I;

    printf("%.1fi\n", cimag(z));

    return 0;
}
```

……と、コードを示してはみたものの、これを実行できる処理系は、筆者には見つけられませんでした。

```md:実行結果
$ gcc ./imaginary.c -o imaginary
imaginary.c: In function 'main':
imaginary.c:6:12: error: expected identifier or '(' before '_Imaginary'
    6 |     double _Imaginary z = 3.0 * I;
      |            ^~~~~~~~~~
imaginary.c:8:29: error: 'z' undeclared (first use in this function)
    8 |     printf("%.1fi\n", cimag(z));
      |                             ^
imaginary.c:8:29: note: each undeclared identifier is reported only once for each function it appears in
```

なんと、GCCもclangも `_Imaginary` 型を**サポートしていない**との事です。

ユニコーンか？はたまたツチノコか！？ってなくらい、名実ともに"想像上の"型のようですね:unicorn:

このざんねんさは、だめぽ さんが書いて下さっている記事にて、すでに紹介されておりますので続きは↓↓コチラ↓↓へ。

https://zenn.dev/mod_poppo/articles/c-misfeatures


## マルチスレッド処理にまつわるシリーズ
お次は、マルチスレッド処理（一つのプログラムで複数の作業を同時に行う仕組み）に関連するものたちです。

> `_Atomic`、`_Thread_local`

### _Atomic
`_Atomic` は型修飾子や型指定子の形で、オブジェクトをアトミックと宣言するためのものです。

以下の2通りの使い方ができます。

```c
_Atomic 型名 変数名  // 型修飾子
_Atomic(型名) 変数名  // 型指定子
```

アトミックとは、一連の操作が他の処理と干渉せず、一度に実行されることを保証する機能です。これにより、マルチスレッド環境でのデータ競合や不整合を防げます。

まずは、比較のため `_Atomic` を使わずに、1つのカウンタを2つのスレッドで加算する例を見てみます。

```no_atmic.c
#include <stdio.h>
#include <pthread.h>

int counter = 0;

void *thread_func(void* arg)
{
    for (int i=0; i<1000000; i++)
    {
        counter++;  // カウンタを加算（2つのスレッドで共有）
    }

    return NULL;
}

int main()
{
    pthread_t thread1, thread2;

    // 2つのスレッドを作成
    pthread_create(&thread1, NULL, thread_func, NULL);
    pthread_create(&thread2, NULL, thread_func, NULL);

    // スレッドの終了を待つ
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    // 結果を表示
    printf("counter: %d\n", counter);

    return 0;
}
```

上記は2つのスレッドを作成し、それぞれのスレッドから `counter` へアクセスして加算を行います。

1000000回のカウントアップを2つのスレッドから行っているため、理想は 2000000回が結果として表示されて欲しいですが、……実はそうはなりません。

```md:実行結果
$ gcc ./no_atomic.c -o no_atomic
$ ./no_atomic
counter: 1438044
```

上記結果より、予想より回数が減ってしまっていますね。

`counter` を加算する際には、大きく「読み出し」→「加算」→「書き込み」の3ステップを踏みます。

例えば、2つのスレッドのタイミングによっては、同時に同じ値を読み出して加算する場合が発生します。この場合、2つのスレッドで**2回分の加算**を行いましたが、最終結果は**1回分の加算**となります。

このようなタイミングが何度も発生することで、意図した回数よりどんどん少ない結果になっていきます。

それもこれも、**アトミックではない操作**をしているからに他なりません。

これを解決する1つの方法として、`_Atomic` が使えます。

`counter` をアトミックとして宣言してみるとどうなるか見てみましょう。

```atmic.c
#include <stdio.h>
#include <pthread.h>
#include <stdatomic.h>

_Atomic int counter = 0;

void *thread_func(void* arg)
{
    for (int i=0; i<1000000; i++)
    {
        atomic_fetch_add(&counter, 1); // カウンタをアトミックにインクリメント
    }

    return NULL;
}

int main()
{
    pthread_t thread1, thread2;

    // 2つのスレッドを作成
    pthread_create(&thread1, NULL, thread_func, NULL);
    pthread_create(&thread2, NULL, thread_func, NULL);

    // スレッドの終了を待つ
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    // 結果を表示
    printf("counter: %d\n", counter);

    return 0;
}
```

先ほどのサンプルコードから大きく2か所変えました。

```c
_Atomic int counter = 0;
```
上記で `counter` をアトミック宣言しました。

```c
#include <stdatomic.h>
atomic_fetch_add(&counter, 1); // カウンタをアトミックにインクリメント
```

続いて加算を、専用のアトミックな加算操作に変えました。

結果がどうなるか見てみましょう。

```md:実行結果
$ gcc ./atomic.c -o atomic
$ ./atomic
counter: 2000000
```

はい。見事、加算における「読み出し」→「加算」→「書き込み」の3ステップの一貫性が保たれ、期待通りの結果となりましたね！

:::note
アトミック操作は、今回ご紹介した加算以外にもいくつかあります。他の例を少しだけ紹介しておきます。

|演算関数名 |内容・説明 |用途・特徴 |
|---|---|---|
|atomic_fetch_add |アトミック変数の値に指定された値を加算し、元の値を返す。 |インクリメントや合計計算に使用。 |
|atomic_fetch_sub |アトミック変数の値から指定された値を減算し、元の値を返す |デクリメントや減算操作に使用。 |
|atomic_fetch_and |アトミック変数の値と指定された値のAND演算を行い、元の値を返す。 |ビットマスクの操作やフラグ管理に使用。 |
|atomic_fetch_or |アトミック変数の値と指定された値のOR演算を行い、元の値を返す。 |ビット設定操作に使用。 |
:::


### _Thread_local
`_Thread_local` は記憶域クラス指定子の1つで、その名の通り、スレッド毎のローカルなオブジェクトを確保するためのものです。

言い換えると、 `_Thread_local` が指定された変数は、そのスレッドでのみ操作されるテンポラリ変数のような挙動となるイメージです。

先ほどの、`_Atomic` の章で使ったサンプルの `counter` に `_Thread_local` を指定してその挙動を確認してみましょう。

```thread_local.c
#include <stdio.h>
#include <pthread.h>

_Thread_local int counter = 0;

void *thread_func(void* arg)
{
    for (int i=0; i<1000000; i++)
    {
        counter++;  // カウンタを加算（2つのスレッドで固有）
    }

    printf("thread_func counter : %d\n", counter);

    return NULL;
}

int main()
{
    pthread_t thread1, thread2;

    // 2つのスレッドを作成
    pthread_create(&thread1, NULL, thread_func, NULL);
    pthread_create(&thread2, NULL, thread_func, NULL);

    // スレッドの終了を待つ
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    // 結果を表示
    printf("counter: %d\n", counter);

    return 0;
}
```

変更箇所は大きく2つです。

```c
_Thread_local int counter = 0;
```

`counter` に `_Thread_local` を付けました。

```c
void *thread_func(void* arg)
{
    for (int i=0; i<1000000; i++)
    {
        counter++;  // カウンタを加算（2つのスレッドで固有）
    }

    printf("thread_func counter : %d\n", counter);

    return NULL;
}
```

上記には `printf` 文を追加し、各スレッドの加算終了後に、その時のカウンタ値を表示させるようにしました。


```md:実行結果
$ gcc ./thread_local.c -o thread_local
$ ./thread_local
thread_func counter : 1000000
thread_func counter : 1000000
counter: 0
```

実行の結果、まず各スレッドで加算した `counter` の結果は 1000000がそれぞれで見られます。

また、最終結果の `counter` は　0と初期値のままです。ローカルで演算した結果は、元の値には何の影響も及ぼさないようですね。

`_Thread_local` のメリットとしては、スレッド毎で固有に操作したいデータについては、管理を簡素化し、データ競合などを防ぐことが挙げられます。

## アライメントにまつわるシリーズ
続いて、アライメント（データを効率よくメモリに配置するための境界揃えのルール）に関連するものたちです。

> `_Alignof`、`_Alignas`

### _Alignof
`_Alignof` はアライメント演算子とよばれ、引数の型が「何バイト境界に配置されなけらばならないか」の値を返します。戻り値の型は、型のバイト数を返す `sizeof` と同じ `size_t` 型です。

実例を見てみましょう。

```alignof.c
#include <stdio.h>

int main()
{
    char        a;
    short       b;
    int         c;
    long        d;
    long long   e;
    float       f;
    double      g;
    long double h;

    printf("char        : %2zu byte alignment\n", _Alignof(a));
    printf("short       : %2zu byte alignment\n", _Alignof(b));
    printf("int         : %2zu byte alignment\n", _Alignof(c));
    printf("long        : %2zu byte alignment\n", _Alignof(d));
    printf("long long   : %2zu byte alignment\n", _Alignof(e));
    printf("float       : %2zu byte alignment\n", _Alignof(f));
    printf("double      : %2zu byte alignment\n", _Alignof(g));
    printf("long double : %2zu byte alignment\n", _Alignof(h));

    return 0;
}
```

```md:実行結果
$ gcc ./alignof.c -o alignof
$ ./alignof
char        :  1 byte alignment
short       :  2 byte alignment
int         :  4 byte alignment
long        :  4 byte alignment
long long   :  8 byte alignment
float       :  4 byte alignment
double      :  8 byte alignment
long double : 16 byte alignment
```

実行結果から、各型をメモリ上に配置する際のアドレスの制約が分かります。

たとえば、 `int` の結果は4バイトでした。これは、`int` を正しく格納するためには、4で割り切れるアドレスに配置しなければならない事を意味しています。（もちろん、8なら8、16なら16で割り切れるアドレスへの配置が必要です）

:::note
書式指定子 `%z` は、`size_t` 型の変数を表示する際に、適切なサイズで出力するために使用されます。
- `%zu`: `size_t` 型の変数を符号なしの 10 進数で表示します
- `%zx`: `size_t` 型の変数を符号なしの 16 進数で表示します
:::

### _Alignas
`_Alignas` はアライメント指定子と呼ばれ、指定された変数や型のメモリ上の配置を指定するために使用されます。

`_Alignas` の構文は以下となっております。

```c
_Alignas(alignment) 型 変数名;
```

`alignment` には必要なアライメントを指定する整数または型名を指定します。

`_Alignas` の効果を見るために、`_Alignas` なしとありのそれぞれの実例を見てみましょう。

いずれも、各型の変数のアライメントと配置されたアドレスを標準出力するものです。

（アライメント指定子なし）

```no_alignas.c
#include <stdio.h>

int main()
{
    char a;
    char b;
    char c;
    short d;
    short e;
    int f;
    int g;
    int h;

    printf("a : alignment = %2zu, address = 0x%p\n", _Alignof(a), (void *)&a);
    printf("b : alignment = %2zu, address = 0x%p\n", _Alignof(b), (void *)&b);
    printf("c : alignment = %2zu, address = 0x%p\n", _Alignof(c), (void *)&c);
    printf("d : alignment = %2zu, address = 0x%p\n", _Alignof(d), (void *)&d);
    printf("e : alignment = %2zu, address = 0x%p\n", _Alignof(e), (void *)&e);
    printf("f : alignment = %2zu, address = 0x%p\n", _Alignof(f), (void *)&f);
    printf("g : alignment = %2zu, address = 0x%p\n", _Alignof(g), (void *)&g);
    printf("h : alignment = %2zu, address = 0x%p\n", _Alignof(h), (void *)&h);

    return 0;
}
```

```md:実行結果
$ gcc ./no_alignas.c -o no_alignas
$ ./no_alignas
a : alignment =  1, address = 0x0000001B899FF87F
b : alignment =  1, address = 0x0000001B899FF87E
c : alignment =  1, address = 0x0000001B899FF87D
d : alignment =  2, address = 0x0000001B899FF87A
e : alignment =  2, address = 0x0000001B899FF878
f : alignment =  4, address = 0x0000001B899FF874
g : alignment =  4, address = 0x0000001B899FF870
h : alignment =  4, address = 0x0000001B899FF86C
```

（アライメント指定子あり）

```alignas.c
#include <stdio.h>

int main()
{
    char a;
    char b;
    char c;
    _Alignas(int) short d;
    short e;
    int f;
    _Alignas(8) int g;
    _Alignas(16) int h;

    printf("a : alignment = %2zu, address = 0x%p\n", _Alignof(a), (void *)&a);
    printf("b : alignment = %2zu, address = 0x%p\n", _Alignof(b), (void *)&b);
    printf("c : alignment = %2zu, address = 0x%p\n", _Alignof(c), (void *)&c);
    printf("d : alignment = %2zu, address = 0x%p\n", _Alignof(d), (void *)&d);
    printf("e : alignment = %2zu, address = 0x%p\n", _Alignof(e), (void *)&e);
    printf("f : alignment = %2zu, address = 0x%p\n", _Alignof(f), (void *)&f);
    printf("g : alignment = %2zu, address = 0x%p\n", _Alignof(g), (void *)&g);
    printf("h : alignment = %2zu, address = 0x%p\n", _Alignof(h), (void *)&h);

    return 0;
}
```

```md:実行結果
$ gcc ./alignas.c -o alignas
$ ./alignas
a : alignment =  1, address = 0x000000B744DFF82F
b : alignment =  1, address = 0x000000B744DFF82E
c : alignment =  1, address = 0x000000B744DFF82D
d : alignment =  4, address = 0x000000B744DFF828
e : alignment =  2, address = 0x000000B744DFF826
f : alignment =  4, address = 0x000000B744DFF820
g : alignment =  8, address = 0x000000B744DFF818
h : alignment = 16, address = 0x000000B744DFF810
```

実行結果の表示だけを見て、ピンとくるのは難しそうですね…。この様子を以下に図示します。

![alignas.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2653390/b560e806-d013-6948-3f6a-e14ff67789bb.png)

配置されるアドレス自体はプログラム実行の度に変わるのであまり気にしなくてOKです。（下2桁だけを番地として表示しています）

`_Alignas` なしの場合は、各型の境界の制約を守りつつ、なるべく詰めて配置されています。

一方、`_Alignas` を指定したもの（赤枠）は、指定のバイトで割り切れる番地に移動している事が分かります。（指定されていないものは、先と同様に制約を守りつつ、なるべく詰めています）

なお、この `_Allignas` を使って配置を指定する事により得られる、うれしい効果としては以下が挙げられます。

- 異なるプラットフォームやコンパイラ間でのコードの一貫性と正確性を維持できる
- メモリアクセス効率の向上やキャッシュ効率の最適化により、パフォーマンスの向上が期待できる


## その他
最後は、どうにも分類できなかった、奇抜な！？ものたちです。

> `_Noreturn`、`_Static_assert`、`_Generic`

### _Noreturn
`_Noreturn` （関数指定子）は、添えることでその関数が呼び出された場合に、「そのまま制御が戻らない」という事を明示するためのものです。

どんなうれしさが？というと、主に以下が挙げられます。
- 警告を抑制すること
- コードの最適化を促すこと

関数が戻ることを想定しているのに実際のコードは戻らない、って場合は非常にマズいです。なので、戻らないと分かってるものはあらかじめ明示しておき、残りは警告で防ぎたいモチベーションがあるのかなと思います。（もちろん、100%警告できるわけではないですが）

`_Noreturn` の有無で警告がどう変わるかを見てみましょう。(コードの最適化については今回は割愛します)

サンプルとして、`abort` を呼ぶことでもう戻らない関数　`goodby` を用意しました。

まずはこの関数に、`_Noreturn` をつけなかった場合です。

```no_noreturn.c
#include <stdio.h>
#include <stdlib.h>

void goodbye()
{
    printf("good-bye here.\n");

    abort();

    printf("hello again. (but...)\n");
}

int main()
{
    goodbye();

    printf("never come back here.\n");

    return 0;
}
```

```md:実行結果
$ gcc ./no_noreturn.c -o no_noreturn -Wmissing-noreturn
no_noreturn.c: In function 'goodbye':
no_noreturn.c:4:6: warning: function might be candidate for attribute 'noreturn' [-Wsuggest-attribute=noreturn]
    4 | void goodbye()
      |      ^~~~~~~
$ ./no_noreturn
good-bye here.
```

あらかじめ `_Noreturn` が必要となりそうな箇所を警告するため `-Wmissing-noreturn` オプションを有効にしています。

案の定、もう戻ってこない `goodbye` 関数がコンパイラに怒られましたね。

次に、先ほどのコードの `goodbye` 関数に `_Noreturn` を付けた結果を示します。

```noreturn.c
#include <stdio.h>
#include <stdlib.h>

_Noreturn void goodbye()
{
    printf("good-bye here.\n");

    abort();

    printf("hello again. (but...)\n");
}

int main()
{
    goodbye();

    printf("never come back here.\n");

    return 0;
}
```

```md:実行結果
$ gcc ./noreturn.c -o noreturn -Wmissing-noreturn
$ ./noreturn
good-bye here.
```

`_Noreturn` であることを明示する事で、無事に警告が消えました。

ちなみに、`Noreturn` を付けた関数に `return` 文がある場合にも警告が出ます。

```return.c
#include <stdio.h>
#include <stdlib.h>

_Noreturn void goodbye()
{
    printf("good-bye here.\n");

    abort();

    printf("hello again. (but...)\n");

    return;
}

int main()
{
    goodbye();

    printf("never come back here.\n");

    return 0;
}
```

```md:実行結果
$ gcc ./noreturn.c -o noreturn
return.c: In function 'goodbye':
return.c:12:5: warning: function declared 'noreturn' has a 'return' statement
   12 |     return;
      |     ^~~~~~
$ ./noreturn
abort here.
```

戻らないはずの関数に `return` 入っちゃってますよ！どっちなんだい？と教えてくれていますね。

実はこの `_Noreturn` も、先にご紹介した[C言語のざんねんなしよう事典](https://zenn.dev/mod_poppo/articles/c-misfeatures)にとりあげらていますので、参照してみて下さい。（ざんねんなところがあります）

### _Static_assert
`_Static_assert` はコンパイル時の静的アサーション（コンパイル時チェック）を行うための宣言です。

他のチェック機構に、`assert` や `#error` がありますが、それぞれの違いもあわせて簡単に示します。

|チェック機構 |実行タイミング |主な用途 |特徴 |
|---|---|---|---|
|`#error` 指令 |プリプロセッサ |プリプロセッサ時のエラーチェック |コンパイルを停止し、メッセージを表示 |
|`_Static_assert` 宣言 |コンパイル時 |定数式のコンパイル時チェック |実行時オーバーヘッドがなく、条件が満たされないとコンパイルエラー |
|`assert` マクロ |実行時 |実行時の条件チェック |デバッグ用で条件が満たされないとアボート、`NDEBUG` を定義することで無効化可能 |

`_Static_assert` は、例えば以下のように「`int` 型が 2バイトより大きいことを確かめる」など、コードの特定の条件が満たされていることの確認に使われるようです。

```static_assert.c
#include <stdio.h>

_Static_assert(sizeof(int) > 2, "16bit code not supported.");

int main()
{
    printf("OK.\n");

    return 0;
}
```

```md:実行結果
$ gcc ./static_assert.c -o static_assert
$ ./static_assert
OK.
```

筆者の環境では、OKとなりましたが、例えば `int` が2バイトの環境では以下のエラーが出力されるはずです。

```md:エラーの場合
$ gcc ./static_assert.c -o static_assert
static_assert.c:3:1: error: static assertion failed: "16bit code not supported."
    3 | _Static_assert(sizeof(int) > 2, "16bit code not supported.");
      | ^~~~~~~~~~~~~~
```

その他、以下の用途での使用も考えられます。

- 配列サイズのチェック
- プラットフォームに依存するコードのチェック


### _Generic
いよいよラストです。筆者的にはこれが一番の「ハトが豆鉄砲！」なキーワードでした。

`_Generic` とは型総称マクロと呼ばれるもので、コンパイル時に型を判定し、指定された型に基づいて異なる式を選択する「ジェネリック選択」を実現するものです。

型に依存しない「一般的な」処理をする、という意味を込めて「ジェネリック」なのだそうです。（決して、お安いとかではなかったみたいですね）

……とはいえイメージが難しいので、さっさと具体例を見てみましょう。

```generic.c
#include <stdio.h>

#define type_to_string(x) _Generic((x), \
    int:     "int",                     \
    double:  "double",                  \
    char *:  "string",                  \
    default: "unknown")

int main()
{
    int    a = 1;
    double b = 3.14;
    char  *c = "generic";
    float  d = 5.69;

    printf("type of a is \"%s\"\n", type_to_string(a));
    printf("type of b is \"%s\"\n", type_to_string(b));
    printf("type of c is \"%s\"\n", type_to_string(c));
    printf("type of d is \"%s\"\n", type_to_string(d));

    return 0;
}
```

マクロ定義のところで `_Generic` が使われています。

まず、サンプルコードが何をしようとしているかというと、`type_to_string` マクロの引数 `x` の「型」が何なのかに応じて、返す文字列を切り替えて表示させようとしています。

```md:実行結果
$ gcc ./generic.c -o generic
$ ./generic
type of a is "int"
type of b is "double"
type of c is "string"
type of d is "unknown"
```

実行結果より、`type_to_string(a)` は引数の `a` が　`int` 型なので、対応する文字列 `"int"` が返されています。

また、`type_to_string(d)` は引数の `d` が　`float` 型で合致するものがありません。その場合は、`default` に対応する文字列 `"unknown"` が返されています。

ここで、分かりやすさのために語弊を恐れず言うと、**`_Generic` は、型名で分岐する `switch` 文**のようなものです。

`_Generic` の構文は以下になります。
```
_Generic( `expression`, `type1`: `selection1`, `type2`: `selection2`, ..., `default`: `default_selection` )
```

コンパイルは通りませんが、`type_to_string` マクロを `switch` 文を使った関数に書き直したイメージを、以下に示します。(引数 `x` に型名を与えられないので正しくはないですが)

```c
char* type_to_string(type x)
{
    switch (x)
    {
        case int:
            return "int";
        case double:
            return "double";
        case string:
            return "string";
        default:
            return "unknown";
    }

    return NULL;
}
```


この場合、`_Generic` の構文と `switc` 分の対応付けは以下のようになります。

|`_Generic` の構文 | `switch` 文 |
|---|---|
|`expression` | `x` |
|`type` |`case` 文の各条件 |
|`selection` |`case` 文の各処理 |
|`default` |`default` 文 |
|`default_selection` |`default` 文の処理 |

シンタクスハイライトが、一部ダメになっちゃってますね。うまくイメージが伝わるといいのですが…。

この `_Generic` の機能の主な用途には以下があります。

- 型に基づく操作の最適化
- 関数オーバーロードの代用

<br>

以上を持ちまして、10個のアンダースコアではじまるキーワードの紹介は終わりです。

# まとめ
あまり馴染みがないものもあったかと思いますが、いかがでしたでしょうか。簡単にでも触れてみると、「なんだ、どうってこと無かったな…」って気もしますね。

最後に、本記事で紹介したキーワードについて、別途標準ライブラリのヘッダをインクルードすることにより、アンダースコア `_` なしの定義が使えるものについて、一覧に残しておきます。

|キーワード |標準ヘッダ |代替マクロ定義 |
|---|---|---|
|`_Bool` | `stdbool.h` | `bool` |
|`_Complex` | `complex.h` | `complex` |
|`_Imaginary` | `complex.h` | `imaginary`:unicorn: |
|`_Atomic` |なし  |なし |
|`_Thread_local` |`threads.h` | `thread_local` |
|`_Alignof` |`stdalign.h` | `alignof` |
|`_Alignas` |`stdalign.h` | `alignas` |
|`_Noreturn` |`stdnoreturn.h` | `noreturn` |
|`_Static_assert` |`assert.h`  | `static_assert` |
|`_Generic` |なし  |なし |

互換性に問題がない場合は、やはりなるべくアンダースコアのない方が**コードの可読性**にとっては良さそうですね。

:::note
実は、C言語の"最新"の標準であるC23におきましては、本記事掲載の10個以外にも、アンダースコアではじまるキーワードが、いくつか追加されております。

> `_Decimal32`、`_Decimal64`、`_Decimal128`、`_BitInt`

……が、本記事では紙面の都合で割愛させていただきました。(キリが悪そうだったもので、すいませーん🙇‍♂️)
:::

ここまで、お読みいただきありがとうございました。大変お疲れ様でした！！

# おわりに
筆者なりに「実例」に触れながら、10個のキーワードをカミ砕いてみました。何かのお役に立てられましたら幸いです。

![ganen.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2653390/232beda0-6153-7761-b42b-f6ac3966125e.png)



# 関連記事
C言語について、他の記事も書いております。良かったら読んでみてください。


https://qiita.com/y-tetsu/items/d839baff7b9f7f54704a

- これは知らなかった！これはけしからん！という筆者が選んだトピックをいくつかご紹介しております


https://qiita.com/y-tetsu/items/204fb43c3b9ac91827a0

- C言語を書く上で絶対やってはいけない「未定義動作」をいくつかご紹介しております