メインコンテンツ

計算量の多い論理演算です

オペランドの順序により、論理演算が両方のオペランドを評価しなければならないため非効率なコードになる

R2021a 以降

説明

この欠陥は、以下の条件がすべて当てはまる場合に発生します。

  • 左辺オペランドと右辺オペランドに二次的影響がない。

  • 右辺オペランドにメンバー関数 const の呼び出しが含まれていない。

  • 左辺オペランドに 1 つ以上のメンバー関数 const の呼び出しが含まれている。

オペランドの潜在的な二次的影響を評価する場合、以下のようになります。

  • Polyspace® はクラスのメンバー関数 const に二次的影響がないと想定します。非メンバー関数には二次的影響があると想定されます。

  • Polyspace は C++ 規格に従って浮動小数点演算を扱います。C++03 以前では、浮動小数点演算に二次的影響はありません。C++11 以降では、浮動小数点ステータス フラグを変更して異常な結果や補足情報を示すなど、浮動小数点演算が二次的影響を及ぼす可能性があります。浮動小数点環境を参照してください。

  • Polyspace は、struct またはクラスの bool 変換演算子と論理 NOT 演算子を組み込み演算子として扱います。このような演算はメンバー関数呼び出しとして扱われません。標準テンプレート ライブラリには、このような bool 変換演算子や論理 NOT 演算子を定義する多数のクラスが含まれています。

リスク

論理演算を評価するときに、コンパイラは最初に左辺引数を評価し、必要な場合にのみ右辺引数を評価します。論理演算で、関数呼び出しを左辺引数として使用しながら、定数および変数を右辺引数として使用するのは非効率です。次のコードについて考えます。

if(Object.attribute()|| var1){
//...
}
if ステートメント内の論理式で、コンパイラは常に関数呼び出し Object.attribute() を評価します。関数の評価は常に必要であるとは限りません。たとえば、var1true に評価された場合、論理式は常に true に評価されます。var1 は右辺オペランドであり、左辺オペランドではないため、コンパイラは不必要な関数呼び出しの評価を行うことになり、非効率です。この非効率なコードは正しくコンパイルされて機能するため、この欠陥は検出されない可能性があります。

修正方法

右辺オペランドよりも "先" に実行する必要がある演算を左辺オペランドが実行するのではない場合、この欠陥を修正するには、論理式でのオペランドの順序を逆にします。これは右辺オペランドを安全かつ正しく評価するためです。

この条件に当てはまらない場合、コードは、フラグが設定された論理式をコンパイラが評価するとおりの順序に依存します。ベスト プラクティスは、式の評価順に依存しないことです。評価順が影響しないようにコードをリファクタリングすることを検討してください。コードをリファクタリングするのが不可能な場合は、注釈またはレビュー情報を使用して欠陥を正当化します。詳細は、以下を参照してください。

パフォーマンスの改善の程度は、使用しているコンパイラ、ライブラリ実装、環境によって異なる可能性があります。

すべて展開する

#include <string>
bool updateNewProperty( const std::string& name );
void updateNewMetaProperty( const std::string& name );
volatile char externalFlag;

void updateProperty( const std::string& name )
{
	bool is_new_property = updateNewProperty( name );

	if( name.compare( "meta" ) == 0 && is_new_property ) 
	{
		updateNewMetaProperty( name );
	}
	if( name.compare( "meta" ) == 0 && externalFlag ) 
	{
		updateNewMetaProperty( name );
	}
	
}

最初の if ステートメントで、変数 is_new_property は論理オペランドの右辺オペランドになっています。メンバー関数 std::string::compare は左辺引数として呼び出されます。コンパイラは is_new_property の値にかかわらず関数呼び出しを評価するため、非効率です。Polyspace はこの論理演算にフラグを設定します。

2 番目の if ステートメントでは、volatile 変数 externalFlag が右辺オペランドになっています。この変数は volatile であるため、Polyspace は変数が二次的影響を生じさせる可能性があると想定します。volatile 変数には二次的影響がある可能性があるため、Polyspace はこの論理演算にフラグを設定しません。

