TENKAIKEN Storehouse

Contents ♪

C言語プリプロセッサ 2024.07.11
#include ファイル内容の取り込み
#include <stdio.h>
#include <sys/time.h>
#include "mydef.h"

int main()
{
    struct timeval t;
    gettimeofday(&t, NULL);
    printf("hello %lu\n", t.tv_sec);
}
<~>はコンパイラのヘッダファイルインストール場所からの相対パス、"~"はソースファイルの位置からの相対パスを指定する。
C言語プリプロセッサ 2024.07.12
#include C++のヘッダファイルとstd名前空間
C++の標準ライブラリ関数はstd名前空間に属しており、それらを定義するヘッダファイルには「.h」が付かない。
#include <iostream>

int main()
{
    std::cout << "Hello" << std::endl;
    return 0;
}
C++98で標準ライブラリをstd名前空間に属するものと定め、「.h」の付かないヘッダファイルを導入した。
C言語プリプロセッサ 2024.07.11
#include C++のヘッダファイル(C言語互換)
C++C言語のヘッダファイルをincludeできるが、それらは全てグローバル名前空間での定義となる。C++では、それらをstd名前空間内に定義しなおしたヘッダファイルを使うことが推奨されている。それらはC言語のヘッダファイル名の「.h」を取り除き、頭文字にcを付けた名前になっている。
#include <cstdio>
#include <cstring>

int main()
{
    std::printf("Hello\n");
    char buf[20];
    std::strcpy(buf, "World");
    std::puts(buf);
    return 0;
}
 実際はstd::は付けなくてもよい。C言語との互換性のためにグローバル名前空間としても定義されている。
C言語プリプロセッサ 2024.07.11
#define 定義だけのマクロ
値を定義しないで定義しただけのマクロは、何もない文字に置き換わる。主に#ifdefなどで定義が存在するかどうかでの判断条件になる。
#include <stdio.h>
#define DEBUG

#ifdef DEBUG
    #define ERR "Error "
#else
    #define ERR
#endif

int main()
{
    printf(ERR"Hello\n");
    return 0;
}
Error Hello
DEBUGのdefine行を削除すると、表示は「Error」が消えて「Hello」だけになる。
C言語プリプロセッサ 2024.07.13
#define マクロ定数定義
マクロ定数は、大文字にする習慣があるが文法的な強制ではない。
マクロ定数の定義の中に、前出のマクロが含まれていてもよい。
#define VERSION         "Ver 2.5.10"
#define BUFSIZE         (1024) 
#define MAX_BUFSIZE     (BUFSIZE*128) 
#define MIN_INT         -(((__int64)1<<30))
#define PI              (3.1416)
#define True            1
#define False           0
特に演算が含まれる数値に展開するマクロ定義は(~)で囲むことが勧められる。数式の中に展開されたとき、周囲との演算の優先順位が変わるかもしれないからである。マクロは単純な置換なので、マクロ定義中の演算結果に置き換わるわけではない。
C言語プリプロセッサ 2024.07.11
#define マクロ関数
#defineのマクロ関数の引数は、マクロ展開が可能ならばデータの形式は問わない。
#include <stdio.h>
#include <math.h>

#define POW2(x)         (int)pow(x,2)
#define IFGT(x,y,z)     if(x>y) z=1;else z=0;
#define ARR(a,b)        a[b]
#define swap16(x)       ((x>>8)&0xff)+(x<<8)
#define ERRMSG(msg)     printf("Error: %s\n", msg)
#define CHKEQ(a,b)      if(a!=b) goto end;

int main()
{
    int a, b;
    unsigned short c = 0xaabb;
    char *er[] = {"syntax", "exception"};

    c = swap16(c);
    printf("%x\n", c);
    a = POW2(4);
    IFGT(a, 10, b)
    CHKEQ(b, 0)
end:
    ERRMSG(ARR(er, 1));
    return 0;
}
bbaa
Error: exception
マクロ関数になにも定義しない場合は、空の定義となり適用箇所は空に置換される(その部分が消える)。
#ifdef DEBUG
    #define LOG_PRINT(x) printf("trace: %s\n", x)
#else
    #define LOG_PRINT(x)
#endif
C言語プリプロセッサ 2024.07.11
#define 複数行のマクロ定義
長い定義、複数行の定義を行末で「\」で折り返すことができる。
#include <stdio.h>
#include <stdlib.h>

#define ASSERT_IFNULL(x)                \
    do {                                \
        if (!(x)) {                     \
            printf("NULL %s %d\n",      \
                __FUNCTION__, __LINE__);\
            exit(0);                    \
        }                               \
    } while (0)


int main()
{
    void *p = NULL;
    ASSERT_IFNULL(p);
    return 0;
}
NULL main 17
#define VLEVEL  (LVLWARN | LVLINFO | \
    LVLERR)
C言語プリプロセッサ 2024.07.11
#define 可変長引数のマクロ関数
マクロ関数の可変長引数はC99で導入された。「...」の0個以上の引数は__VA_ARGS__の箇所に置き換わる。
#include <stdio.h>

