_Pragmaの使い方とユースケース [C言語] 

# `#pragma`? `_Pragma`?
C処理系において、コンパイラに追加情報を提供するために`#pragma`指令を使うことができる。例えば、構造体メンバのメモリアラインメントのパディングを無くすための `#pragma pack(1)` や、インクルードガードの `#pragma once` 等だ。`#pragma`指令の多くは処理系依存である。

`#pragma`に似たキーワードで、C99で導入された`_Pragma`演算子がある。備忘録も兼ねて、`#pragma`と`_Pragma`の関係、および`_Pragma`の使い方とそのユースケースを述べる。

## `_Pragma`の書き方

まずは、シンタックス、セマンティクスが一目で分かるよう、ごく簡単な例を示す。以下の二つは同義である。

```c
_Pragma("pack(1)")
```

```c
#pragma pack(1)
```

実際に`gcc`, `clang` ではプリプロセッサにより前者は後者に変換される。

```console
$ echo '_Pragma("pack(1)")' | gcc -E -
# 0 "<stdin>"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 0 "<command-line>" 2
# 1 "<stdin>"

# 1 "<stdin>"
#pragma pack(1)
# 1 "<stdin>"
```

```console
$ echo '_Pragma("pack(1)")' | clang -E -
# 1 "<stdin>"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 361 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "<stdin>" 2
#pragma pack(1)
```

`_Pragma`演算子は、オペランドとして文字列リテラルを取る。このオペランドを「文字列解除 (destringize)」し、上記例のように`#pragma`指令を生成する。

オペランドの文字列解除は、以下のルールで行われる。
* L接頭辞を削除
* 文字列を囲む引用符を削除
* `\"` を `"` に変更 (エスケープ解除)
* `\\` を `\` に変更 (エスケープ解除)


## `_Pragma`のユースケース

`#pragma`を使えば済むところを、なぜわざわざ言語仕様として`_Pragma`を導入したのか?
多くの処理系において、マクロ展開を用いて`#pragma`指令を直接構築することができない[^1]。
この課題への対応として、`_Pragma`演算子を用いることで、`#pragma`指令がマクロで定義可能となる。

逆に、マクロを用いるまでも無いケースで、`_Pragma` 単独で使うメリットは無いだろう[^2]。

[^1]: 正確には、C99標準は、言語標準のpragmaを除いて、処理系定義の`#pragma`指令をマクロ展開で構築することを許可している。

[^2]: 例えば、インクルードガードの `#pragma once` は `#pragma once` のままで十分だろう。もちろん、`#define INCLUDE_GUARD _Pragma("once")` のようにすれば、pragmaを用いたインクルードガードを知らなかった人にとっては可読性が向上するし、条件コンパイル (`#pragma once` は主要な処理系でサポートされるが処理系依存である) も適用しやすくなるので、悪くは無い。

以下は、`_Pragma`とマクロ展開を用いて`#pragma`指令を行うサンプルコードである。構造体メンバのアラインメントサイズを引数で指定できるマクロ `ALIGN(n)` を定義している。

```c
#define PRAGMA(x) _Pragma(#x)   // e.g. PRAGMA(foo) -> _Pragma("foo")
#define ALIGN(n) PRAGMA(pack(n))
#define ALIGN_RESET PRAGMA(pack())

// 以下のように書くことはできない
// #define ALIGN(n) #pragma pack(n) // error: '#' is not followed by a macro parameter

ALIGN(1)    // -> PRAGMA(pack(1)) -> _Pragma("pack(1)") -> #pragma pack(1)
typedef struct {
    uint8_t u8;
    uint32_t u32;
    uint16_t u16;
} packed;
ALIGN_RESET // -> PRAGMA(pack()) -> _Pragma("pack()") -> #pragma pack()

ALIGN(2)    // -> PRAGMA(pack(2)) -> _Pragma("pack(2)") -> #pragma pack(2)
typedef struct {
    uint8_t u8;
    uint32_t u32;
    uint16_t u16;
} align2;
ALIGN_RESET // -> PRAGMA(pack()) -> _Pragma("pack()") -> #pragma pack()
```

## 実行可能な全文と実行例

### コード

```c
#include <stdio.h>
#include <stdint.h>
#include <stddef.h> // for offsetof

#define PRAGMA(x) _Pragma(#x)   // e.g. PRAGMA(foo) -> _Pragma("foo")
#define ALIGN(n) PRAGMA(pack(n))
#define ALIGN_RESET PRAGMA(pack())

// 以下のように書くことはできない
// #define ALIGN(n) #pragma pack(n) // error: '#' is not followed by a macro parameter

typedef struct {
    uint8_t u8;
    uint32_t u32;
    uint16_t u16;
} normal;

ALIGN(1)    // -> PRAGMA(pack(1)) -> _Pragma("pack(1)") -> #pragma pack(1)
typedef struct {
    uint8_t u8;
    uint32_t u32;
    uint16_t u16;
} packed;
ALIGN_RESET // -> PRAGMA(pack()) -> _Pragma("pack()") -> #pragma pack()

ALIGN(2)    // -> PRAGMA(pack(2)) -> _Pragma("pack(2)") -> #pragma pack(2)
typedef struct {
    uint8_t u8;
    uint32_t u32;
    uint16_t u16;
} align2;
ALIGN_RESET // -> PRAGMA(pack()) -> _Pragma("pack()") -> #pragma pack()

int main() {
    printf("normal offset of u8: %zu\n", offsetof(normal, u8)); 
    printf("normal offset of u32: %zu\n", offsetof(normal, u32)); 
    printf("normal offset of u16: %zu\n", offsetof(normal, u16)); 
    printf("size of normal struct: %zu\n", sizeof(normal)); 
    puts("");

    printf("packed offset of u8: %zu\n", offsetof(packed, u8)); 
    printf("packed offset of u32: %zu\n", offsetof(packed, u32)); 
    printf("packed offset of u16: %zu\n", offsetof(packed, u16)); 
    printf("size of packed struct: %zu\n", sizeof(packed)); 
    puts("");

    printf("align2 offset of u8: %zu\n", offsetof(align2, u8)); 
    printf("align2 offset of u32: %zu\n", offsetof(align2, u32)); 
    printf("align2 offset of u16: %zu\n", offsetof(align2, u16)); 
    printf("size of align2 struct: %zu\n", sizeof(align2)); 

    return 0;
}
```

### 実行例 (gcc on GNU/Linux x86_64)

```console
$ gcc -std=c99 pragma.c && ./a.out
normal offset of u8: 0
normal offset of u32: 4
normal offset of u16: 8
size of normal struct: 12

packed offset of u8: 0
packed offset of u32: 1
packed offset of u16: 5
size of packed struct: 7

align2 offset of u8: 0
align2 offset of u32: 2
align2 offset of u16: 6
size of align2 struct: 8
```