修正

式を安全かつ正しく評価するためにオペランドの順序を維持する必要があるかどうかを判断します。以下の例では、2 つのオペランド is_new_property および name.compare( "meta" ) == 0 は独立しているため、順序を変更しても論理式の値は変わりません。この欠陥を修正するには、is_new_property を左辺オペランドとして使用します。

#include <string>
bool updateNewProperty( const std::string& name );
void updateNewMetaProperty( const std::string& name );
volatile char externalFlag;

void updateProperty( const std::string& name )
{
	bool is_new_property = updateNewProperty( name );
	if(is_new_property && name.compare( "meta" ) == 0 ) 
	{
		updateNewMetaProperty( name );
	}
	if( name.compare( "meta" ) == 0 && externalFlag ) 
	{
		updateNewMetaProperty( name );
	}
}

コンパイラは、is_new_propertytrue である場合にのみ、最初の if ステートメント内の std::string::compare の呼び出しを評価します。

論理式で浮動小数点演算を使用する場合、Polyspace がオペランドの二次的影響をどのように推定するかは、使用している C++ のバージョンによって異なります。C++03 以前のバージョンでは、浮動小数点演算自体に二次的影響はありません。C++11 以降のバージョンでは、浮動小数点演算自体に二次的影響がある可能性があります。

class A{
	//...
	float makeFloat() const{
		//..
	}
	void testfloat(){
		if( makeFloat() == 0.1f && fp==0.2f) 
		{
			//...
		}
	}
	
private:
	float fp;		
};
C++03 を使用している場合、このコードでは、いずれのオペランドにも二次的影響はありません。左辺オペランドはメンバー関数を呼び出すため、Polyspace はこの式にフラグを設定します。

C++11 以降を使用している場合、浮動小数点演算に二次的影響がある可能性があります。この場合、Polyspace はこの論理式にフラグを設定しません。

修正

式を安全かつ正しく評価するためにオペランドの順序を維持する必要があるかどうかを判断します。以下の例では、2 つのオペランド fp==0.2f および makeFloat() == 0.1f は独立しているため、順序を変更しても論理式の値は変わりません。この欠陥を修正するには、fp==0.2f を左辺オペランドとして使用します。

class A{
	//...
	float makeFloat() const{
		//..
	}
	void testfloat(){
		if( fp==0.2f && makeFloat() == 0.1f) 
		{
			//...
		}
	}
	
private:
	float fp;		
};
コンパイラは、fp==0.2ftrue に評価された場合にのみ、makeFloat() の呼び出しを評価します。

#include<cstdlib>
class A{
	//...
	bool isLoaded() const { return p != NULL; }
	int get() {
		if(isLoaded() && *p > 0) { 
			return *p;
		}
	}

	
private:
	int* p;		
};

(isLoaded() && *p > 0) では、右辺引数で *p をデリファレンスしても安全なのは、左辺引数が true の場合のみです。Polyspace は、論理式にこのような特定の評価順が必要な場合でも、それをチェックしません。いずれのオペランドにも二次的影響はなく、メンバー関数呼び出しは左辺オペランドであるため、Polyspace はこの演算にフラグを設定します。

修正

以下の例では、式を安全かつ正しく評価するために、論理式でのオペランドの順序を維持しなければなりません。この欠陥を修正するには、コードをリファクタリングします。ベスト プラクティスは、式の評価順に依存しないようにすることです。

#include<cstdlib>
class A{
	//...
	bool isLoaded() const { return p != NULL; }
	int get() {
		if(isLoaded()== true) { 
			if(*p > 0){
				return *p;	
			}
			
		}
	}

	
private:
	int* p;		
};
このコードは 2 つの条件を個別にチェックし、評価順には依存しません。コードのリファクタリングが不可能な場合は、注釈またはレビュー情報を使用して欠陥を正当化します。

結果情報

グループ: パフォーマンス
言語: C++
既定値: オフ
コマンド ライン構文: EXPENSIVE_LOGICAL_OPERATION
影響度: Low

バージョン履歴

R2021a で導入