#define DEBUG1(fmt, ...)    printf("LOG: " fmt "\n", __VA_ARGS__)
#define DEBUG2(...)         printf("LOG: " __VA_ARGS__)
// GNU extension
#define DEBUG3(fmt, ...)    printf("LOG: " fmt, ##__VA_ARGS__)
#define DEBUG4(args...)     printf("LOG: " args)

int main()
{
    DEBUG1("error code %d %s", 100, "syntax");
    DEBUG2("error code %d %s\n", 200, "null pointer");
    DEBUG3("error code 300\n");
    DEBUG4("error code %d %s\n", 400, "unexpected");
    return 0;
}
LOG: error code 100 syntax
LOG: error code 200 null pointer
LOG: error code 300
LOG: error code 400 unexpected
DEBUG3、DEBUG4はGNU拡張である。
##__VA_ARGS__は、可変長引数が何も指定されず0個の場合に、直前の余計に残る「,」を削除する。GNU拡張を使わないならば、DEBUG2のように「fmt」相当の文字列も可変長引数の中に含めればよい。
DEBUG4は、可変長部分の名前を任意に付けている。※
GCCがC99前から使っていた方法。
C言語プリプロセッサ 2024.07.11
#A マクロ関数引数を文字列化
#はマクロ引数をクォーテーションで囲って文字列定数化する。このとき、その引数が変数やほかのマクロ定数であっても、その内容が持つ値は解釈しない。単純にマクロの引数に書いてあるものを"~"で囲むだけである。
#include <stdio.h>

#define err(n)      puts("error code=" #n)

int main()
{
    int a = 200;
    err(100);
    err(a);
    return 0;
}
error code=100
error code=a
C言語プリプロセッサ 2024.07.11
A ## B トークン連結
##は、前後のトークンを連結して置き換える。前後の連結なので、##を定義の先頭や末尾に配置できない。
#include <stdio.h>

#define LOG(f1, f2, s)      f1##_##f2(s)

void stdo_log(char *msg)
{
    puts(msg);
}

int main()
{
    LOG(stdo, log, "Hello");
    return 0;
}
##がトークンを連結するときの手順は、
① ##の前後をつなぐ
② その結果が更にマクロ展開できる部分があるなら展開する
なので、次のVERSIONは210になることを期待したいが「MJMN」になる。
#define MJ  2
#define MN  10
#devine VERSION     MJ##MN
C言語プリプロセッサ 2024.07.11
#if #elif #else #endif 条件付きコンパイル
#ifや#elifの条件式は、
① 評価が0なら区間をコンパイルしない、0以外ならする。
② 右辺が整数定数になる比較演算と論理演算ができる。
#include <stdio.h>

#define ENABLED     10
#define VER         350

int main()
{
#if ENABLED
    printf("Enabled\n");
#endif
#if VER >= 400
    printf("version %4.2f\n", ((double)VER)/100);
#elif VER < 400 && VER >= 350
    printf("version %4.2f\n", ((double)VER)/100);
#elif VER == 100
    printf("version 1.00\n");
#else
    printf("beta %3.2f\n", ((double)VER)/100);
#endif
    return 0;
}
C言語プリプロセッサ 2024.07.11
#indef #ifndef 定義で判断する条件コンパイル
#ifdef、#ifndefは、続くマクロの定義が存在するかどうかでコンパイル可否を決める。#ifのような0と非0の判断ではなく、どんな設定値でも設定値がなくても定義があるかないかで判断する。定義は#defineによるもの以外に、コンパイルオプションの-Dで指定される場合がある。
#include <stdio.h>

#define ENABLE

int main()
{
#ifdef ENABLE
    printf("Enable\n");
#endif
#ifndef DISABLE
    printf("not Disable\n");
#endif
    return 0;
}
$ gcc -o test -DDEBUG test.c
$ ./test

Enable
Debug
not Disable
C言語プリプロセッサ 2024.07.11
#if defined 条件付きコンパイル
defined(マクロ)は、マクロ定義が存在する場合に0以外、定義が存在しない場合に0を返し、!define()はその否定を返す。#if defined()、#if !defined()は、#ifdefと#ifndefの代替になる。
#include <stdio.h>

#define ENABLE

int main()
{
#if defined(ENABLE)
    printf("Enable\n");
#endif
#if !defined(DISABLE)
    printf("not Disable\n");
#endif
    return 0;
}
defined()のマクロ名は()を使わずにdefinedに続けて書いてもよい。
#if defined ENABLE
#endif
#elif !defined DISABLE
#endif
C言語プリプロセッサ 2024.07.11
#undef マクロ定義消去~再定義
#undefはすでに存在するマクロ定義を未定義にする。存在しない定義の場合は無視する。ヘッダファイルの中で既に定義されている可能性のあるマクロは#undefで破棄してから再定義する。
#include <stdio.h>
#include <string.h>

#define BUFSIZE     256
#undef BUFSIZE      
#define BUFSIZE     16

