#pragma
? _Pragma
?
C処理系において、コンパイラに追加情報を提供するために#pragma
指令を使うことができる。例えば、構造体メンバのメモリアラインメントのパディングを無くすための #pragma pack(1)
や、インクルードガードの #pragma once
等だ。#pragma
指令の多くは処理系依存である。
#pragma
に似たキーワードで、C99で導入された_Pragma
演算子がある。備忘録も兼ねて、#pragma
と_Pragma
の関係、および_Pragma
の使い方とそのユースケースを述べる。
_Pragma
の書き方
まずは、シンタックス、セマンティクスが一目で分かるよう、ごく簡単な例を示す。以下の二つは同義である。
_Pragma("pack(1)")
#pragma pack(1)
実際にgcc
, clang
ではプリプロセッサにより前者は後者に変換される。
$ 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>"
$ 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。
以下は、_Pragma
とマクロ展開を用いて#pragma
指令を行うサンプルコードである。構造体メンバのアラインメントサイズを引数で指定できるマクロ ALIGN(n)
を定義している。
#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()
実行可能な全文と実行例
コード
#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)
$ 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