0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

#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
  1. 正確には、C99標準は、言語標準のpragmaを除いて、処理系定義の#pragma指令をマクロ展開で構築することを許可している。

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

0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?