C++ コア ガイドライン:C と C++ の混在

C と C++ の歴史は同じであるため、両方の言語は密接に関連しています。どちらも他のサブセットではないため、それらを混在させるにはいくつかのルールを知っておく必要があります。

C++ コア ガイドラインの章は「C スタイルのプログラミング」と呼ばれています。正直なところ、最初は飛ばそうと思っていたのですが、いろいろ考えた末に書くことにしました。私の理由は 2 つあります:

<オール>
  • これは、レガシー コードを扱う際に発生する典型的な問題です。
  • ある読者は、レガシー コードの課題についてもっと書いてほしいと思っていました。
  • 今日の 3 つのルールは次のとおりです。

    • CPL.1:C よりも C++ を好む
    • CPL.2:C を使用する必要がある場合は、C と C++ の共通サブセットを使用し、C コードを C++ としてコンパイルします
    • CPL.3:インターフェイスに C を使用する必要がある場合は、そのようなインターフェイスを使用する呼び出しコードで C++ を使用してください

    C++ コア ガイドラインについて書いているので、最初のルールは明らかです。

    CPL.1:C より C++ を優先

    これ以上苦労することなく、C++ コア ガイドラインからの理由を説明します。

    CPL.2:C を使用する必要がある場合は、C と C++ の共通サブセットを使用し、C コードを C++ としてコンパイルします

    最初に答えなければならない質問は、C++ コンパイラでコード全体をコンパイルできますか?

    ソースコード全体が利用可能

    よし、あと少しで終わりだ。ほとんど、C は C++ のサブセットではないためです。以下は、C++ コンパイラで壊れてしまう小さくて悪い C プログラムです。

    // cStyle.c
    
    #include <stdio.h>
    
    int main(){
    
     double sq2 = sqrt(2); // (1)
     
     printf("\nsizeof(\'a\'): %d\n\n", sizeof('a')); // (2)
     
     char c;
     void* pv = &c;
     int* pi = pv; // (3)
     
     int class = 5; // (4)
     
    }
    

    まず、C90 標準でコンパイルして実行します。

    コンパイルは成功しますが、いくつかの警告が表示されます。

    プログラム cStyle.c にはいくつかの問題があります。 sqrt 関数の宣言はなく (2 行目)、(3 行目) は void ポインターから int ポインターへの暗黙的な変換を実行し、(4 行目) はキーワード class を使用します。

    C++ コンパイラが何を言っているのか見てみましょう。

    3 つのコンパイラ エラーです。 cStyle.c が示すプログラムは、C コンパイラと C++ コンパイラのより微妙な違いを示しています。プログラムを (2) 行に縮小しました:printf("\nsizeof(\'a\'):%d\n\n", sizeof('a'));.これが出力です。

    C コンパイラなどの 4 ではなく、sizeof('a') は C++ コンパイラでは 1 です。 C では 'c' は int です。

    では、よりやりがいのある仕事に取り掛かりましょう。

    ソース コード全体が利用できません

    これらが重要なポイントです。

    <オール>
  • C++ コンパイラを使用してメイン関数をコンパイルします。 C コンパイラとは対照的に、C++ コンパイラは main 関数の前に実行される追加のスタートアップ コードを生成します。たとえば、このスタートアップ コードは、グローバル (静的) オブジェクトのコンストラクターを呼び出します。
  • C++ コンパイラを使用してプログラムをリンクします。 プログラムのリンクに C++ コンパイラを使用すると、標準の C++ ライブラリに自動的にリンクされます。
  • 同じ呼び出し規約を持つ同じベンダーの C および C++ コンパイラを使用します。 呼び出し規約は、関数にアクセスするためにコンパイラが設定するメソッドを指定します。これには、パラメーターが割り当てられる順序、パラメーターが渡される方法、または呼び出し先の呼び出し元がスタックを準備するかどうかが含まれます。ウィキペディアで x86 の呼び出し規約の詳細を確認してください。
  • CPL.3:インターフェイスには C を使用する必要があり、そのようなインターフェイスを使用する呼び出しコードでは C++ を使用してください

    C とは対照的に、C++ は関数のオーバーロードをサポートしています。これは、同じ名前でパラメーターが異なる関数を定義できることを意味します。関数が呼び出されると、コンパイラは適切な関数を選択します。

    // functionOverloading.cpp
    
    #include <iostream> 
     
    void print(int) { 
     std::cout << "int" << std::endl; 
    } 
    
    void print(double) { 
     std::cout << "double" << std::endl; 
    } 
    
    void print(const char*) { 
     std::cout << "const char* " << std::endl; 
    } 
    
    void print(int, double, const char*) { 
     std::cout << "int, double, const char* " << std::endl; 
    } 
    
     
    int main() { 
     
     std::cout << std::endl; 
    
     print(10); 
     print(10.10); 
     print("ten"); 
     print(10, 10.10, "ten");
    
     std::cout << std::endl;
    
    }
    

    出力は期待どおりです。

    C++ コンパイラはどのようにしてさまざまな関数を区別できるのでしょうか? C++ コンパイラは、さらにパラメーターの型を関数名にエンコードします。このプロセスは名前管理と呼ばれ、C++ コンパイラごとに固有です。標準化されていないプロセスは、名前の装飾とも呼ばれます。

    コンパイラ エクスプローラーの functionOverloading.cpp の助けを借りて、マングルされた名前を表示するのは非常に簡単です。 Demangle ボタンを無効にするだけです。

    GCC 8.3 と MSVC 19.16 が生成している名前は次のとおりです。

    extern "C" リンケージ指定子を使用すると、C++ コンパイラが名前をマングリングするのを防ぐことができます。

    コード内で extern "C" を使用して関数を宣言することにより、C++ から C 関数を呼び出すか、C から C++ 関数を呼び出すことができます。

    各関数に extern "C" を使用できます。

    extern "C" void foo(int);
    

    スコープ内の各関数に対して、

    extern "C" {
     void foo(int);
     double bar(double);
    };
    

    または、インクルード ガードを使用してヘッダー ファイル全体に対して。マクロ __cplusplus は、C++ コンパイラが使用されるときに定義されます。

    #ifdef __cplusplus
    extern "C" {
    #endif
     void foo(int);
     double bar(double);
     .
     .
     .
    #ifdef __cplusplus
    }
    #endif
    

    次は?

    次の投稿で CppInsight の連載が始まることをお知らせできることを大変うれしく思います。 CppInsight は、投稿やクラスで C++ コンパイラの魔法を示すために頻繁に使用する素晴らしいツールです。しかし、このツールには適切な紹介がありません。 CppInsight の著者である Andreas Fertig として、この紹介を書くのに適した人物は誰ですか?