【埋め込み式】C言語高度プログラミング-文式(03)


00.目次
文書ディレクトリ
  • 00. ディレクトリ
  • 01. C言語の式
  • 02. C言語の文
  • 03. C言語のコードブロック
  • 04. C言語における文式
  • 05. マクロで使用される文式
  • 06. Linuxカーネル応用例
  • 07. 付録
  • 01.C言語の表現
    式と文はC言語の基礎概念である.式とは何ですか.式は一連のオペレータとオペランドからなる式である.オペレータは、C言語標準で規定された各種算術演算子、論理演算子、賦値演算子、比較演算子などであってもよい.オペランドは定数でも変数でも構いません.エクスプレッションにはオペレータがなくても、単独の定数、さらには文字列であり、エクスプレッションでもあります.次の文字列は式です.
    22 + 33
    
    22
        
    a = a + b
    
    i++
        
    "hello world"
    

    式は一般的にデータ計算や何らかの機能を実現するアルゴリズムに用いられる.式には、値とタイプの2つの基本プロパティがあります.上記の式2+3のように、その値は5です.オペレータによって、式は次のようなさまざまなタイプに分けられます.
  • 関係式
  • 論理式
  • 条件式
  • 付与式
  • 算術式
  • などなど.
    02.C言語の文
    文はプログラムを構成する基本単位であり、一般的な形式は以下の通りである:式;
    **式の後に1つ追加します.基本的な文を構成します.**コンパイラは、プログラム、解析プログラムをコンパイルする際に、物理行ではなく、セミコロンに基づいています.文の終了フラグを判断します.例えばi=2+3;この文は、次の形式でコンパイルすることもできます.
    #include 
    
    int main(void)
    {
        int i = 0;
    
        //      
        i
        =
        1
        +
        2
        ;
    
        return 0;
    }
    

    03.C言語のコードブロック
    異なる文は、括弧{}で囲まれ、コードブロックを構成します.C言語はコードブロックに変数を定義することを許可し、この変数の役割ドメインもこのコードブロック内に限られている.コンパイラは{}に基づいてスタックアウトスタック操作を行い、変数の役割ドメインを管理するからである.次の手順に従います.
    プログラムの例
    #include 
    
    int main(void)
    {
        int var = 3;
    
        printf("var = %d
    "
    , var); // { int var = 88; printf(" var = %d
    "
    , var); } printf("var = %d
    "
    , var); return 0; }

    実行結果
    deng@itcast:~/tmp$ gcc 5hello.c  
    deng@itcast:~/tmp$ ./a.out  
    var = 3
        var = 88
    var = 3
    

    04.C言語の文式
    GNU CはC標準を拡張し、1つの式に文を埋め込むことを許可し、式の内部で局所変数、forループ、gotoジャンプ文を使用することを許可した.このような式を文式と呼ぶ.文式の形式は次のとおりです.
    ({    1;    2;    3; ...    n;})
    

    文式の一番外側は括弧()で囲まれており、中の一対の括弧{}がコードブロックで囲まれており、コードブロックには様々な文が埋め込まれています.文の形式は「式;このような一般的な形式の文は、ループ、ジャンプなどの文であってもよい.
    **一般式と同様に、文式にも独自の値があります.文式の値は、埋め込み文の最後の式の値です.**例を挙げて、文式を使用して値を求めます.
    プログラムの例
    #include 
    
    int main(void)
    {
        int sum = 0;
    
    
        sum = 
        ({
            int sum = 0;
    
            for (int i = 0; i < 100; i++)
            {
                sum = sum + i;
            }
    
            sum;
    
        });
    
        printf("sum = %d
    "
    , sum); return 0; }

    実行結果
    deng@itcast:~/tmp$ gcc 6.c  
    deng@itcast:~/tmp$ ./a.out  
    sum = 4950
    

    上記のプログラムでは、文式の値が最後の式の値に等しいため、forループの後にsumを追加します.文は文式全体の値を表します.これを付けないとsum=0になります.あるいは、この行の文を100に変更します.文式の値が最後の式の値に等しいため、最後のsumの値は100になります.
    goto文と文式を組み合わせて使用
    文式ではgotoを使用してジャンプすることもできます.
    プログラムの例
    #include 
    
    int main(void)
    {
        int sum = 0;
    
    
        sum = 
        ({
            int sum = 0;
    
            for (int i = 0; i < 100; i++)
            {
                sum = sum + i;
    
                if (i == 10)
                    goto loop;
            }
    
            sum;
    
        });
    
        printf("sum = %d
    "
    , sum); loop: printf("goto loop
    "
    ); printf("sum = %d
    "
    , sum); return 0; }

    実行結果
    deng@itcast:~/tmp$ gcc 6.c  
    deng@itcast:~/tmp$ ./a.out  
    goto loop
    sum = 0
    

    05.マクロでの文式の使用
    文式のハイライトは、複雑な機能を定義するマクロです.文式を使用してマクロを定義すると、複雑な機能を実現するだけでなく、マクロ定義による曖昧さや脆弱性を回避できます.次に、マクロ定義の例を示します.マクロ定義における文式の適用を見てみましょう.
    面接の過程で、面接官から質問がありました.
    マクロを定義して、2つの数の最大値を求めてください.
    このような簡単な試験問題を見ないでください.面接官はあなたが書いたマクロに基づいて、あなたのC言語の基礎を判断して、Offerをあげるかどうかを決めることができます.
    しょきプログラマ
    C言語を学んだ学生にとって、このマクロを書くのは基本的に難しいことではありません.条件演算子を使うと完成します.
    #define  MAX(x, y)  x > y ? x : y
    

    これは最も基本的なC言語文法で、これさえ書けないと、場面が気まずいと思います.面接官は気まずい思いを和らげるために、普通はあなたに言います:若者、あなたはとても良くて、帰ってニュースを待って、ニュースがあって、私达はあなたに知らせます!この时、あなたは理解するべきです:もう待つ必要はありませんて、急いでこの文章を読んで、それから家に会います.このマクロは書くことができて、あなたがとても牛Xだと思わないでください.これはあなたがC言語の基礎を持っていることを説明するしかありませんが、まだ大きな進歩の空間があります.
    実は上の書き方は文法的には問題ありませんが、実際にはバグがあります.
    プログラムの例
    #include 
    
    #define MAX(x, y) x > y ? x : y
    
    int main(void)
    {
        printf("Max = %d
    "
    , MAX(1, 2)); printf("Max = %d
    "
    , MAX(2, 1)); printf("Max = %d
    "
    , MAX(2, 2)); printf("Max = %d
    "
    , MAX(1 != 1, 1 != 2)); return 0; }

    実行結果
    deng@itcast:~/tmp$ gcc 6.c  
    deng@itcast:~/tmp$ ./a.out  
    Max = 2
    Max = 2
    Max = 2
    Max = 0
    

    テストプログラムの過程で、私たちは必ずいろいろな可能性のある状況を測定しなければなりません.10行目の文をテストします.マクロのパラメータが式である場合、実際の実行結果はMax=0であり、予想された結果Max=1とは異なります.これは、宏展が開かれると、このようになったからです.
    printf("Max = %d
    "
    , 1 != 1 > 1 != 2 ? 1 != 1 : 1 != 2);

    比較演算子>の優先度が6なので、より大きい!=(優先度は7)なので、展開された式は演算順序が変わり、結果は私たちの予想とは違います.このような展開エラーを回避するために,マクロのパラメータに括弧()を付けて展開後に式の演算順序が変化することを防止することができる.このようなマクロは、適切なマクロとして計算されます.
    #define  MAX(x, y)  (x) > (y) ? (x) : (y)
    

    プログラムの例
    #include 
    
    #define MAX(x, y) (x) > (y) ? (x) : (y)
    
    int main(void)
    {
        printf("Max = %d
    "
    , MAX(1, 2)); printf("Max = %d
    "
    , MAX(2, 1)); printf("Max = %d
    "
    , MAX(2, 2)); printf("Max = %d
    "
    , MAX(1 != 1, 1 != 2)); return 0; }

    実行結果
    deng@itcast:~/tmp$ gcc 6.c  
    deng@itcast:~/tmp$ ./a.out  
    Max = 2
    Max = 2
    Max = 2
    Max = 1
    

    上のマクロは、合格としか言えませんが、脆弱性があります.たとえば、次のコードテストを使用します.
    プログラムの例
    #include 
    
    #define MAX(x, y) (x) > (y) ? (x) : (y)
    
    int main(void)
    {
        printf("max = %d
    "
    , 3 + MAX(1, 2)); return 0; }

    テスト結果
    deng@itcast:~/tmp$ gcc 6.c  
    deng@itcast:~/tmp$ ./a.out  
    max = 1
    

    プログラムでは、式3+MAX(1,2)の値を印刷し、予想結果は5であるはずですが、実際の実行結果は1です.私たちが展開した後、同じように問題があることに気づきました.
    3 + (1) > (2) ? (1) : (2);
    

    演算子+の優先度が比較演算子>より大きいため、この式は4>2になりますか?1:2、結局1になったのもおかしくない.このマクロを変更し続ける必要があります.
    #define MAX(x,y) ((x) > (y) ? (x) : (y))
    

    カッコを使用してマクロ定義をパッケージ化することで、1つの式にマクロ定義と他の優先度の高い演算子が同時に含まれる場合、式全体の演算順序が破壊されることを回避できます.もしあなたがこの一歩を書くことができたら、前の面接の同級生より強いことを説明して、前の同級生はもう帰ってニュースを待っています.
    ミドルプログラマー
    上のマクロでは、演算子の優先度による問題は解決されていますが、一定の脆弱性があります.たとえば、定義したマクロをテストするには、次のテストプログラムを使用します.
    プログラムの例
    #include 
    
    #define MAX(x, y) ((x) > (y) ? (x) : (y))
    
    int main(void)
    {
        int i = 2;
        int j = 6;
    
        printf("max = %d
    "
    , MAX(i++, j++)); return 0; }

    実行結果
    deng@itcast:~/tmp$ vim 6.c  
    deng@itcast:~/tmp$ gcc 6.c  
    deng@itcast:~/tmp$ ./a.out  
    max = 7
    

    プログラムでは、2つの変数iとjを定義し、2つの変数のサイズを比較し、自己増加演算を行います.実際の実行結果はmax=7であり、予想結果max=6ではないことが分かった.これは、変数iとjがマクロ展開後、2回の自己加算演算を行い、iの値が7に印刷されるためである.
    このような状況に遭遇したら、どうすればいいですか?この時、文の式が登場するはずです.このマクロを定義するには、文式を使用し、iとjの値を一時的に格納して比較する2つの一時変数を文式で定義し、2回の自己増加、自己減少の問題を回避できます.
    プログラムの例
    #include 
    
    #define MAX(x, y) ({    \
        int _x = x;         \
        int _y = y;         \
        _x > _y ? _x : _y;  \
        })
    
    
    int main(void)
    {
        int i = 2;
        int j = 6;
    
        printf("max = %d
    "
    , MAX(i++, j++)); return 0; }

    実行結果
    deng@itcast:~/tmp$ gcc 6.c  
    deng@itcast:~/tmp$ ./a.out  
    max = 6
    

    文式では、2つのローカル変数を定義しました.x、_yは、マクロパラメータxおよびyの値を格納し、xと_yでサイズを比較することで,iとjによる2回の自己増加演算の問題を回避できる.
    上級プログラマー
    上記のマクロでは,我々が定義した2つの一時変数データ型はint型であり,2つの整数型のデータしか比較できない.他のタイプのデータに対しては、マクロを再定義する必要があります.これは面倒です.上記のマクロに基づいて、任意のタイプのデータ比較サイズをサポートできるように変更を続行できます.
    プログラムの例
    #include 
    
    #define MAX(type, x, y) ({    \
        type _x = x;        \
        type _y = y;         \
        _x > _y ? _x : _y;  \
        })
    
    
    int main(void)
    {
        int i = 2;
        int j = 6;
    
        printf("max = %d
    "
    , MAX(int, i++, j++)); printf("max = %lf
    "
    , MAX(float, 3.33, 4.44)); return 0; }

    実行結果
    deng@itcast:~/tmp$ ./a.out  
    max = 6
    max = 4.440000
    

    このマクロでは、一時変数_を指定するためのパラメータ:typeを追加します.xと_yのタイプ.このように,2つの数の大きい時間を比較し,2つのデータのタイプをパラメータとしてマクロに渡すだけで,任意のタイプのデータを比較することができる.
    上のマクロ定義では、異なるデータ型を互換化するためにtypeタイプパラメータを追加しました.実は、私たちはもっとすごい文法を持っています.typeofはGNU Cに追加されたキーワードで、データ型を取得するために使用されています.私たちは参加する必要はありません.typeofを直接取得させます.
    プログラムの例
    #include 
    
    #define MAX(x, y) ({    \
        typeof(x) _x = x;   \
        typeof(y) _y = y;   \
        (void)(&_x == &_y);  \
        _x > _y ? _x : _y;  \
        })
    
    
    int main(void)
    {
        int i = 2;
        int j = 6;
    
    
        printf("max = %d
    "
    , MAX(i++, j++)); printf("max = %lf
    "
    , MAX(3.33, 4.44)); return 0; }

    実行結果
    deng@itcast:~/tmp$ gcc 6.c  
    deng@itcast:~/tmp$ ./a.out  
    max = 6
    max = 4.440000
    

    このマクロ定義では、typeofキーワードを使用してマクロの2つのパラメータタイプを取得します.乾物は**(void)(&x=&y);**この言葉は、まるで天才的なデザイン!1つは、ユーザーにエラーを提示するために使用され、異なるタイプのポインタを比較すると、コンパイラはエラーを与え、2つのデータ型が異なることを提示します.二つ目は、2つの値を比較すると、比較の結果が使用されず、一部のコンパイラはwarningを与え、(void)を加えると、この警告を解消することができます.
    06.Linuxカーネル応用例
    Linuxカーネルには文式が非常に多く使用されています.
    #define min(x,y) ({ \
        typeof(x) _x = (x); \
        typeof(y) _y = (y); \
        (void) (&_x == &_y);    \
        _x < _y ? _x : _y; })
    
    #define max(x,y) ({ \
        typeof(x) _x = (x); \
        typeof(y) _y = (y); \
        (void) (&_x == &_y);    \
        _x > _y ? _x : _y; })
    
    #define min_t(type, a, b) min(((type) a), ((type) b))
    #define max_t(type, a, b) max(((type) a), ((type) b))
    

    07.付録