int main()
{
#define CPY(a, b)       memcpy(a, b, sizeof(a))
    char buf[BUFSIZE];
    CPY(buf, "Hello");
#undef CPY
#define CPY(a, b)       strcpy(a, b)
    CPY(buf, "Hello");
    return 0;
}
C言語プリプロセッサ 2024.07.11
__FILE__ __LINE__ など 定義済みマクロ定数
それぞれ、__FILE__(ファイル名)、__LINE__(行番号)、__func__(現在の関数名)を格納している。
__DATE__と__TIME__は「コンパイル時」の日付時刻である(実行時ではない)。
#include <stdio.h>

#define TRACE() \
    printf("trace %s %s (%d)\n", \
        __FILE__, __func__, __LINE__);


void the_func()
{
    TRACE()
}

int main()
{
    printf("update %s %s\n", __DATE__, __TIME__);
    the_func();
    TRACE()
    return 0;
}
update Jul 13 2024 18:50:26
trace test04.c the_func (10)
trace test04.c main (17)
__func__はC90から導入された。
C言語プリプロセッサ 2024.07.11
__STDC__ __STDC_VERSION__ Cバージョンを示す定数
__STDC__はANSI準拠なら1を返す。
__STDC_VERSION__は準拠バージョンによって以下のlong整数に置き換わる。
C95 = 199409L
C99 = 199901L
C11 = 201112L
C17 = 201710L
コンパイルオプションに-std=c11のように指定すると__STDC_VERSION__がそれに応じた値になる。
#include <stdio.h>

int main()
{
#if __STDC__
    printf("This is ANSI C %ld\n", __STDC_VERSION__);
#endif
    return 0;
}
This is ANSI C 201710 
C言語プリプロセッサ 2024.07.11
#line 行番号/ファイル名の途中変更
#lineは行番号とファイル名を変更する。
#line 新行番号 [新ファイル名]
新ファイル名を省略した場合は、行番号のみ変更する。#lineが現れた時点のソースコードから下について変更が適用される。
#include <stdio.h>

void the_func()
{
    printf("%s %d\n", __FILE__, __LINE__);
}

int main()
{
the_func();
#line 100 "other.c"
    printf("%s %d\n", __FILE__, __LINE__);
the_func();
    return 0;
}
test.c 5
other.c 100
test.c 5
C言語プリプロセッサ 2024.07.11
#error #warning エラー中断と警告
#errorはその場でエラーとしてコンパイルを中断し、#warningは警告を表示してコンパイルを続行する。どちらも続けてエラーや警告の内容を書くことができる。
#include <stdio.h>

#define VER 90

int main()
{
#ifndef VER
#error version not set
#elif VER < 100
#warning beta version
#endif
    printf("Hello\n");
    return 0;
}
C言語プリプロセッサ 2024.07.11
重複include防止
同じヘッダファイルを重複してincludeすることを防止できる。
#ifndef HDRNAME_H
#define HDRNAME_H

struct ST {
};

#endif /* HDRNAME_H */
C言語プリプロセッサ 2024.07.11
#pragma once 重複include防止
ヘッダファイルの先頭に#pragma onceを置くと、そのヘッダが多重にincludeされないようになる。
#pragma once

struct ST {
};
C言語プリプロセッサ 2024.07.11
#if 0 コメントアウト
#if 0~#endifの間はコンパイルされないので、無効にしたい区間のコメントアウトになる。#if 1にすると有効になるので#elseと組み合わせて使いたい区間を交互に切り替えることができる。
#include <stdio.h>

int main()
{
#if 0
This software may be distributed under the terms of the BSD license.
See README for more details.
#else
    printf("Hello\n");
#endif
    return 0;
}
C言語プリプロセッサ 2024.07.11
#pragma pack 構造体アライメント調整
#pragmaのpack()は、構造体のアライメントのバイト数を変更できる。pack(1)とした場合、構造体の定義をバイト単位で直列化した状態でメモリに配置される。
#pragma pack(境界バイト数)
#include <stdio.h>

#pragma pack(1)
struct ST1 {
    char buf[13];
    int a;
};

#pragma pack(4)
struct ST2 {
    char buf[13];
    int a;
};

int main()
{
    printf("pack 1 size=%lu\n", sizeof(struct ST1));
    printf("pack 4 size=%lu\n", sizeof(struct ST2));
    return 0;
}
pack 1 size=17
pack 4 size=20
また、pack()にpushとpopを指定することで一時的に変更してデフォルトに戻す操作ができる。
#pragma pack(push, 境界バイト数)
#pragma pack(pop)       デフォルトに戻す
#include <stdio.h>

#pragma pack(push, 1)
struct ST1 {
    char buf[13];
    int a;
};

#pragma pack(pop)
struct ST2 {
    char buf[13];
    int a;
};

int main()
{
    printf("pack 1 size=%lu\n", sizeof(struct ST1));
    printf("pack 4 size=%lu\n", sizeof(struct ST2));
    return 0;
}
この例ではデフォルトのアライメントはintの4バイトになる