メインコンテンツ

AUTOSAR C++14 Rule A12-0-2

Bitwise operations and operations that assume data representation in memory shall not be performed on objects

説明

ルール定義

Bitwise operations and operations that assume data representation in memory shall not be performed on objects.

根拠

C++ では、メモリ内のオブジェクト表現には以下が含まれます。

  • さまざまなアクセス権限を使用して宣言されたデータ メンバー

  • ビット フィールド データ メンバー

  • データ メンバー間のパディング バイト

  • データ メンバーの末尾にあるパディング バイト

  • バーチャル関数をサポートするための vtable へのポインター

このようなメモリ内のオブジェクトのさまざまな部分の配置は環境によって異なります。加えて、オブジェクトの静的データ メンバーや関数メンバーは、メモリ内で個別の物理的な場所に保存されます。メモリ内のデータの特定の配置を前提とし、オブジェクトに対するビット演算を実行すると、気が付かずに前提を誤り、オブジェクトの値表現の一部ではないビットにアクセスする可能性があります。このようなビットへのアクセスは未定義動作につながる可能性があります。

バーチャル関数を含む次のクラスについて考えます。

class notPOD{
public:
	virtual void foo();
	int value;
protected:
	double dvalue;

};
//... 
int main(){
	notPOD Obj;
	std::memset(&Obj, 57, 2); // attempts to set Obj::value to 57
}
Obj がメモリ ブロック内に保存されている場合、そのブロックには変数 Obj::value と変数 Obj::dvalue に加えて、バーチャル テーブルへのポインターが含まれています。メモリ内のこのポインターのサイズまたはその位置は環境によって異なる場合があります。main() では、std::memset() が以下を前提として Obj::value の値を設定しようとします。

  • Obj::value は、Obj のメモリ表現内の最初のブロックです。

  • Obj::value は、メモリ内で 2 バイトで表現されます。

一般的にこれらの前提は正しくないため、std::memset() の使用は未定義動作につながる可能性があります。たとえば、バーチャル テーブルへのポインターを誤って変更した場合は、foo() を呼び出すと、予期せぬ関数が呼び出される可能性があります。

クラスの表現とメモリ内の構造は環境によって異なるため、値表現と一緒に追加のバイトが含まれている場合があります。オブジェクトのデータ表現に依存してビット演算を実行すると、値表現の一部ではないビットの変更を引き起こし、未定義動作につながる可能性があります。メモリ内のオブジェクトの特定の表現を前提としてビットにアクセスする演算は避けてください。クラスに対して演算を実行するには、専用のメンバー関数、オーバーロードされた演算子、またはミューテーターを使用してください。

Polyspace 実装

メモリ ビットにアクセスする C 関数には、std::memset()std::memcpy()std::memmove()std::strcpy()std::memcmp()std::strcmp() があります。Polyspace® は、以下の場合にステートメントにフラグを設定します。

  • C 関数を使用して、非トリビアル オブジェクトを初期化またはコピーする。

  • C 関数を使用して、非標準のレイアウト オブジェクトを比較する。

  • パディング データを含むオブジェクトに対して C 関数を使用する。

非準拠の演算を含むステートメントにはフラグが設定され、関連するクラス宣言が強調表示されます。トリビアルおよび標準のレイアウト クラスに関する定義については、C++ Standard、[class]、段落 6 および 7 をそれぞれ参照してください。

例外として、パディング データを含まないトリビアルと標準のレイアウト オブジェクトのメモリ ビットに C 関数を使用してアクセスする演算には、Polyspace はフラグを設定しません。パディング データを含まないトリビアルと標準のレイアウト クラスに対するビット演算の使用は、このルールに準拠していますが、適切な使用法ではありません。代わりに、専用のメンバー関数、オーバーロードされた演算子、またはミューテーターを使用してください。

トラブルシューティング

ルール違反が想定されるものの、Polyspace から報告されない場合は、コーディング規約違反が想定どおりに表示されない理由の診断を参照してください。

すべて展開する

これらのクラスを含む次のコードについて考えます。

  • TrivialClass は、パディング データとオーバーロードされた operator|= を含むトリビアル クラスです。

  • NonTrivialClass は、バーチャル関数とオーバーロードされた operator== を含む非トリビアル クラスです。

これらのクラスは、メモリ内で異なる方法で表現されます。この例では、Polyspace がこのようなオブジェクトに対して実行されるビット演算にどのようにフラグを設定するかを示します。

#include <cstdint>
#include <cstring>
class TrivialClass
{
public:
	TrivialClass() = default;
	TrivialClass(uint8_t c, uint32_t i, int8_t d) :
	c(c), i(i), d(d) {}
	TrivialClass& operator |=(const TrivialClass& other)
	{
		uint32_t buf[4] {this->c|other.c,this->i|other.i,this->d|other.d};
		memcpy(this, buf, sizeof(uint32_t) * 3); //Noncompliant
		return *this;
	}
	
private:
	uint8_t c;
	uint32_t i;
	int8_t d;
};

class NonTrivialClass 
{
public:
	NonTrivialClass() = default;
	NonTrivialClass(uint32_t a, uint32_t b, uint32_t c) : 
	a(a), b(b), c(c){}
	bool operator==(const NonTrivialClass& rhs) const noexcept
	{
		return a==rhs.a && b==rhs.b && c==rhs.c;
	}
	virtual ~NonTrivialClass() {} 
private:
	uint32_t a;
	uint32_t b;
	uint32_t c;
};

int main(void)
{
	TrivialClass A, A1{3,5,7};
	NonTrivialClass B, B1{10,11,12};
	std::memset(&A, 3, 1); //Noncompliant
	A |= A1; 
	if (!std::memcmp(&A, &A1, sizeof(TrivialClass))) {} //Noncompliant
	std::memcpy(&B, &B1, sizeof(NonTrivialClass)); //Noncompliant
	if (B == B1){} //Compliant
	return 0;
}

  • Polyspace はステートメント std::memset(&A, 3, 1); にフラグを設定します。これは、このステートメント内で std::memset() がパディング データを含むトリビアル オブジェクト A のメモリ表現内の個別のビットを変更するためです。オブジェクトがトリビアル クラス オブジェクトであっても、オブジェクトのパディング データ ビットにアクセスするとこのルールに違反します。同じ理由で、Polyspace は memcopy() を含む TrivialClass::operator|= の定義内のステートメントにフラグを設定します。

  • Polyspace はステートメント std::memcpy(&B, &B1, sizeof(NonTrivialClass)); にフラグを設定します。これは、std::memcpy() が vtable へのポインターを含む非トリビアル オブジェクト B のメモリ表現内の個別のビットにアクセスするためです。このポインターは値表現の一部ではないため、このポインターへのアクセスはこのルールに違反します。

  • Polyspace は if(B==B1) ステートメントにフラグを設定しません。これは、個別のビットにアクセスすることなく BB1 を比較することができる、オーバーロードされた operator==NonTrivialClass に含まれているためです。

チェック情報

グループ: 特殊なメンバー関数
カテゴリ: Required、Partially automated

バージョン履歴

R2020b で導入