メインコンテンツ

インライン制約が守られていません

const でない静的変数が非静的インライン関数で変更される

説明

この欠陥は、非静的インライン関数で、ファイル スコープの非 const 静的変数を参照した場合や、ローカルの非 const 静的変数を定義した場合に発生します。

たとえば、var は、inline 関数 func 内で定義された非 conststatic 変数です。g_step は、同じインライン関数内で参照先となるファイル スコープの非 const 静的変数です。

static int g_step;
inline void func (void) {
   static int var = 0;  // Defect
   var += g_step; // Defect
}

リスク

非静的インライン関数で非 const 静的変数を変更すると、予期しない誤った結果が生じる可能性があります。file2.cpp 内でインライン関数として使用する extern 関数 func() について考えます。この関数はインラインでない場合、file1.cpp 内で指定されるため、この関数には外部定義があります。

// file1.c
void func(void) {
   static var1 = 0;
   var2 = 0;
   var1++;
   var2++;
}
// file2.c
extern inline void func(void) {
   static var1 = 0;
   var2 = 0;
   var1++;
   var2++;
}

func() が非 const 静的変数を変更した場合、関数の動作が予測不能になります。func() を呼び出した場合、コンパイラはこの関数のインライン バージョンと非インライン バージョンのどちらでも呼び出すことができます。ISO®/IEC 9899:2011 の節6.7.4 を参照してください。func() に対して複数の呼び出しを行った場合、同じ静的変数 var1 が変更されない可能性があります。これは予期しない動作であり、誤った結果につながるおそれがあります。

修正方法

次のいずれかの修正方法を使用します。

  • 変数を変更する意図がない場合は、変数を const として宣言する。

    変数を変更しなければ、予期しない変更の問題は起こりません。

  • 変数を非 static にする。宣言から static 修飾子を削除します。

    変数が関数内で定義されている場合、その変数は標準ローカル変数になります。ファイル スコープで定義されている場合は extern 変数になります。

  • 関数を static にする。static 修飾子を関数定義に追加します。

    関数を static にすれば、インライン定義のあるファイルでは関数を呼び出すときに常にインライン定義が使用されます。他のファイルでは関数の別の定義が使用されます。どの関数定義が使用されるかという問題が、コンパイラ任せでなくなります。

すべて展開する

/* file1. c  : contains inline definition of get_random()*/

inline unsigned int get_random(void) 
{

    static unsigned int m_z = 0xdeadbeef; 
    static unsigned int m_w = 0xbaddecaf; 

    /* Compute next pseudorandom value and update seeds */
    m_z = 36969 * (m_z & 65535) + (m_z >> 16); 
    m_w = 18000 * (m_w & 65535) + (m_w >> 16); 
    return (m_z << 16) + m_w;   
}


int call_get_random(void)
{
    unsigned int rand_no;
    int ii;
    for (ii = 0; ii < 100; ii++) {
         rand_no = get_random();
    }
    rand_no = get_random();
    return 0;
}
/* file2. c  : contains external definition of get_random()*/

extern unsigned int get_random(void)
{
    /* Initialize seeds */
    static unsigned int m_z = 0xdeadbeef;
    static unsigned int m_w = 0xbaddecaf;
    
    /* Compute next pseudorandom value and update seeds */
    m_z = 36969 * (m_z & 65535) + (m_z >> 16);
    m_w = 18000 * (m_w & 65535) + (m_w >> 16);
    return (m_z << 16) + m_w;
}

この例では、get_random()file1.c にインライン定義があり、file2.c に外部定義があります。file1.cget_random が呼び出された場合、コンパイラでは自由にインライン定義を使用するか外部定義を使用するかを選択できます。get_random() のインライン バージョンにおいて m_z および m_w のバージョンを変更するかどうかは、コンパイラが使用する定義に依存します。この動作は、静的変数に対する通常の想定に矛盾します。C++では、すべての関数定義に含まれるローカルの静的変数はすべての翻訳単位で共有されるため、このコードを C++ コードとしてコンパイルする場合、この問題の影響はありません。

修正 — インライン関数を静的化

1 つの修正方法として、インライン化された get_random() を静的にします。コンパイラに関係なく、その後、file1.c での get_random() の呼び出しではインライン定義を使用します。他のファイルでの get_random() の呼び出しでは外部定義を使用します。この修正により、使用される定義と、その定義の静的変数が変更されるかどうかについて、あいまいさが解消されます。

/* file1. c  : contains inline definition of get_random()*/

static inline unsigned int get_random(void) 
{

    static unsigned int m_z = 0xdeadbeef; 
    static unsigned int m_w = 0xbaddecaf; 

    /* Compute next pseudorandom value and update seeds */
    m_z = 36969 * (m_z & 65535) + (m_z >> 16); 
    m_w = 18000 * (m_w & 65535) + (m_w >> 16); 
    return (m_z << 16) + m_w;   
}


int call_get_random(void)
{
    unsigned int rand_no;
    int ii;
    for (ii = 0; ii < 100; ii++) {
         rand_no = get_random();
    }
    rand_no = get_random();
    return 0;
}
/* file2. c  : contains external definition of get_random()*/

extern unsigned int get_random(void)
{
    /* Initialize seeds */
    static unsigned int m_z = 0xdeadbeef;
    static unsigned int m_w = 0xbaddecaf;
    
    /* Compute next pseudorandom value and update seeds */
    m_z = 36969 * (m_z & 65535) + (m_z >> 16);
    m_w = 18000 * (m_w & 65535) + (m_w >> 16);
    return (m_z << 16) + m_w;
}

結果情報

グループ: プログラミング
言語: C | C++
既定値: 手書きコードはオン、生成コードはオフ
コマンド ライン構文: INLINE_CONSTRAINT_NOT_RESPECTED
影響度: Medium

バージョン履歴

R2018a で導入

すべて展開する